chore: Remove even more dead sources (#1674)
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,12 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'CARTOONS4U'
|
||||
pkgNameSuffix = 'ar.cartons4u'
|
||||
extClass = '.Cartoons4U'
|
||||
extVersionCode = 3
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 99 KiB |
@ -1,188 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.cartons4u
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.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.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Exception
|
||||
|
||||
class Cartoons4U : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "CARTOONS4U"
|
||||
|
||||
override val baseUrl = "https://cartoons4u.net"
|
||||
|
||||
override val lang = "ar"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// Popular
|
||||
|
||||
override fun popularAnimeSelector(): String = "ul.MovieList.Rows.Alt li.TPostMv article.TPost a:has(div.Image)"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/category/movies/page/$page/")
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.attr("href"))
|
||||
// anime.thumbnail_url = "https:" + element.select("div.Image figure img").attr("data-src") // .replace("//", "")
|
||||
anime.title = element.select("div.Title").text().replace("فيلم", "")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "div.wp-pagenavi a.next"
|
||||
|
||||
// episodes
|
||||
|
||||
override fun episodeListSelector() = "link[rel=canonical]"
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode {
|
||||
val episode = SEpisode.create()
|
||||
episode.setUrlWithoutDomain(element.attr("href"))
|
||||
episode.name = element.ownerDocument()!!.select("header.Top h1").text()
|
||||
return episode
|
||||
}
|
||||
|
||||
// Video links
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val iframe = document.select("span.Button.TrLnk").attr("data-id")
|
||||
val referer = response.request.url.toString()
|
||||
val refererHeaders = Headers.headersOf("referer", referer)
|
||||
val iframeResponse = client.newCall(GET(iframe, refererHeaders))
|
||||
.execute().asJsoup()
|
||||
return videosFromElement(iframeResponse.selectFirst(videoListSelector())!!)
|
||||
}
|
||||
|
||||
override fun videoListSelector() = "a.button.is-success"
|
||||
|
||||
private fun videosFromElement(element: Element): List<Video> {
|
||||
val vidURL = element.attr("abs:href")
|
||||
val apiCall = client.newCall(POST(vidURL.replace("/v/", "/api/source/"))).execute().body.string()
|
||||
val data = apiCall.substringAfter("\"data\":[").substringBefore("],")
|
||||
val sources = data.split("\"file\":\"").drop(1)
|
||||
val videoList = mutableListOf<Video>()
|
||||
for (source in sources) {
|
||||
val src = source.substringAfter("\"file\":\"").substringBefore("\"").replace("\\/", "/")
|
||||
val quality = source.substringAfter("\"label\":\"").substringBefore("\"")
|
||||
val video = Video(vidURL, quality, src)
|
||||
videoList.add(video)
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun videoFromElement(element: Element) = throw Exception("not used")
|
||||
|
||||
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 videoUrlParse(document: Document) = throw Exception("not used")
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.setUrlWithoutDomain(element.attr("href"))
|
||||
// anime.thumbnail_url = element.select("div.Image figure img").attr("data-src").replace("//", "")
|
||||
anime.title = element.select("div.Title").text().replace("فيلم", "")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = "div.wp-pagenavi a.next"
|
||||
|
||||
override fun searchAnimeSelector(): String = "ul.MovieList.Rows.Alt li.TPostMv article.TPost a:has(div.Image)"
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val url = "$baseUrl/page/$page/?s=$query".toHttpUrlOrNull()!!.newBuilder()
|
||||
return GET(url.build().toString(), headers)
|
||||
}
|
||||
|
||||
// Anime Details
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
val getThumbnail = document.select("figure.Objf img.lazy").attr("data-src")
|
||||
val getThumbnail1 = document.select("figure.Image img").attr("data-lazy-src")
|
||||
if (getThumbnail.startsWith("//")) {
|
||||
anime.thumbnail_url = "https:" + getThumbnail
|
||||
} else {
|
||||
anime.thumbnail_url = getThumbnail1
|
||||
}
|
||||
anime.title = document.select("header.Top h1.Title").text()
|
||||
anime.genre = document.select("p.Genre a").joinToString(", ") { it.text() }
|
||||
anime.description = document.select("div.Description p:first-child").text()
|
||||
anime.status = SAnime.COMPLETED
|
||||
return anime
|
||||
}
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesSelector(): String = throw Exception("Not used")
|
||||
|
||||
// preferred quality settings
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val videoQualityPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p", "240p")
|
||||
entryValues = arrayOf("1080", "720", "480", "360", "240")
|
||||
setDefaultValue("1080")
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,17 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'شاهد فور يو'
|
||||
pkgNameSuffix = 'ar.shahid4u'
|
||||
extClass = '.Shahid4U'
|
||||
extVersionCode = 9
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-okru-extractor'))
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 97 KiB |
@ -1,380 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.shahid4u
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.ar.shahid4u.extractors.UQLoadExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.ar.shahid4u.extractors.VidBomExtractor
|
||||
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.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.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Exception
|
||||
|
||||
class Shahid4U : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "شاهد فور يو"
|
||||
|
||||
override val baseUrl = "https://shahed4u.agency"
|
||||
|
||||
override val lang = "ar"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// Popular
|
||||
|
||||
override fun popularAnimeSelector(): String = "div.glide-slides div.media-block"
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET(if (page == 1)" $baseUrl/home2/" else baseUrl)
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.title = titleEdit(element.select("h3").text()).trim()
|
||||
anime.thumbnail_url = element.select("a.image img").attr("data-src")
|
||||
anime.setUrlWithoutDomain(element.select("a.fullClick").attr("href") + "watch/")
|
||||
return anime
|
||||
}
|
||||
|
||||
private fun titleEdit(title: String, details: Boolean = false): String {
|
||||
return if (Regex("فيلم (.*?) مترجم").containsMatchIn(title)) {
|
||||
Regex("فيلم (.*?) مترجم").find(title)!!.groupValues[1] + " (فيلم)" // افلام اجنبيه مترجمه
|
||||
} else if (Regex("فيلم (.*?) مدبلج").containsMatchIn(title)) {
|
||||
Regex("فيلم (.*?) مدبلج").find(title)!!.groupValues[1] + " (مدبلج)(فيلم)" // افلام اجنبيه مدبلجه
|
||||
} else if (Regex("فيلم ([^a-zA-Z]+) ([0-9]+)").containsMatchIn(title)) {
|
||||
// افلام عربى
|
||||
Regex("فيلم ([^a-zA-Z]+) ([0-9]+)").find(title)!!.groupValues[1] + " (فيلم)"
|
||||
} else if (title.contains("مسلسل")) {
|
||||
if (title.contains("الموسم") and details) {
|
||||
val newTitle = Regex("مسلسل (.*?) الموسم (.*?) الحلقة ([0-9]+)").find(title)
|
||||
return "${newTitle!!.groupValues[1]} (م.${newTitle.groupValues[2]})(${newTitle.groupValues[3]}ح)"
|
||||
} else if (title.contains("الحلقة") and details) {
|
||||
val newTitle = Regex("مسلسل (.*?) الحلقة ([0-9]+)").find(title)
|
||||
return "${newTitle!!.groupValues[1]} (${newTitle.groupValues[2]}ح)"
|
||||
} else {
|
||||
Regex(if (title.contains("الموسم")) "مسلسل (.*?) الموسم" else "مسلسل (.*?) الحلقة").find(title)!!.groupValues[1] + " (مسلسل)"
|
||||
}
|
||||
} else if (title.contains("انمي")) {
|
||||
return Regex(if (title.contains("الموسم"))"انمي (.*?) الموسم" else "انمي (.*?) الحلقة").find(title)!!.groupValues[1] + " (انمى)"
|
||||
} else if (title.contains("برنامج")) {
|
||||
Regex(if (title.contains("الموسم"))"برنامج (.*?) الموسم" else "برنامج (.*?) الحلقة").find(title)!!.groupValues[1] + " (برنامج)"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "div.paginate ul.page-numbers li.active + li a"
|
||||
|
||||
// episodes
|
||||
|
||||
private fun seasonsNextPageSelector() = "div.allseasonstab ul li"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val episodes = mutableListOf<SEpisode>()
|
||||
fun addEpisodeNew(url: String, type: String, title: String = "") {
|
||||
val episode = SEpisode.create()
|
||||
episode.setUrlWithoutDomain(url)
|
||||
if (type == "assembly") {
|
||||
episode.name = title.replace("فيلم", "").trim()
|
||||
} else if (type == "movie") {
|
||||
episode.name = "مشاهدة"
|
||||
} else {
|
||||
episode.name = title
|
||||
}
|
||||
|
||||
episodes.add(episode)
|
||||
}
|
||||
fun addEpisodes(response: Response) {
|
||||
val document = response.asJsoup()
|
||||
val url = response.request.url.toString()
|
||||
if (url.contains("assemblies")) {
|
||||
for (movie in document.select(searchAnimeSelector())) {
|
||||
addEpisodeNew(
|
||||
movie.select("a.fullClick").attr("href") + "watch/",
|
||||
"assembly",
|
||||
movie.select("h3").text(),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (document.select("div.seasons--episodes").isNullOrEmpty()) {
|
||||
// Movies
|
||||
addEpisodeNew(url, "movie")
|
||||
} else {
|
||||
// Series
|
||||
// look for what is wrong
|
||||
for (season in document.select(seasonsNextPageSelector())) {
|
||||
val seasonNum = season.text()
|
||||
if (season.hasClass("active")) {
|
||||
// get episodes from page
|
||||
for (episode in document.select("ul.episodes-list li a")) {
|
||||
addEpisodeNew(
|
||||
episode.attr("href"),
|
||||
"series",
|
||||
seasonNum + " " + episode.text(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// send request to get episodes
|
||||
val seasonData = season.attr("data-id")
|
||||
val refererHeaders = Headers.headersOf("referer", response.request.url.toString(), "x-requested-with", "XMLHttpRequest")
|
||||
val requestBody = FormBody.Builder().add("season", seasonData).build()
|
||||
val getEpisodes = client.newCall(POST("$baseUrl/wp-content/themes/Shahid4u-WP_HOME/Ajaxat/Single/Episodes.php", refererHeaders, requestBody)).execute().asJsoup()
|
||||
for (episode in getEpisodes.select("li a")) {
|
||||
addEpisodeNew(
|
||||
episode.attr("href"),
|
||||
"series",
|
||||
seasonNum + " " + episode.text(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addEpisodes(response)
|
||||
return episodes
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = "link[rel=canonical]"
|
||||
|
||||
override fun episodeFromElement(element: Element): SEpisode = throw Exception("not used")
|
||||
|
||||
// Video links
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videos = mutableListOf<Video>()
|
||||
val servers = document.select(videoListSelector())
|
||||
fun getUrl(v_id: String, i: String): String {
|
||||
val refererHeaders = Headers.headersOf(
|
||||
"referer",
|
||||
response.request.url.toString(),
|
||||
"x-requested-with",
|
||||
"XMLHttpRequest",
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"user-agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42",
|
||||
)
|
||||
val requestBody = FormBody.Builder().add("id", v_id).add("i", i).build()
|
||||
val iframe = client.newCall(
|
||||
POST(
|
||||
"$baseUrl/wp-content/themes/Shahid4u-WP_HOME/Ajaxat/Single/Server.php",
|
||||
refererHeaders,
|
||||
requestBody,
|
||||
),
|
||||
).execute().asJsoup()
|
||||
return iframe.select("iframe").attr("src")
|
||||
}
|
||||
for (server in servers) {
|
||||
if (server.hasClass("active")) {
|
||||
// special server
|
||||
val videosFromURL = videosFromElement(client.newCall(GET(document.select("input[name=fserver]").`val`(), headers)).execute().asJsoup())
|
||||
videos.addAll(videosFromURL)
|
||||
} else if (server.text().contains("ok")) {
|
||||
val videosFromURL = OkruExtractor(client).videosFromUrl(getUrl(server.attr("data-id"), server.attr("data-i")))
|
||||
videos.addAll(videosFromURL)
|
||||
} else if (server.text().contains("vidbom", ignoreCase = true) or server.text().contains("Vidshare", ignoreCase = true)) {
|
||||
val videosFromURL = VidBomExtractor(client).videosFromUrl(getUrl(server.attr("data-id"), server.attr("data-i")))
|
||||
videos.addAll(videosFromURL)
|
||||
} else if (server.text().contains("dood", ignoreCase = true)) {
|
||||
val videosFromURL = DoodExtractor(client).videoFromUrl(getUrl(server.attr("data-id"), server.attr("data-i")))
|
||||
if (videosFromURL != null) videos.add(videosFromURL)
|
||||
} else if (server.text().contains("uqload", ignoreCase = true)) {
|
||||
val videosFromURL = UQLoadExtractor(client).videoFromUrl(getUrl(server.attr("data-id"), server.attr("data-i")), "Uqload: 720p")
|
||||
if (videosFromURL != null) videos.add(videosFromURL)
|
||||
}
|
||||
}
|
||||
return videos
|
||||
}
|
||||
|
||||
override fun videoListSelector() = "ul.servers-list li.server--item"
|
||||
|
||||
private fun videosFromElement(document: Document): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
return try {
|
||||
val scriptSelect = document.selectFirst("script:containsData(eval)")!!.data()
|
||||
val serverPrefix =
|
||||
scriptSelect.substringAfter("|net|cdn|amzn|").substringBefore("|rewind|icon|")
|
||||
val sourceServer = "https://$serverPrefix.e-amzn-cdn.net"
|
||||
val qualities = scriptSelect.substringAfter("|image|").substringBefore("|sources|")
|
||||
.replace("||", "|").split("|")
|
||||
qualities.forEachIndexed { i, q ->
|
||||
if (i % 2 == 0) {
|
||||
val id = qualities[i + 1]
|
||||
val src = "$sourceServer/$id/v.mp4"
|
||||
val video = Video(src, "Main: $q", src)
|
||||
videoList.add(video)
|
||||
}
|
||||
}
|
||||
videoList
|
||||
} catch (e: Exception) {
|
||||
videoList
|
||||
}
|
||||
}
|
||||
|
||||
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 videoUrlParse(document: Document) = throw Exception("not used")
|
||||
|
||||
override fun videoFromElement(element: Element) = throw Exception("not used")
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
var link = element.select("a.fullClick").attr("href")
|
||||
anime.title = titleEdit(element.select("h3").text(), details = true).trim()
|
||||
if (link.contains("assemblies")) {
|
||||
anime.thumbnail_url = element.select("a.image img").attr("data-src")
|
||||
} else {
|
||||
anime.thumbnail_url = element.select("a.image img.imgInit").attr("data-image")
|
||||
}
|
||||
if (!link.contains("assemblies")) link += "watch/"
|
||||
anime.setUrlWithoutDomain(link)
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = "div.paginate ul.page-numbers li.active + li a"
|
||||
|
||||
override fun searchAnimeSelector(): String = "div.MediaGrid div.media-block"
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val url = if (query.isNotBlank()) {
|
||||
"$baseUrl/page/$page/?s=$query"
|
||||
} else {
|
||||
val url = "$baseUrl/home2/page/$page"
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||
when (filter) {
|
||||
is CategoryList -> {
|
||||
if (filter.state > 0) {
|
||||
val catQ = getCategoryList()[filter.state].query
|
||||
val catUrl = "$baseUrl/$catQ/page/$page/"
|
||||
return GET(catUrl, headers)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
// Anime Details
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val anime = SAnime.create()
|
||||
// div.CoverSingle div.CoverSingleContent
|
||||
anime.genre = document.select("div.SingleDetails li:contains(النوع) a").joinToString(", ") { it.text() }
|
||||
anime.title = titleEdit(document.select("meta[property=og:title]").attr("content")).trim()
|
||||
anime.author = document.select("div.SingleDetails li:contains(دولة) a").text()
|
||||
anime.description = document.select("div.ServersEmbeds section.story").text().replace(document.select("meta[property=og:title]").attr("content"), "").replace(":", "").trim()
|
||||
anime.status = SAnime.COMPLETED
|
||||
if (anime.title.contains("سلسلة")) anime.thumbnail_url = document.selectFirst("img.imgInit")!!.attr("data-image")
|
||||
return anime
|
||||
}
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = "div.paginate ul.page-numbers li.active + li a"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SAnime {
|
||||
val anime = SAnime.create()
|
||||
anime.title = titleEdit(element.select("h3").text()).trim()
|
||||
anime.thumbnail_url = element.select("a.image img").attr("data-image")
|
||||
anime.setUrlWithoutDomain(element.select("a.fullClick").attr("href") + "watch/")
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home2/page/$page/")
|
||||
|
||||
override fun latestUpdatesSelector(): String = "div.MediaGrid div.media-block"
|
||||
|
||||
// Filters
|
||||
|
||||
override fun getFilterList() = AnimeFilterList(
|
||||
CategoryList(categoriesName),
|
||||
)
|
||||
|
||||
private class CategoryList(categories: Array<String>) : AnimeFilter.Select<String>("الأقسام", categories)
|
||||
private data class CatUnit(val name: String, val query: String)
|
||||
private val categoriesName = getCategoryList().map {
|
||||
it.name
|
||||
}.toTypedArray()
|
||||
|
||||
private fun getCategoryList() = listOf(
|
||||
CatUnit("اختر", ""),
|
||||
CatUnit("افلام اجنبى", "category/%d8%a7%d9%81%d9%84%d8%a7%d9%85-%d8%a7%d8%ac%d9%86%d8%a8%d9%8a-3/"),
|
||||
CatUnit("افلام انمى", "category/افلام-انمي/"),
|
||||
CatUnit("افلام تركيه", "category/افلام-تركية/"),
|
||||
CatUnit("افلام اسيويه", "category/افلام-اسيوية/"),
|
||||
CatUnit("افلام هنديه", "category/افلام-هندي-1/"),
|
||||
CatUnit("سلاسل افلام", "assemblies/"),
|
||||
CatUnit("مسلسلات اجنبى", "category/مسلسلات-اجنبي-1/"),
|
||||
CatUnit("مسلسلات انمى", "category/مسلسلات-انمي-4/"),
|
||||
CatUnit("مسلسلات تركى", "category/مسلسلات-تركي-3/"),
|
||||
CatUnit("مسلسلات اسيوى", "category/مسلسلات-اسيوي/"),
|
||||
CatUnit("مسلسلات هندى", "category/مسلسلات-هندية/"),
|
||||
)
|
||||
|
||||
// preferred quality settings
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val videoQualityPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
title = "Preferred quality"
|
||||
entries = arrayOf("1080p", "720p", "480p", "360p", "240p")
|
||||
entryValues = arrayOf("1080", "720", "480", "360", "240")
|
||||
setDefaultValue("1080")
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.shahid4u.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class UQLoadExtractor(private val client: OkHttpClient) {
|
||||
fun videoFromUrl(url: String, quality: String): Video? {
|
||||
val document = client.newCall(GET(url)).execute().asJsoup()
|
||||
val check = document.selectFirst("script:containsData(sources)")!!.data()
|
||||
val videoUrl = check.substringAfter("sources: [\"").substringBefore("\"")
|
||||
return if (check.contains("sources")) {
|
||||
Video(url, quality, videoUrl)
|
||||
} else {
|
||||
Video(url, "no 1video", "https")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.ar.shahid4u.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class VidBomExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
val script = doc.selectFirst("script:containsData(sources)")!!
|
||||
val data = script.data().substringAfter("sources: [").substringBefore("],")
|
||||
val sources = data.split("file:\"").drop(1)
|
||||
val videoList = mutableListOf<Video>()
|
||||
for (source in sources) {
|
||||
val src = source.substringBefore("\"")
|
||||
var quality = "Vidbom: " + source.substringAfter("label:\"").substringBefore("\"") // .substringAfter("format: '")
|
||||
if (quality.length > 15) {
|
||||
quality = "Vidshare: 480p"
|
||||
}
|
||||
val video = Video(src, quality, src)
|
||||
videoList.add(video)
|
||||
}
|
||||
return videoList
|
||||
/*Log.i("looool", "$js")
|
||||
val json = JSONObject(js)
|
||||
Log.i("looool", "$json")
|
||||
val videoList = mutableListOf<Video>()
|
||||
val jsonArray = json.getJSONArray("sources")
|
||||
for (i in 0 until jsonArray.length()) {
|
||||
val `object` = jsonArray.getJSONObject(i)
|
||||
val videoUrl = `object`.getString("file")
|
||||
Log.i("looool", videoUrl)
|
||||
val quality = "Vidbom:" + `object`.getString("label")
|
||||
videoList.add(Video(videoUrl, quality, videoUrl))
|
||||
}
|
||||
return videoList*/
|
||||
/*if (jas.contains("sources")) {
|
||||
val js = script.data()
|
||||
val json = JSONObject(js)
|
||||
val videoList = mutableListOf<Video>()
|
||||
val jsonArray = json.getJSONArray("sources")
|
||||
for (i in 0 until jsonArray.length()) {
|
||||
val `object` = jsonArray.getJSONObject(i)
|
||||
val videoUrl = `object`.getString("file")
|
||||
Log.i("lol", videoUrl)
|
||||
val quality = "Vidbom:" + `object`.getString("label")
|
||||
videoList.add(Video(videoUrl, quality, videoUrl))
|
||||
}
|
||||
return videoList
|
||||
} else {
|
||||
val videoList = mutableListOf<Video>()
|
||||
videoList.add(Video(url, "no 2video", null))
|
||||
return videoList
|
||||
}*/
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,18 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'FireAnime'
|
||||
pkgNameSuffix = 'de.fireanime'
|
||||
extClass = '.FireAnime'
|
||||
extVersionCode = 7
|
||||
libVersion = '13'
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib-dood-extractor'))
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB |
@ -1,21 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.fireanime
|
||||
|
||||
object FAConstants {
|
||||
const val LANG_SUB = "Sub"
|
||||
const val LANG_DUB = "Dub"
|
||||
|
||||
val LANGS = arrayOf(LANG_SUB, LANG_DUB)
|
||||
|
||||
const val PREFERRED_SOURCE = "preferred_source"
|
||||
const val PREFERRED_LANG = "preferred_sub"
|
||||
const val SOURCE_SELECTION = "source_selection"
|
||||
|
||||
const val NAME_FIRECDN = "FireCDN"
|
||||
const val NAME_DOOD = "Doodstream"
|
||||
|
||||
const val URL_FIRECDN = "https://firecdn"
|
||||
const val URL_DOOD = "https://dood"
|
||||
|
||||
val SOURCE_NAMES = arrayOf(NAME_FIRECDN, NAME_DOOD)
|
||||
val SOURCE_URLS = arrayOf(URL_FIRECDN, URL_DOOD)
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.fireanime
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.AbsSourceBaseDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.AnimeBaseDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.AnimeDetailsDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.AnimeDetailsWrapperDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.CdnSourceDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.EpisodeListingWrapperDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.EpisodeSourcesDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.HosterSourceDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.VideoLinkDto
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.extractors.FireCdnExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FireAnime : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override val name = "FireAnime"
|
||||
|
||||
override val baseUrl = "https://api.fireani.me"
|
||||
|
||||
override val lang = "de"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(120, 1, TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
private val json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ===== POPULAR ANIME =====
|
||||
override fun popularAnimeRequest(page: Int): Request = POST(
|
||||
"$baseUrl/api/public/airing",
|
||||
body = FormBody.Builder()
|
||||
.add("langs[0]", "de-DE")
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage = parseAnimeListJson(response, true)
|
||||
|
||||
// ===== LATEST ANIME =====
|
||||
override fun latestUpdatesRequest(page: Int): Request = POST(
|
||||
"$baseUrl/api/public/new",
|
||||
body = FormBody.Builder()
|
||||
.add("langs[0]", "de-DE")
|
||||
.add("limit", "30")
|
||||
.add("offset", (page - 1).toString())
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = parseAnimeListJson(response)
|
||||
|
||||
// ===== ANIME SEARCH =====
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = POST(
|
||||
"$baseUrl/api/public/search",
|
||||
body = FormBody.Builder()
|
||||
.add("q", query)
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = parseAnimeListJson(response, true)
|
||||
|
||||
// ===== ANIME LIST PARSING =====
|
||||
private fun parseAnimeListJson(response: Response, singlePage: Boolean = false): AnimesPage {
|
||||
val animes = json.decodeFromString(ListSerializer(AnimeBaseDto.serializer()), response.body.string())
|
||||
.distinctBy { it.url }
|
||||
return AnimesPage(animes.map { createAnime(it) }, animes.count() > 0 && !singlePage)
|
||||
}
|
||||
|
||||
// ===== ANIME DETAILS =====
|
||||
override fun animeDetailsRequest(anime: SAnime): Request = POST(
|
||||
"$baseUrl/api/public/anime",
|
||||
body = FormBody.Builder()
|
||||
.add("url", anime.url)
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
return client.newCall(animeDetailsRequest(anime))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
animeDetailsParse(response, anime).apply { initialized = true }
|
||||
}
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime = throw UnsupportedOperationException("Not used")
|
||||
|
||||
private fun animeDetailsParse(response: Response, baseAnime: SAnime): SAnime {
|
||||
val anime = json.decodeFromString(AnimeDetailsWrapperDto.serializer(), response.body.string()).response
|
||||
return createAnime(baseAnime, anime)
|
||||
}
|
||||
|
||||
// ===== CREATE ANIME =====
|
||||
private fun createAnime(anime: AnimeBaseDto): SAnime {
|
||||
return SAnime.create().apply {
|
||||
title = anime.title
|
||||
url = anime.url
|
||||
thumbnail_url = "$baseUrl/api/get/img/" + anime.imgPoster + "-normal-poster.webp"
|
||||
status = SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAnime(baseAnime: SAnime, details: AnimeDetailsDto): SAnime {
|
||||
return baseAnime.apply {
|
||||
description = details.description
|
||||
genre = "FSK ${details.fsk}, " + (if (details.votingDouble != null) "%.1f/5 ⭐, ".format(details.votingDouble) else "") + details.genres.joinToString(", ") { it.genre }
|
||||
}
|
||||
}
|
||||
|
||||
// ===== EPISODE =====
|
||||
override fun episodeListRequest(anime: SAnime): Request = POST(
|
||||
"$baseUrl/api/public/episodes",
|
||||
body = FormBody.Builder()
|
||||
.add("url", anime.url)
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||
return if (anime.status != SAnime.LICENSED) {
|
||||
client.newCall(episodeListRequest(anime))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
episodeListParse(response, anime.url)
|
||||
}
|
||||
} else {
|
||||
Observable.error(Exception("Licensed - No episodes to show"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = throw UnsupportedOperationException("Not used")
|
||||
|
||||
private fun episodeListParse(response: Response, animeUrl: String): List<SEpisode> {
|
||||
val episodes = json.decodeFromString(EpisodeListingWrapperDto.serializer(), response.body.string()).response
|
||||
return episodes.mapIndexed { i, ep ->
|
||||
SEpisode.create().apply {
|
||||
episode_number = ep.episode.toFloat()
|
||||
name = if (ep.title.startsWith("Episode")) ep.title else "Episode ${i + 1}: ${ep.title}"
|
||||
url = animeUrl + (-1..i).joinToString("") { " " } // Add some spaces so that all episodes are shown
|
||||
}
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
// ===== VIDEO SOURCES =====
|
||||
override fun videoListRequest(episode: SEpisode): Request = POST(
|
||||
"$baseUrl/api/public/episode",
|
||||
body = FormBody.Builder()
|
||||
.add("url", episode.url.trim())
|
||||
.add("ep", "%.0f".format(episode.episode_number))
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val episode = json.decodeFromString(EpisodeSourcesDto.serializer(), response.body.string().substringAfter("\"ep\":").substringBefore(",\"next\""))
|
||||
|
||||
val videos = mutableListOf<Video>()
|
||||
videos.addAll(getVideos(episode.cdns, "$baseUrl/api/public/cdn"))
|
||||
videos.addAll(getVideos(episode.hosters, "$baseUrl/api/public/link"))
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getVideos(sources: List<AbsSourceBaseDto>, apiUrl: String): List<Video> {
|
||||
return sources.mapNotNull { source ->
|
||||
val linkRequest = POST(
|
||||
apiUrl,
|
||||
body = FormBody.Builder()
|
||||
.add("id", source.id.toString())
|
||||
.build(),
|
||||
)
|
||||
val link = json.decodeFromString(VideoLinkDto.serializer(), client.newCall(linkRequest).execute().body.string()).url
|
||||
|
||||
val sourceName = if (source is CdnSourceDto) FAConstants.NAME_FIRECDN else (source as HosterSourceDto).hoster
|
||||
val lang = if (source.isSub == 1) FAConstants.LANG_SUB else FAConstants.LANG_DUB
|
||||
val quality = "$sourceName, $lang"
|
||||
|
||||
val sourceSelection = preferences.getStringSet(FAConstants.SOURCE_SELECTION, FAConstants.SOURCE_NAMES.toSet())
|
||||
|
||||
fun isSource(source: String): Boolean = sourceName == source && sourceSelection?.contains(source) == true
|
||||
when {
|
||||
isSource(FAConstants.NAME_FIRECDN) -> {
|
||||
FireCdnExtractor(client, json).videoFromUrl(link, quality)
|
||||
}
|
||||
isSource(FAConstants.NAME_DOOD) -> {
|
||||
val video = try {
|
||||
DoodExtractor(client).videoFromUrl(link, quality, false)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
video
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val hoster = preferences.getString(FAConstants.PREFERRED_SOURCE, null)
|
||||
val subPreference = preferences.getString(FAConstants.PREFERRED_LANG, FAConstants.LANG_SUB)!!
|
||||
val hosterList = mutableListOf<Video>()
|
||||
val otherList = mutableListOf<Video>()
|
||||
if (hoster != null) {
|
||||
for (video in this) {
|
||||
if (video.url.contains(hoster)) {
|
||||
hosterList.add(video)
|
||||
} else {
|
||||
otherList.add(video)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
otherList += this
|
||||
}
|
||||
val newList = mutableListOf<Video>()
|
||||
var preferred = 0
|
||||
for (video in hosterList) {
|
||||
if (video.quality.contains(subPreference)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
for (video in otherList) {
|
||||
if (video.quality.contains(subPreference)) {
|
||||
newList.add(preferred, video)
|
||||
preferred++
|
||||
} else {
|
||||
newList.add(video)
|
||||
}
|
||||
}
|
||||
|
||||
return newList
|
||||
}
|
||||
|
||||
// ===== PREFERENCES ======
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val hosterPref = ListPreference(screen.context).apply {
|
||||
key = FAConstants.PREFERRED_SOURCE
|
||||
title = "Standard-Quelle"
|
||||
entries = FAConstants.SOURCE_NAMES
|
||||
entryValues = FAConstants.SOURCE_URLS
|
||||
setDefaultValue(FAConstants.URL_FIRECDN)
|
||||
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 subPref = ListPreference(screen.context).apply {
|
||||
key = FAConstants.PREFERRED_LANG
|
||||
title = "Standardmäßig Sub oder Dub?"
|
||||
entries = FAConstants.LANGS
|
||||
entryValues = FAConstants.LANGS
|
||||
setDefaultValue(FAConstants.LANG_SUB)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
val subSelection = MultiSelectListPreference(screen.context).apply {
|
||||
key = FAConstants.SOURCE_SELECTION
|
||||
title = "Quellen auswählen"
|
||||
entries = FAConstants.SOURCE_NAMES
|
||||
entryValues = FAConstants.SOURCE_NAMES
|
||||
setDefaultValue(FAConstants.SOURCE_NAMES.toSet())
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(subPref)
|
||||
screen.addPreference(hosterPref)
|
||||
screen.addPreference(subSelection)
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.fireanime.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
abstract class AbsAnimeBaseDto {
|
||||
abstract val url: String
|
||||
abstract val title: String
|
||||
|
||||
abstract val season: Int?
|
||||
abstract val part: Int?
|
||||
|
||||
abstract val imgPoster: String
|
||||
abstract val imgWallpaper: String?
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class AnimeBaseDto(
|
||||
override val url: String,
|
||||
override val title: String,
|
||||
|
||||
override val season: Int?,
|
||||
override val part: Int?,
|
||||
|
||||
override val imgPoster: String,
|
||||
override val imgWallpaper: String?,
|
||||
) : AbsAnimeBaseDto()
|
||||
|
||||
@Serializable
|
||||
data class AnimeDto(
|
||||
val id: Int,
|
||||
override val url: String,
|
||||
override val title: String,
|
||||
|
||||
override val season: Int?,
|
||||
override val part: Int?,
|
||||
val episodes: Int,
|
||||
|
||||
override val imgPoster: String,
|
||||
override val imgWallpaper: String?,
|
||||
) : AbsAnimeBaseDto()
|
||||
|
||||
@Serializable
|
||||
data class AnimeDetailsWrapperDto(
|
||||
val response: AnimeDetailsDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AnimeDetailsDto(
|
||||
val title: String,
|
||||
val description: String,
|
||||
|
||||
val season: Int?,
|
||||
val part: Int?,
|
||||
|
||||
val imgPoster: String,
|
||||
val imgWallpaper: String?,
|
||||
|
||||
@SerialName("generes")
|
||||
val genres: List<GenreDto>,
|
||||
val tags: List<TagDto>,
|
||||
|
||||
val fsk: Int,
|
||||
@SerialName("voting")
|
||||
val votingStr: String?,
|
||||
val votingDouble: Double? = votingStr?.toDoubleOrNull(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AiringEpisodeDto(
|
||||
override val url: String,
|
||||
override val title: String,
|
||||
|
||||
val info: String,
|
||||
val status: String,
|
||||
|
||||
override val season: Int?,
|
||||
override val part: Int?,
|
||||
val episode: Int,
|
||||
|
||||
val langId: Int,
|
||||
val isSub: Int,
|
||||
|
||||
@SerialName("time")
|
||||
val timeSeconds: Long,
|
||||
val timeMillis: Long = timeSeconds * 1000,
|
||||
|
||||
override val imgPoster: String,
|
||||
override val imgWallpaper: String?,
|
||||
val imgThumb: String,
|
||||
) : AbsAnimeBaseDto()
|
||||
|
||||
@Serializable
|
||||
data class EpisodeListingWrapperDto(
|
||||
val response: List<EpisodeListingDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeListingDto(
|
||||
val title: String,
|
||||
val episode: Int,
|
||||
val img: String,
|
||||
val version: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeSourcesDto(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val episode: Int,
|
||||
val img: String,
|
||||
@SerialName("links")
|
||||
val hosters: List<HosterSourceDto>,
|
||||
val cdns: List<CdnSourceDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VideoLinkDto(
|
||||
@SerialName("response")
|
||||
val url: String,
|
||||
)
|
||||
|
||||
abstract class AbsSourceBaseDto {
|
||||
abstract val id: Int
|
||||
abstract val isSub: Int
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HosterSourceDto(
|
||||
override val id: Int,
|
||||
override val isSub: Int,
|
||||
val hoster: String,
|
||||
) : AbsSourceBaseDto()
|
||||
|
||||
@Serializable
|
||||
data class CdnSourceDto(
|
||||
override val id: Int,
|
||||
override val isSub: Int,
|
||||
) : AbsSourceBaseDto()
|
||||
|
||||
@Serializable
|
||||
data class GenreDto(
|
||||
val id: Int,
|
||||
@SerialName("genere")
|
||||
val genre: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TagDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val category: String,
|
||||
val isAdult: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FireCdnFileDto(
|
||||
val proxy: String,
|
||||
val file: String,
|
||||
)
|
@ -1,30 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.de.fireanime.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animeextension.de.fireanime.dto.FireCdnFileDto
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class FireCdnExtractor(private val client: OkHttpClient, private val json: Json) {
|
||||
fun videoFromUrl(url: String, quality: String): Video? {
|
||||
// Check if video is available. It is faster than waiting for the api response status
|
||||
if (client.newCall(GET(url)).execute().body.string().contains("still converting")) {
|
||||
return null
|
||||
}
|
||||
|
||||
val fileName = url.split("/").last()
|
||||
val fileRequest = POST(
|
||||
"https://firecdn.cc/api/stream/deploy",
|
||||
body = FormBody.Builder()
|
||||
.add("file", fileName)
|
||||
.build(),
|
||||
)
|
||||
|
||||
val file = json.decodeFromString(FireCdnFileDto.serializer(), client.newCall(fileRequest).execute().body.string())
|
||||
val videoUrl = "${file.proxy.replace("\\", "")}/share/${file.file}/apple.mp4"
|
||||
return Video(url, quality, videoUrl)
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,13 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'Index1763686211'
|
||||
pkgNameSuffix = 'en.index1763686211'
|
||||
extClass = '.Index1763686211'
|
||||
extVersionCode = 1
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 30 KiB |
@ -1,277 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.en.index1763686211
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Base64
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class Index1763686211 : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "Index - 176.36.86.211"
|
||||
|
||||
override val baseUrl = Base64.decode("aHR0cDovLzE3Ni4zNi44Ni4yMTEvdmlkZW8vYW5pbWU=", Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||
|
||||
private val videoFormats = arrayOf(".mkv", ".mp4", ".avi")
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val badNames = arrayOf("../", "gifs/")
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
|
||||
document.select(popularAnimeSelector()).forEach {
|
||||
val name = it.text()
|
||||
if (name in badNames) return@forEach
|
||||
|
||||
if (videoFormats.any { t -> name.endsWith(t) }) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = videoFormats.fold(name) { acc, suffix -> acc.removeSuffix(suffix) }
|
||||
anime.setUrlWithoutDomain(
|
||||
LinkData(
|
||||
"single",
|
||||
"$baseUrl/${it.attr("href")}",
|
||||
it.nextSibling()?.toString()?.substringAfterLast(" ")?.trim(),
|
||||
).toJsonString(),
|
||||
)
|
||||
animeList.add(anime)
|
||||
} else if (name.endsWith("/")) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = name.removeSuffix("/")
|
||||
anime.setUrlWithoutDomain(
|
||||
LinkData(
|
||||
"multi",
|
||||
"/${it.attr("href")}",
|
||||
).toJsonString(),
|
||||
)
|
||||
animeList.add(anime)
|
||||
} else { }
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, false)
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "pre > a"
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String? = null
|
||||
|
||||
override fun popularAnimeFromElement(element: Element): SAnime = throw Exception("Not used")
|
||||
|
||||
// =============================== 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 fetchSearchAnime(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeFilterList,
|
||||
): Observable<AnimesPage> {
|
||||
return Observable.defer {
|
||||
try {
|
||||
client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess()
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
// RxJava doesn't handle Errors, which tends to happen during global searches
|
||||
// if an old extension using non-existent classes is still around
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
.map { response ->
|
||||
searchAnimeParse(response, query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = popularAnimeRequest(page)
|
||||
|
||||
private fun searchAnimeParse(response: Response, query: String): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val badNames = arrayOf("../", "gifs/")
|
||||
val animeList = mutableListOf<SAnime>()
|
||||
|
||||
document.select(popularAnimeSelector()).forEach {
|
||||
val name = it.text()
|
||||
if (name in badNames || !name.contains(query, ignoreCase = true)) return@forEach
|
||||
|
||||
if (videoFormats.any { t -> name.endsWith(t) }) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = videoFormats.fold(name) { acc, suffix -> acc.removeSuffix(suffix) }
|
||||
anime.setUrlWithoutDomain(
|
||||
LinkData(
|
||||
"single",
|
||||
"$baseUrl/${it.attr("href")}",
|
||||
it.nextSibling()?.toString()?.substringAfterLast(" ")?.trim(),
|
||||
).toJsonString(),
|
||||
)
|
||||
animeList.add(anime)
|
||||
} else if (name.endsWith("/")) {
|
||||
val anime = SAnime.create()
|
||||
anime.title = name.removeSuffix("/")
|
||||
anime.setUrlWithoutDomain(
|
||||
LinkData(
|
||||
"multi",
|
||||
"/${it.attr("href")}",
|
||||
).toJsonString(),
|
||||
)
|
||||
animeList.add(anime)
|
||||
} else { }
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, false)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector(): String = throw Exception("Not used")
|
||||
|
||||
override fun searchAnimeNextPageSelector(): String = throw Exception("Not used")
|
||||
|
||||
override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("Not used")
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
|
||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
||||
return Observable.just(anime)
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime = throw Exception("Not used")
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
|
||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
val parsed = json.decodeFromString<LinkData>(anime.url)
|
||||
var counter = 1
|
||||
|
||||
if (parsed.type == "single") {
|
||||
val episode = SEpisode.create()
|
||||
val size = if (parsed.info == null) {
|
||||
""
|
||||
} else {
|
||||
" - ${parsed.info}"
|
||||
}
|
||||
episode.name = "${parsed.url.toHttpUrl().pathSegments.last()}$size"
|
||||
episode.url = parsed.url
|
||||
episode.episode_number = 1F
|
||||
episodeList.add(episode)
|
||||
} else if (parsed.type == "multi") {
|
||||
fun traverseDirectory(url: String) {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
|
||||
doc.select("a").forEach { link ->
|
||||
val href = link.attr("href")
|
||||
val text = link.text()
|
||||
if (href.isNotBlank() && text != "../") {
|
||||
val fullUrl = url + href
|
||||
if (fullUrl.endsWith("/")) {
|
||||
traverseDirectory(fullUrl)
|
||||
}
|
||||
if (videoFormats.any { t -> fullUrl.endsWith(t) }) {
|
||||
val episode = SEpisode.create()
|
||||
val paths = fullUrl.toHttpUrl().pathSegments
|
||||
val season = if (paths.size == 4) {
|
||||
""
|
||||
} else {
|
||||
"[${paths[3]}] "
|
||||
}
|
||||
|
||||
val extraInfo = if (paths.size > 5) {
|
||||
paths.subList(4, paths.size - 1).joinToString("/")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val size = link.nextSibling()?.toString()?.substringAfterLast(" ")?.trim()
|
||||
|
||||
episode.name = "${season}Episode ${videoFormats.fold(paths.last()) { acc, suffix -> acc.removeSuffix(suffix) }}${if (size == null) "" else " - $size"}"
|
||||
episode.url = fullUrl
|
||||
episode.scanlator = extraInfo
|
||||
episode.episode_number = counter.toFloat()
|
||||
counter++
|
||||
|
||||
episodeList.add(episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverseDirectory(baseUrl + parsed.url)
|
||||
}
|
||||
|
||||
return Observable.just(episodeList.reversed())
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = throw Exception("Not used")
|
||||
|
||||
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>> {
|
||||
return Observable.just(listOf(Video(episode.url, "Video", episode.url)))
|
||||
}
|
||||
|
||||
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 ==============================
|
||||
|
||||
@Serializable
|
||||
data class LinkData(
|
||||
val type: String,
|
||||
val url: String,
|
||||
val info: String? = null,
|
||||
)
|
||||
|
||||
private fun LinkData.toJsonString(): String {
|
||||
return json.encodeToString(this)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) { }
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
@ -1,14 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'AnimeVibe'
|
||||
pkgNameSuffix = 'pt.animevibe'
|
||||
extClass = '.AnimeVibe'
|
||||
extVersionCode = 1
|
||||
libVersion = '13'
|
||||
containsNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 72 KiB |
@ -1,240 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animevibe
|
||||
|
||||
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.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AnimeVibe : AnimeHttpSource() {
|
||||
override val name = "AnimeVibe"
|
||||
override val baseUrl = "https://animevibe.cc"
|
||||
override val lang = "pt-BR"
|
||||
override val supportsLatest = false
|
||||
|
||||
private val format = Json { ignoreUnknownKeys = true }
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
.add("Accept", "application/json")
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(5, 1, TimeUnit.SECONDS)
|
||||
.addInterceptor(::requestIntercept)
|
||||
.build()
|
||||
|
||||
private val requestCache: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
private fun requestIntercept(chain: Interceptor.Chain): Response {
|
||||
val url = chain.request().url.toString()
|
||||
if (!url.contains("data?page=medias")) return chain.proceed(chain.request())
|
||||
|
||||
if (requestCache.containsKey(url)) {
|
||||
val body = requestCache[url]!!.toResponseBody("application/json; charset=UTF-8".toMediaTypeOrNull())
|
||||
return Response.Builder()
|
||||
.code(200)
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.request(chain.request())
|
||||
.message("OK")
|
||||
.body(body)
|
||||
.build()
|
||||
}
|
||||
|
||||
val response = chain.proceed(chain.request())
|
||||
val contentType = response.body.contentType()
|
||||
val body = response.body.string()
|
||||
requestCache[url] = body
|
||||
return response.newBuilder()
|
||||
.body(body.toResponseBody(contentType))
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String): Long {
|
||||
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
private fun getAnimeTitle(anime: AnimeVibeAnimeDto): String = (anime.title["romaji"] ?: anime.title["english"] ?: anime.title["native"]!!) + (if (anime.audio == "Dublado") " (DUBLADO)" else "")
|
||||
|
||||
private fun getAnimeFromObject(anime: AnimeVibeAnimeDto): SAnime = SAnime.create().apply {
|
||||
initialized = true
|
||||
thumbnail_url = "$CDN_URL/img/animes/${anime.slug}-large.webp"
|
||||
url = "$baseUrl/anime/${anime.id}"
|
||||
title = getAnimeTitle(anime)
|
||||
description = anime.description
|
||||
genre = anime.genres?.joinToString(", ")
|
||||
status = when (anime.status) {
|
||||
"Completo" -> SAnime.COMPLETED
|
||||
"Em lançamento" -> SAnime.ONGOING
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
val headers = headersBuilder().build()
|
||||
return GET("$baseUrl/$API_PATH/data?page=medias", headers)
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val animes = format.decodeFromString<AnimeVibePopularDto>(response.body.string())
|
||||
|
||||
if (animes.data.isNullOrEmpty()) {
|
||||
return AnimesPage(emptyList(), hasNextPage = false)
|
||||
}
|
||||
|
||||
val animeList = animes.data
|
||||
.sortedByDescending { it.views }
|
||||
.take(50)
|
||||
.map(::getAnimeFromObject)
|
||||
return AnimesPage(animeList, hasNextPage = false)
|
||||
}
|
||||
|
||||
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||
val id = anime.url.substringAfter("/anime/")
|
||||
.substringBefore("/")
|
||||
val headers = headersBuilder()
|
||||
.add("X-id", id)
|
||||
.build()
|
||||
return GET("$baseUrl/$API_PATH/data?page=medias", headers)
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val animes = format.decodeFromString<AnimeVibePopularDto>(response.body.string())
|
||||
if (animes.data.isNullOrEmpty()) throw Exception(COULD_NOT_PARSE_ANIME)
|
||||
|
||||
val id = response.request.header("X-id")!!.toInt()
|
||||
val anime = animes.data.find { it.id == id } ?: throw Exception(COULD_NOT_PARSE_ANIME)
|
||||
|
||||
return getAnimeFromObject(anime)
|
||||
}
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request {
|
||||
val id = anime.url.substringAfter("/anime/")
|
||||
.substringBefore("/")
|
||||
return GET("$baseUrl/$API_PATH/data?page=episode&mediaID=$id")
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val episodes = format.decodeFromString<AnimeVibeEpisodeListDto>(response.body.string())
|
||||
if (episodes.data.isNullOrEmpty()) return emptyList()
|
||||
|
||||
return episodes.data
|
||||
.map {
|
||||
SEpisode.create().apply {
|
||||
url = "$baseUrl/anime/${it.mediaID}?episode=${it.number}"
|
||||
name = "Episódio ${it.number.toInt()}"
|
||||
date_upload = parseDate(it.datePublished)
|
||||
episode_number = it.number
|
||||
}
|
||||
}
|
||||
.reversed()
|
||||
}
|
||||
|
||||
override fun videoListRequest(episode: SEpisode): Request {
|
||||
val id = episode.url.substringAfter("/anime/")
|
||||
.substringBefore("?")
|
||||
val headers = headersBuilder()
|
||||
.add("X-mediaid", id)
|
||||
.add("X-number", episode.episode_number.toString())
|
||||
.add("X-url", episode.url)
|
||||
.build()
|
||||
return GET("$baseUrl/$API_PATH/data?page=episode&mediaID=$id", headers)
|
||||
}
|
||||
|
||||
private fun getVideoFromSource(source: String): List<Video> {
|
||||
return if (source.startsWith('/')) {
|
||||
val url = "$VIDEO_URL$source"
|
||||
listOf(Video(url, source.substringBeforeLast('/').substringAfterLast('/').uppercase(), url))
|
||||
} else if (source.startsWith("https://www.blogger.com/")) {
|
||||
val headers = headersBuilder()
|
||||
.add("User-Agent", USER_AGENT)
|
||||
.build()
|
||||
val response = client.newCall(GET(source, headers)).execute()
|
||||
val streams = response.body.string().substringAfter("\"streams\":[").substringBefore("]")
|
||||
return streams.split("},")
|
||||
.map {
|
||||
val url = it.substringAfter("{\"play_url\":\"").substringBefore('"')
|
||||
val quality = when (it.substringAfter("\"format_id\":").substringBefore("}")) {
|
||||
"18" -> "360p"
|
||||
"22" -> "720p"
|
||||
else -> "Unknown Resolution"
|
||||
}
|
||||
Video(url, quality, url, null, headers)
|
||||
}
|
||||
} else {
|
||||
throw Exception("UNKOWN VIDEO SOURCE")
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val number = response.request.header("X-number")!!.toFloat()
|
||||
val episodes = format.decodeFromString<AnimeVibeEpisodeListDto>(response.body.string())
|
||||
if (episodes.data.isNullOrEmpty()) throw Exception("NO DATA ${response.request.header("X-mediaid")} ${response.request.header("X-url")}")
|
||||
|
||||
val episode = episodes.data.find { it.number == number } ?: throw Exception("NO EPISODE $number")
|
||||
return episode.videoSource
|
||||
.flatMap(::getVideoFromSource)
|
||||
.reversed()
|
||||
}
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val headers = headersBuilder()
|
||||
.add("X-page", page.toString())
|
||||
.add("X-query", query)
|
||||
.build()
|
||||
return GET("$baseUrl/$API_PATH/data?page=medias", headers)
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val query = response.request.header("X-query")!!
|
||||
val animes = format.decodeFromString<AnimeVibePopularDto>(response.body.string())
|
||||
if (animes.data.isNullOrEmpty()) {
|
||||
return AnimesPage(emptyList(), hasNextPage = false)
|
||||
}
|
||||
|
||||
val animeList = animes.data
|
||||
.filter { getAnimeTitle(it).contains(query, ignoreCase = true) }
|
||||
.sortedByDescending { it.views }
|
||||
.map(::getAnimeFromObject)
|
||||
return AnimesPage(animeList, hasNextPage = false)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val API_PATH = "animevibe/api/v1"
|
||||
private const val CDN_URL = "https://animefire.net"
|
||||
private const val VIDEO_URL = "https://akumaharu.org"
|
||||
|
||||
// blogger.com videos needs an user agent to work
|
||||
private const val USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
|
||||
|
||||
private const val COULD_NOT_PARSE_ANIME = "Ocorreu um erro ao obter as informações do anime."
|
||||
|
||||
private val DATE_FORMATTER by lazy {
|
||||
SimpleDateFormat("yyyy-MM-ddTHH:mm:ss", Locale.ENGLISH)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animevibe
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias AnimeVibePopularDto = AnimeVibeResultDto<List<AnimeVibeAnimeDto>>
|
||||
typealias AnimeVibeEpisodeListDto = AnimeVibeResultDto<List<AnimeVibeEpisodeDto>>
|
||||
|
||||
@Serializable
|
||||
data class AnimeVibeResultDto<T>(
|
||||
val data: T? = null,
|
||||
)
|
||||
|
||||
/*
|
||||
id: 26673,
|
||||
slug: 'kyou-no-asuka-show',
|
||||
type: 'Anime',
|
||||
audio: 'Legendado',
|
||||
score: 6.3,
|
||||
title: { native: '今日のあすかショー', romaji: 'Kyou no Asuka Show', english: null },
|
||||
views: 0,
|
||||
format: 'Ona',
|
||||
genres: [ 'Comédia', 'Ecchi', 'Seinen' ],
|
||||
season: 'Verão',
|
||||
source: 'Manga',
|
||||
status: 'Completo',
|
||||
studios: [ 'Lantis', 'Half H.P Studio' ],
|
||||
duration: '3 min',
|
||||
episodes: 20,
|
||||
synonyms: [ "Today's Asuka show" ],
|
||||
producers: [ 'SILVER LINK.' ],
|
||||
startDate: { day: 3, year: 2012, month: 8 },
|
||||
description: `Asuka é uma garota bonita e sem noção do ensino médio que costuma fazer coisas, com toda inocência, que parecem sexualmente sugestivas para os homens ao seu redor. Cada capítulo de "Today's Asuka Show" apresenta uma situação diferente e embaraçosa. \n` +
|
||||
'\n',
|
||||
countryOfOrigin: 'JP'
|
||||
*/
|
||||
|
||||
@Serializable
|
||||
data class AnimeVibeAnimeDto(
|
||||
val id: Int = -1,
|
||||
val slug: String = "",
|
||||
val audio: String = "",
|
||||
val title: Map<String, String?> = emptyMap(),
|
||||
val views: Int = -1,
|
||||
val genres: List<String>? = emptyList(),
|
||||
val status: String? = "",
|
||||
val description: String? = "",
|
||||
)
|
||||
|
||||
/*
|
||||
{
|
||||
audio: 'Legendado',
|
||||
title: 'Koharu Biyori',
|
||||
number: 3,
|
||||
mediaID: 27476,
|
||||
videoSource: [
|
||||
'https://www.blogger.com/video.g?token=AD6v5dyxFaNmvMsasiRlDr8PiwoMlF7j4ykPewpKr_uRyAPZifCqlt0nj5D1oOgI9bjhbvKBO0CRwDUTXg-U81pRt6FBuj_GN6ynwPY5bwtYyAeURxT1HrB4MWxwr70mIP1m-8NHGrNs'
|
||||
],
|
||||
datePublished: '2022-01-28T21:41:18.366Z'
|
||||
}
|
||||
*/
|
||||
|
||||
@Serializable
|
||||
data class AnimeVibeEpisodeDto(
|
||||
val title: String = "",
|
||||
val number: Float = -1f,
|
||||
val mediaID: Int = -1,
|
||||
val videoSource: List<String> = emptyList(),
|
||||
val datePublished: String = "",
|
||||
)
|