mirror of
https://github.com/yuzono/anime-extensions.git
synced 2026-06-13 13:39:44 +00:00
refactor: using helper methods, improvement & fix bugs
This commit is contained in:
@@ -351,8 +351,7 @@ abstract class AnimeStream(
|
||||
// Taken from LuciferDonghua
|
||||
protected open suspend fun getHosterUrl(encodedData: String): String {
|
||||
val doc = if (encodedData.toHttpUrlOrNull() == null) {
|
||||
Base64.decode(encodedData, Base64.DEFAULT)
|
||||
.let(::String) // bytearray -> string
|
||||
String(Base64.decode(encodedData, Base64.DEFAULT))
|
||||
.let(Jsoup::parse) // string -> document
|
||||
} else {
|
||||
client.newCall(GET(encodedData, headers)).awaitSuccess().useAsJsoup()
|
||||
|
||||
@@ -187,7 +187,7 @@ abstract class DopeFlix(
|
||||
/* Both latest Movies & TV Shows are on same home page */
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home/", docHeaders, cacheControl)
|
||||
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = throw UnsupportedOperationException()
|
||||
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
|
||||
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
|
||||
|
||||
|
||||
@@ -3,19 +3,18 @@ package aniyomi.lib.bloggerextractor
|
||||
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 eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class BloggerExtractor(private val client: OkHttpClient) {
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
fun videosFromUrl(url: String, headers: Headers, suffix: String = ""): List<Video> {
|
||||
val body = client.newCall(GET(url, headers)).execute()
|
||||
.body.string()
|
||||
suspend fun videosFromUrl(url: String, headers: Headers, suffix: String = ""): List<Video> {
|
||||
val body = client.newCall(GET(url, headers))
|
||||
.awaitSuccess().bodyString()
|
||||
|
||||
var videos = getStreamVideos(body, headers, suffix)
|
||||
|
||||
@@ -53,13 +52,13 @@ class BloggerExtractor(private val client: OkHttpClient) {
|
||||
* Extract videos from the RPC URL
|
||||
* Based on https://github.com/FightFarewellFearless/AniFlix/blob/4b07254fc0051664691fd2f3c001dbd6b43e18ad/src/utils/scrapers/animeSeries.ts#L445
|
||||
*/
|
||||
private fun getRpcVideos(
|
||||
private suspend fun getRpcVideos(
|
||||
url: String,
|
||||
body: String,
|
||||
headers: Headers,
|
||||
suffix: String = "",
|
||||
): List<Video> {
|
||||
val token = url.toHttpUrl().queryParameter("token")
|
||||
val token = url.toHttpUrl().queryParameter("token") ?: return emptyList()
|
||||
|
||||
val fSid = body.substringAfter("FdrFJe\":\"").substringBefore("\"")
|
||||
val bl = body.substringAfter("cfb2h\":\"").substringBefore("\"")
|
||||
@@ -92,11 +91,10 @@ class BloggerExtractor(private val client: OkHttpClient) {
|
||||
"https://www.blogger.com/",
|
||||
)
|
||||
|
||||
val rpcString = client.newCall(POST(rpcUrl, body = rpcBody, headers = rpcHeaders)).execute()
|
||||
.body.string()
|
||||
val rpcString = client.newCall(POST(rpcUrl, body = rpcBody, headers = rpcHeaders))
|
||||
.awaitSuccess().bodyString()
|
||||
|
||||
return rpcString
|
||||
.let { it ?: return emptyList() }
|
||||
.substringAfter("[[\\\"", "")
|
||||
.substringBefore("]]]")
|
||||
.takeIf { it.contains("https://") }
|
||||
@@ -107,8 +105,8 @@ class BloggerExtractor(private val client: OkHttpClient) {
|
||||
val videoUrl = it.substringAfter("\\\"", "")
|
||||
.substringBefore("\\\"")
|
||||
.takeIf(String::isNotBlank)
|
||||
.let { json.decodeFromString<String>("\"$it\"") }
|
||||
.let { json.decodeFromString<String>("\"$it\"") } // Yes, need decode twice
|
||||
?.parseAs<String> { "\"$it\"" }
|
||||
?.parseAs<String> { "\"$it\"" } // Yes, need decode twice
|
||||
?: return@mapNotNull null
|
||||
|
||||
val format = it.substringAfter("[").substringBefore("]")
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package aniyomi.lib.filemoonextractor
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URI
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -21,11 +18,10 @@ import javax.crypto.Cipher
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class FilemoonExtractor(private val client: OkHttpClient, private val preferences: SharedPreferences? = null) {
|
||||
class FilemoonExtractor(private val client: OkHttpClient) {
|
||||
private val playlistUtils by lazy { PlaylistUtils(client) }
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
//Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/filemoon.py
|
||||
// Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/filemoon.py
|
||||
fun videosFromUrl(
|
||||
url: String,
|
||||
prefix: String = "Filemoon - ",
|
||||
@@ -34,7 +30,7 @@ class FilemoonExtractor(private val client: OkHttpClient, private val preference
|
||||
return try {
|
||||
val httpUrl = url.toHttpUrl()
|
||||
val host = httpUrl.host
|
||||
val mediaId = if (httpUrl.pathSegments[0] == "e") {
|
||||
val mediaId = if (httpUrl.pathSegments.size > 1 && httpUrl.pathSegments[0] == "e") {
|
||||
httpUrl.pathSegments[1]
|
||||
} else {
|
||||
httpUrl.pathSegments.lastOrNull { it.isNotEmpty() } ?: return emptyList()
|
||||
@@ -44,7 +40,7 @@ class FilemoonExtractor(private val client: OkHttpClient, private val preference
|
||||
|
||||
val embedUrl =
|
||||
client.newCall(GET("https://$host/api/videos/$mediaId/embed/details"))
|
||||
.execute().body.string()
|
||||
.execute().bodyString()
|
||||
.substringAfter("embed_frame_url", "")
|
||||
.substringAfter(":")
|
||||
.substringAfter('"')
|
||||
@@ -74,9 +70,8 @@ class FilemoonExtractor(private val client: OkHttpClient, private val preference
|
||||
}.build()
|
||||
|
||||
val apiUrl = "https://$embedHost/api/videos/$mediaId/embed/playback"
|
||||
val response = client.newCall(GET(apiUrl, playbackHeaders)).execute()
|
||||
val responseData = response.body.string()
|
||||
val playbackJson = json.decodeFromString<PlaybackResponse>(responseData)
|
||||
val playbackJson = client.newCall(GET(apiUrl, playbackHeaders)).execute()
|
||||
.parseAs<PlaybackResponse>()
|
||||
|
||||
var finalSources: List<VideoSource>? = null
|
||||
|
||||
@@ -85,7 +80,7 @@ class FilemoonExtractor(private val client: OkHttpClient, private val preference
|
||||
} else if (playbackJson.playback != null) {
|
||||
val pb = playbackJson.playback
|
||||
val decryptedData = decrypt(pb)
|
||||
val decryptedJson = json.decodeFromString<PlaybackResponse>(decryptedData)
|
||||
val decryptedJson = decryptedData.parseAs<PlaybackResponse>()
|
||||
finalSources = decryptedJson.sources
|
||||
}
|
||||
|
||||
@@ -164,20 +159,6 @@ class FilemoonExtractor(private val client: OkHttpClient, private val preference
|
||||
val url: String? = null,
|
||||
val label: String? = "Default",
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun addSubtitlePref(screen: PreferenceScreen) {
|
||||
EditTextPreference(screen.context).apply {
|
||||
key = PREF_SUBTITLE_KEY
|
||||
title = "Filemoon subtitle preference"
|
||||
summary = "Leave blank to use all subs"
|
||||
setDefaultValue(PREF_SUBTITLE_DEFAULT)
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
private const val PREF_SUBTITLE_KEY = "pref_filemoon_sub_lang_key"
|
||||
private const val PREF_SUBTITLE_DEFAULT = "eng"
|
||||
}
|
||||
}
|
||||
|
||||
fun String.encodeUrlPath(): String {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package aniyomi.lib.googledriveplayerextractor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -13,12 +12,13 @@ import android.webkit.WebViewClient
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.applicationContext
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -37,9 +37,7 @@ import java.util.concurrent.TimeUnit
|
||||
*/
|
||||
class GoogleDrivePlayerExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
/**
|
||||
* Extracts video URLs from a Google Drive URL.
|
||||
@@ -51,6 +49,7 @@ class GoogleDrivePlayerExtractor(private val client: OkHttpClient, private val h
|
||||
* @return A list of [Video] objects with streaming URLs and quality information
|
||||
*/
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val latch = CountDownLatch(1)
|
||||
@@ -58,7 +57,7 @@ class GoogleDrivePlayerExtractor(private val client: OkHttpClient, private val h
|
||||
var playbackUrl: String? = null
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
@@ -121,11 +120,11 @@ class GoogleDrivePlayerExtractor(private val client: OkHttpClient, private val h
|
||||
}.build()
|
||||
|
||||
return try {
|
||||
val response = client.newCall(GET(playbackUrlFinal, requestHeaders)).execute()
|
||||
val responseBody = response.body.string()
|
||||
val responseBody = client.newCall(GET(playbackUrlFinal, requestHeaders)).execute()
|
||||
.bodyString()
|
||||
Log.d(tag, "Response body: ${responseBody.take(200)}...")
|
||||
|
||||
val streamingData = json.decodeFromString<GoogleDriveStreamingResponse>(responseBody)
|
||||
val streamingData = responseBody.parseAs<GoogleDriveStreamingResponse>()
|
||||
val videos = mutableListOf<Video>()
|
||||
|
||||
// Process progressive transcodes
|
||||
|
||||
@@ -4,15 +4,17 @@ import android.util.Base64
|
||||
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 eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.json.JSONObject
|
||||
|
||||
class LycorisExtractor(private val client: OkHttpClient) {
|
||||
@@ -26,12 +28,12 @@ class LycorisExtractor(private val client: OkHttpClient) {
|
||||
}
|
||||
|
||||
// Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/lycoris.py
|
||||
fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> {
|
||||
suspend fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> {
|
||||
val videos = mutableListOf<Video>()
|
||||
|
||||
val document = client.newCall(
|
||||
GET(url, headers = headers),
|
||||
).execute().asJsoup()
|
||||
).awaitSuccess().useAsJsoup()
|
||||
|
||||
val script =
|
||||
document.selectFirst("script[type='application/json']")?.data() ?: return emptyList()
|
||||
@@ -54,7 +56,7 @@ class LycorisExtractor(private val client: OkHttpClient) {
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun fetchAndDecodeVideo(client: OkHttpClient, headers: Headers, episodeId: String): VideoLinksApi {
|
||||
private suspend fun fetchAndDecodeVideo(client: OkHttpClient, headers: Headers, episodeId: String): VideoLinksApi {
|
||||
val decryptHeaders = headers.newBuilder()
|
||||
.add("x-api-key", DECRYPT_API_KEY)
|
||||
.add("Content-Type", "application/json")
|
||||
@@ -65,25 +67,25 @@ class LycorisExtractor(private val client: OkHttpClient) {
|
||||
.build()
|
||||
|
||||
val encryptedText = client.newCall(GET(url))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
|
||||
val textByte = encryptedText.toByteArray(Charsets.ISO_8859_1)
|
||||
|
||||
val base64Data = String(Base64.encode(textByte, Base64.DEFAULT), Charsets.UTF_8)
|
||||
val base64Data = Base64.encodeToString(textByte, Base64.DEFAULT)
|
||||
|
||||
val jsonObject = JSONObject()
|
||||
jsonObject.put("encoded", base64Data)
|
||||
|
||||
client.newCall(POST(DECRYPTURL, headers = decryptHeaders, body = jsonObject.toString().toRequestBody("application/json".toMediaType()))).execute().use { response ->
|
||||
return response.body.string().parseAs<VideoLinksApi>()
|
||||
}
|
||||
return client.newCall(POST(DECRYPTURL, headers = decryptHeaders, body = jsonObject.toJsonRequestBody()))
|
||||
.awaitSuccess()
|
||||
.parseAs<VideoLinksApi>()
|
||||
}
|
||||
|
||||
private fun checkLinks(client: OkHttpClient, link: String): Boolean {
|
||||
private suspend fun checkLinks(client: OkHttpClient, link: String): Boolean {
|
||||
if (!link.contains("https://")) return false
|
||||
|
||||
client.newCall(GET(link)).execute().use { response ->
|
||||
return response.code.toString() == "200"
|
||||
client.newCall(GET(link)).await().use { response ->
|
||||
return response.code == 200
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package aniyomi.lib.streamupextractor
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.decodeHex
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.Headers
|
||||
@@ -13,11 +16,11 @@ import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class StreamupExtractor(private val client: OkHttpClient) {
|
||||
// Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/streamup.py
|
||||
fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> {
|
||||
suspend fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> {
|
||||
val videos = mutableListOf<Video>()
|
||||
|
||||
val response = client.newCall(GET(url, headers = headers)).execute()
|
||||
val body = response.body.string()
|
||||
val response = client.newCall(GET(url, headers = headers)).awaitSuccess()
|
||||
val body = response.bodyString()
|
||||
|
||||
val requestUrl = response.request.url
|
||||
val baseUrl = "${requestUrl.scheme}://${requestUrl.host}"
|
||||
@@ -28,11 +31,13 @@ class StreamupExtractor(private val client: OkHttpClient) {
|
||||
val encodedMatch = Regex("""decodePrintable95\("([a-f0-9]+)"""").find(body)
|
||||
val shiftMatch = Regex("""__enc_shift\s*=\s*(\d+)""").find(body)
|
||||
|
||||
if (encodedMatch != null && shiftMatch != null) {
|
||||
streamUrl = decodePrintable95(
|
||||
encodedMatch.groupValues[1],
|
||||
shiftMatch.groupValues[1].toInt(),
|
||||
)
|
||||
runCatching {
|
||||
if (encodedMatch != null && shiftMatch != null) {
|
||||
streamUrl = decodePrintable95(
|
||||
encodedMatch.groupValues[1],
|
||||
shiftMatch.groupValues[1].toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,15 +56,15 @@ class StreamupExtractor(private val client: OkHttpClient) {
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
val keyResponse = client.newCall(GET(keyUrl, keyHeaders)).execute()
|
||||
val keyB64 = keyResponse.body.string().trim()
|
||||
val keyResponse = client.newCall(GET(keyUrl, keyHeaders)).awaitSuccess()
|
||||
val keyB64 = keyResponse.bodyString().trim()
|
||||
|
||||
streamUrl = decryptAES(encryptedDataB64, keyB64)
|
||||
} else {
|
||||
val sUrl = "$baseUrl/ajax/stream?filecode=$mediaId"
|
||||
val sResponse = client.newCall(GET(sUrl, Headers.Builder().add("Referer", url).build())).execute()
|
||||
val sResponse = client.newCall(GET(sUrl, Headers.Builder().add("Referer", url).build())).awaitSuccess()
|
||||
|
||||
streamUrl = sResponse.body.string().parseAs<StreamupResponse>().streaming_url
|
||||
streamUrl = sResponse.parseAs<StreamupResponse>().streaming_url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +80,8 @@ class StreamupExtractor(private val client: OkHttpClient) {
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun decodePrintable95(encoded: String, shift: Int): String = try {
|
||||
val bytes = encoded.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
private fun decodePrintable95(encoded: String, shift: Int): String {
|
||||
val bytes = encoded.decodeHex()
|
||||
val intermediate = String(bytes, Charsets.ISO_8859_1)
|
||||
val decoded = StringBuilder()
|
||||
|
||||
@@ -86,9 +91,7 @@ class StreamupExtractor(private val client: OkHttpClient) {
|
||||
if (i < 0) i += 95
|
||||
decoded.append((i + 32).toChar())
|
||||
}
|
||||
decoded.toString()
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
return decoded.toString()
|
||||
}
|
||||
|
||||
private fun decryptAES(encryptedDataB64: String, keyB64: String): String? {
|
||||
|
||||
@@ -3,12 +3,16 @@ package aniyomi.lib.vkextractor
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.toHex
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.MessageDigest
|
||||
|
||||
class VkExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
class VkExtractor(private val client: OkHttpClient, headers: Headers) {
|
||||
// Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/vk.py
|
||||
private val documentHeaders = headers.newBuilder()
|
||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
|
||||
@@ -21,7 +25,7 @@ class VkExtractor(private val client: OkHttpClient, private val headers: Headers
|
||||
.add("Referer", "$VK_URL/")
|
||||
.build()
|
||||
|
||||
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
|
||||
suspend fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
|
||||
val videoId = extractVideoId(url) ?: return emptyList()
|
||||
|
||||
val apiVideos = getVideosViaApi(videoId)
|
||||
@@ -38,31 +42,38 @@ class VkExtractor(private val client: OkHttpClient, private val headers: Headers
|
||||
if (parts.size == 2) "$VK_URL/video_ext.php?oid=${parts[0]}&id=${parts[1]}&autoplay=0" else url
|
||||
}
|
||||
|
||||
val htmlContent = handleWafChallenge(embedUrl) ?: return emptyList()
|
||||
val htmlContent = handleWafChallenge(embedUrl)
|
||||
return extractVideosFromHtml(htmlContent, prefix)
|
||||
}
|
||||
|
||||
private fun handleWafChallenge(url: String): String? {
|
||||
val response = client.newCall(GET(url, documentHeaders)).execute()
|
||||
private suspend fun handleWafChallenge(url: String): String {
|
||||
val response = client.newCall(GET(url, documentHeaders)).await()
|
||||
val responseUrl = response.request.url.toString()
|
||||
|
||||
if (responseUrl.contains("429.html") || response.code == 429) {
|
||||
response.close()
|
||||
val cookies = client.cookieJar.loadForRequest(response.request.url)
|
||||
val hash429Cookie = cookies.find { it.name == "hash429" }?.value
|
||||
val hash429Cookie = cookies.firstOrNull { it.name == "hash429" }?.value
|
||||
|
||||
if (hash429Cookie != null) {
|
||||
val hash429 = md5(hash429Cookie)
|
||||
val challengeUrl = "$responseUrl&key=$hash429"
|
||||
val challengeUrl = response.request.url.newBuilder()
|
||||
.addQueryParameter("key", hash429)
|
||||
.build()
|
||||
.toString()
|
||||
|
||||
client.newCall(GET(challengeUrl, documentHeaders)).execute()
|
||||
client.newCall(GET(challengeUrl, documentHeaders)).awaitSuccess().close()
|
||||
|
||||
return client.newCall(GET(url, documentHeaders)).execute().body.string()
|
||||
return client.newCall(GET(url, documentHeaders)).awaitSuccess().bodyString()
|
||||
} else {
|
||||
throw Exception("hash429 cookie not found, cannot bypass WAF")
|
||||
}
|
||||
} else {
|
||||
return response.bodyString()
|
||||
}
|
||||
return response.body.string()
|
||||
}
|
||||
|
||||
private fun getVideosViaApi(videoId: String): List<RawVideo> {
|
||||
private suspend fun getVideosViaApi(videoId: String): List<RawVideo> {
|
||||
val body = FormBody.Builder()
|
||||
.add("act", "show")
|
||||
.add("al", "1")
|
||||
@@ -75,11 +86,12 @@ class VkExtractor(private val client: OkHttpClient, private val headers: Headers
|
||||
.build()
|
||||
|
||||
return try {
|
||||
val response = client.newCall(POST(VK_API_URL, apiHeaders, body)).execute().body.string()
|
||||
val cleanJson = response.substringAfter("<!--")
|
||||
val cleanJson = client.newCall(POST(VK_API_URL, apiHeaders, body)).awaitSuccess()
|
||||
.bodyString()
|
||||
.substringAfter("<!--")
|
||||
|
||||
parseVideoUrls(cleanJson)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -111,7 +123,9 @@ class VkExtractor(private val client: OkHttpClient, private val headers: Headers
|
||||
// videos.add(RawVideo(url, "HLS"))
|
||||
// }
|
||||
|
||||
return videos.distinctBy { it.quality }.sortedByDescending { it.quality }
|
||||
return videos
|
||||
.distinctBy { it.quality }
|
||||
.sortedByDescending { it.quality.removeSuffix("p").toIntOrNull() ?: 0 }
|
||||
}
|
||||
|
||||
private fun extractVideoId(url: String): String? {
|
||||
@@ -134,7 +148,7 @@ class VkExtractor(private val client: OkHttpClient, private val headers: Headers
|
||||
|
||||
private fun md5(input: String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return md.digest(input.toByteArray()).joinToString("") { "%02x".format(it) }
|
||||
return md.digest(input.toByteArray()).toHex()
|
||||
}
|
||||
|
||||
data class RawVideo(val url: String, val quality: String)
|
||||
|
||||
@@ -1,51 +1,44 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.animeonsen
|
||||
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
|
||||
class AOAPIInterceptor(client: OkHttpClient, apiUrl: String) : Interceptor {
|
||||
|
||||
private val token: String
|
||||
private val host: String
|
||||
|
||||
init {
|
||||
token = try {
|
||||
private val token: String by lazy {
|
||||
runCatching {
|
||||
val body = """
|
||||
{
|
||||
"client_id": "f296be26-28b5-4358-b5a1-6259575e23b7",
|
||||
"client_secret": "349038c4157d0480784753841217270c3c5b35f4281eaee029de21cb04084235",
|
||||
"grant_type": "client_credentials"
|
||||
}
|
||||
{
|
||||
"client_id": "f296be26-28b5-4358-b5a1-6259575e23b7",
|
||||
"client_secret": "349038c4157d0480784753841217270c3c5b35f4281eaee029de21cb04084235",
|
||||
"grant_type": "client_credentials"
|
||||
}
|
||||
""".trimIndent().toJsonRequestBody()
|
||||
|
||||
val headers = Headers.headersOf("user-agent", AO_USER_AGENT)
|
||||
|
||||
val tokenResponse = client.newCall(
|
||||
val tokenObject = client.newCall(
|
||||
POST(
|
||||
"https://auth.animeonsen.xyz/oauth/token",
|
||||
headers,
|
||||
body,
|
||||
),
|
||||
).execute().body.string()
|
||||
|
||||
val tokenObject = Json.decodeFromString<JsonObject>(tokenResponse)
|
||||
).execute().parseAs<JsonObject>()
|
||||
|
||||
tokenObject["access_token"]!!.jsonPrimitive.content
|
||||
} catch (_: Throwable) {
|
||||
""
|
||||
}
|
||||
host = apiUrl.toHttpUrl().host
|
||||
}.getOrElse { "" }
|
||||
}
|
||||
|
||||
private val host: String = apiUrl.toHttpUrlOrNull()?.host ?: apiUrl
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
|
||||
@@ -20,18 +20,14 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.Headers
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class AnimeOnsen :
|
||||
AnimeHttpSource(),
|
||||
@@ -58,8 +54,6 @@ class AnimeOnsen :
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override fun headersBuilder() = Headers.Builder().add("user-agent", AO_USER_AGENT)
|
||||
|
||||
// ============================== Popular ===============================
|
||||
@@ -81,11 +75,9 @@ class AnimeOnsen :
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||
val postBody = json.encodeToString(
|
||||
buildJsonObject {
|
||||
put("q", query)
|
||||
},
|
||||
).toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||
val postBody = buildJsonObject {
|
||||
put("q", query)
|
||||
}.toJsonRequestBody()
|
||||
|
||||
return POST("$searchUrl/indexes/content/search", body = postBody)
|
||||
}
|
||||
@@ -157,13 +149,6 @@ class AnimeOnsen :
|
||||
entryValues = PREF_SUB_VALUES
|
||||
setDefaultValue(PREF_SUB_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.animeonsen
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
|
||||
class SearchInterceptor(client: OkHttpClient, baseUrl: String, searchUrl: String) : Interceptor {
|
||||
|
||||
private val token: String
|
||||
private val host: String
|
||||
|
||||
init {
|
||||
token = try {
|
||||
private val token: String by lazy {
|
||||
runCatching {
|
||||
val document = client.newCall(
|
||||
GET(
|
||||
baseUrl,
|
||||
),
|
||||
).execute().asJsoup()
|
||||
).execute().useAsJsoup()
|
||||
|
||||
document.selectFirst("meta[name=ao-search-token]")?.attr("content") ?: ""
|
||||
} catch (_: Throwable) {
|
||||
""
|
||||
}
|
||||
host = searchUrl.toHttpUrl().host
|
||||
}.getOrElse { "" }
|
||||
}
|
||||
|
||||
private val host: String = searchUrl.toHttpUrlOrNull()?.host ?: searchUrl
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ class JavGuru :
|
||||
|
||||
val iframeUrls = IFRAME_B64_REGEX.findAll(iframeData)
|
||||
.map { it.groupValues[1] }
|
||||
.map { Base64.decode(it, Base64.DEFAULT).let(::String) }
|
||||
.map { String(Base64.decode(it, Base64.DEFAULT)) }
|
||||
.toList()
|
||||
|
||||
return iframeUrls
|
||||
|
||||
@@ -694,7 +694,6 @@ import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
@@ -750,7 +749,7 @@ class SuperStreamAPI(private val json: Json, private val hideNsfw: Int) {
|
||||
IvParameterSpec(iv.toByteArray()),
|
||||
)
|
||||
|
||||
String(Base64.encode(cipher.doFinal(str.toByteArray()), 2), StandardCharsets.UTF_8)
|
||||
Base64.encodeToString(cipher.doFinal(str.toByteArray()), 2)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
@@ -814,7 +813,7 @@ class SuperStreamAPI(private val json: Json, private val hideNsfw: Int) {
|
||||
key,
|
||||
)
|
||||
}","encrypt_data":"$encryptedQuery"}"""
|
||||
val base64Body = String(Base64.encode(newBody.toByteArray(), Base64.NO_WRAP))
|
||||
val base64Body = Base64.encodeToString(newBody.toByteArray(), Base64.NO_WRAP)
|
||||
|
||||
val formData: RequestBody = FormBody.Builder()
|
||||
.add("data", base64Body)
|
||||
@@ -1000,7 +999,7 @@ class SuperStreamAPI(private val json: Json, private val hideNsfw: Int) {
|
||||
|
||||
linkData.data.list.forEach {
|
||||
if (it.path.isNullOrBlank().not()) {
|
||||
val videoUrl = it.path?.replace("\\/", "") ?: ""
|
||||
val videoUrl = it.path.replace("\\/", "")
|
||||
try {
|
||||
videoList.add(
|
||||
Video(
|
||||
@@ -1011,7 +1010,7 @@ class SuperStreamAPI(private val json: Json, private val hideNsfw: Int) {
|
||||
headers = headers,
|
||||
),
|
||||
)
|
||||
} catch (e: Error) {
|
||||
} catch (_: Error) {
|
||||
videoList.add(
|
||||
Video(
|
||||
videoUrl,
|
||||
@@ -1033,7 +1032,7 @@ class SuperStreamAPI(private val json: Json, private val hideNsfw: Int) {
|
||||
@SuppressLint("NewApi")
|
||||
private fun base64DecodeArray(string: String): ByteArray = try {
|
||||
Base64.decode(string, Base64.DEFAULT)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
JavaBase64.getDecoder().decode(string)
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ class AnimeBum :
|
||||
val iframeRegex = """video\[\d+]\s*=\s*['"]<iframe[^>]+src=["']([^"']+)["']""".toRegex()
|
||||
val matches = iframeRegex.findAll(scriptContent)
|
||||
|
||||
matches.toList().parallelCatchingFlatMapBlocking { match ->
|
||||
matches.asIterable().parallelCatchingFlatMapBlocking { match ->
|
||||
var videoUrl = match.groupValues[1]
|
||||
|
||||
if (videoUrl.startsWith("//")) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
@@ -113,7 +112,7 @@ class AnimeOnlineNinja :
|
||||
override val episodeMovieText = "Película"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val doc = getRealAnimeDoc(response.asJsoup())
|
||||
val doc = getRealAnimeDoc(response.useAsJsoup())
|
||||
val seasonList = doc.select(seasonListSelector)
|
||||
return if (seasonList.isEmpty()) {
|
||||
listOf(
|
||||
@@ -132,7 +131,7 @@ class AnimeOnlineNinja :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking { player ->
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
@@ -207,7 +206,7 @@ class AnimeOnlineNinja :
|
||||
else -> "div.OD_$prefLang"
|
||||
}
|
||||
return document.select("div.ODDIV $langSelector > li").flatMap {
|
||||
val hosterUrl = it.attr("onclick").toString()
|
||||
val hosterUrl = it.attr("onclick")
|
||||
.substringAfter("('")
|
||||
.substringBefore("')")
|
||||
val lang = when (langSelector) {
|
||||
@@ -263,13 +262,6 @@ class AnimeOnlineNinja :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
@@ -278,13 +270,6 @@ class AnimeOnlineNinja :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
val vrfIterceptPref = CheckBoxPreference(screen.context).apply {
|
||||
|
||||
@@ -77,7 +77,7 @@ class AsiaLiveAction :
|
||||
val anime = SAnime.create()
|
||||
val link = element.selectFirst("a")!!
|
||||
anime.setUrlWithoutDomain(link.attr("href"))
|
||||
anime.title = element.selectFirst("h5, h4, h3.Title, a.Title, span.Title")?.text()?.trim().orEmpty()
|
||||
anime.title = element.selectFirst("h5, h4, h3.Title, a.Title, span.Title")!!.text()
|
||||
val image = element.selectFirst("img")
|
||||
anime.thumbnail_url = image?.imageUrl()
|
||||
return anime
|
||||
@@ -88,9 +88,9 @@ class AsiaLiveAction :
|
||||
override fun animeDetailsParse(document: Document): SAnime = SAnime.create().apply {
|
||||
thumbnail_url = document.selectFirst("div.Poster")?.attr("style")?.extractBackgroundUrl()
|
||||
?: document.selectFirst("figure img")?.attr("abs:src")?.getHdImg()
|
||||
title = document.selectFirst("h2.Title, h1.Title")?.text()?.trim().orEmpty()
|
||||
document.selectFirst("h2.Title, h1.Title")?.text()?.let { title = it }
|
||||
val descriptionText = document.select("section article > p")
|
||||
.joinToString("\n\n") { it.text().trim() }
|
||||
.joinToString("\n\n") { it.text() }
|
||||
description = descriptionText.takeIf { it.isNotBlank() }
|
||||
val genreText = document.select("footer a.tag").joinToString { it.text() }
|
||||
genre = genreText.takeIf { it.isNotBlank() }
|
||||
@@ -341,13 +341,6 @@ class AsiaLiveAction :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -357,13 +350,6 @@ class AsiaLiveAction :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -52,7 +54,7 @@ class DeTodoPeliculas :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val referer = response.request.url.toString()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
if (players.isEmpty()) return emptyList()
|
||||
@@ -98,17 +100,13 @@ class DeTodoPeliculas :
|
||||
.add("Origin", baseUrl)
|
||||
.build()
|
||||
|
||||
val embedBody = runCatching {
|
||||
client.newCall(GET(normalized, embedHeaders)).execute().use { response ->
|
||||
if (!response.isSuccessful) "" else response.body.string()
|
||||
}
|
||||
}.getOrDefault("")
|
||||
val embedBody = client.newCall(GET(normalized, embedHeaders))
|
||||
.awaitSuccess()
|
||||
.useAsJsoup()
|
||||
|
||||
if (embedBody.isBlank()) return emptyList()
|
||||
|
||||
val iframeUrl = Jsoup.parse(embedBody)
|
||||
val iframeUrl = embedBody
|
||||
.selectFirst("iframe[src], iframe[data-src], iframe[data-lazy-src]")
|
||||
?.let { element ->
|
||||
?.let { element: Element ->
|
||||
sequenceOf("src", "data-src", "data-lazy-src")
|
||||
.map(element::attr)
|
||||
.firstOrNull { it.isNotBlank() }
|
||||
@@ -119,30 +117,26 @@ class DeTodoPeliculas :
|
||||
|
||||
return extractVideos(iframeUrl, lang, referer, depth + 1)
|
||||
}
|
||||
|
||||
val vidHideDomains = listOf("vidhide", "vidhidepro", "luluvdo", "vidhideplus")
|
||||
|
||||
return runCatching {
|
||||
vidHideDomains.firstOrNull { normalized.contains(it, ignoreCase = true) }
|
||||
?.let { domain ->
|
||||
vidHideExtractor.videosFromUrl(
|
||||
normalized,
|
||||
videoNameGen = { "$lang - ${domain.uppercase()} : $it" },
|
||||
)
|
||||
}
|
||||
?: when {
|
||||
"uqload" in normalized -> uqloadExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
listOf("streamwish", "strwish", "wishembed").any { normalized.contains(it) } -> streamWishExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
listOf("vidguard", "listeamed", "guard", "listeam").any { normalized.contains(it) } -> vidGuardExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
"voe" in normalized -> voeExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
else -> emptyList()
|
||||
}
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
emptyList()
|
||||
}
|
||||
return vidHideDomains.firstOrNull { normalized.contains(it, ignoreCase = true) }
|
||||
?.let { domain ->
|
||||
vidHideExtractor.videosFromUrl(
|
||||
normalized,
|
||||
videoNameGen = { "$lang - ${domain.uppercase()} : $it" },
|
||||
)
|
||||
}
|
||||
?: when {
|
||||
"uqload" in normalized -> uqloadExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
listOf("streamwish", "strwish", "wishembed").any { normalized.contains(it) } -> streamWishExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
listOf("vidguard", "listeamed", "guard", "listeam").any { normalized.contains(it) } -> vidGuardExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
"voe" in normalized -> voeExtractor.videosFromUrl(normalized, "$lang - ")
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element, referer: String): String? {
|
||||
private suspend fun getPlayerUrl(player: Element, referer: String): String? {
|
||||
val directCandidate = sequenceOf(
|
||||
player.attr("data-option"),
|
||||
player.attr("data-player"),
|
||||
@@ -175,9 +169,10 @@ class DeTodoPeliculas :
|
||||
.add("type", type)
|
||||
.build()
|
||||
|
||||
val responseBody = client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", ajaxHeaders, body)).execute().use { response ->
|
||||
response.body.string()
|
||||
}
|
||||
val responseBody = client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", ajaxHeaders, body))
|
||||
.awaitSuccess()
|
||||
.bodyString()
|
||||
val jsoupBody = Jsoup.parse(responseBody)
|
||||
|
||||
if (responseBody.isBlank()) return null
|
||||
|
||||
@@ -187,8 +182,8 @@ class DeTodoPeliculas :
|
||||
?.takeIf { it.isNotBlank() }
|
||||
if (embedByRegex != null) return embedByRegex
|
||||
|
||||
val iframe = Jsoup.parse(responseBody).selectFirst("iframe[src], iframe[data-src], iframe[data-lazy-src]")
|
||||
?.let { element ->
|
||||
val iframe = jsoupBody.selectFirst("iframe[src], iframe[data-src], iframe[data-lazy-src]")
|
||||
?.let { element: Element ->
|
||||
sequenceOf("src", "data-src", "data-lazy-src")
|
||||
.map(element::attr)
|
||||
.firstOrNull { it.isNotBlank() }
|
||||
@@ -197,7 +192,7 @@ class DeTodoPeliculas :
|
||||
?.takeIf { it.isNotBlank() }
|
||||
if (iframe != null) return iframe
|
||||
|
||||
val source = Jsoup.parse(responseBody).selectFirst("source[src]")?.attr("src")
|
||||
val source = jsoupBody.selectFirst("source[src]")?.attr("src")
|
||||
?.let(::normalizeUrl)
|
||||
?.takeIf { it.isNotBlank() }
|
||||
|
||||
@@ -272,13 +267,6 @@ class DeTodoPeliculas :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
@@ -287,13 +275,6 @@ class DeTodoPeliculas :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
screen.addPreference(langPref)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.lib.cryptoaes.CryptoAES
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
@@ -33,7 +32,7 @@ import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
@@ -44,7 +43,6 @@ import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URLDecoder
|
||||
import kotlin.text.RegexOption
|
||||
|
||||
class FlixLatam :
|
||||
DooPlay(
|
||||
@@ -52,8 +50,6 @@ class FlixLatam :
|
||||
"FlixLatam",
|
||||
"https://flixlatam.com",
|
||||
) {
|
||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/pelicula/page/$page")
|
||||
|
||||
override fun popularAnimeSelector() = latestUpdatesSelector()
|
||||
@@ -71,7 +67,7 @@ class FlixLatam :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
val referer = response.request.url.toString()
|
||||
val embedHeaders = headersBuilder().set("Referer", referer).build()
|
||||
@@ -85,7 +81,7 @@ class FlixLatam :
|
||||
|
||||
val embedDoc = Jsoup.parse(htmlContent)
|
||||
val links = extractNewExtractorLinks(embedDoc, htmlContent)
|
||||
?: return@parallelCatchingFlatMapBlocking emptyList<Video>()
|
||||
?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
links.parallelCatchingFlatMap { (link, language) ->
|
||||
serverVideoResolver(link, " $language")
|
||||
@@ -294,7 +290,7 @@ class FlixLatam :
|
||||
|
||||
return runCatching {
|
||||
val decoded = Base64.decode(payload, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
val element = json.parseToJsonElement(String(decoded))
|
||||
val element = String(decoded, Charsets.UTF_8).parseAs<JsonElement>()
|
||||
val obj = element.jsonObject
|
||||
|
||||
val link = obj["link"]?.jsonPrimitive?.contentOrNull
|
||||
@@ -354,13 +350,6 @@ class FlixLatam :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
ListPreference(screen.context).apply {
|
||||
key = PREF_SERVER_KEY
|
||||
@@ -369,14 +358,8 @@ class FlixLatam :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
screen.addPreference(langPref)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,17 +29,15 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.catchingFlatMapBlocking
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -55,8 +53,6 @@ class Gnula :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
companion object {
|
||||
@@ -85,10 +81,10 @@ class Gnula :
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/archives/movies/page/$page")
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val jsonString = document.selectFirst("script:containsData({\"props\":{\"pageProps\":)")?.data()
|
||||
?: return AnimesPage(emptyList(), false)
|
||||
val jsonData = jsonString.parseTo<PopularModel>().props.pageProps
|
||||
val jsonData = jsonString.parseAs<PopularModel>().props.pageProps
|
||||
val hasNextPage = document.selectFirst("ul.pagination > li.page-item.active ~ li > a > span.visually-hidden")?.text()?.contains("Next") ?: false
|
||||
var type = jsonData.results.typename ?: ""
|
||||
|
||||
@@ -115,7 +111,7 @@ class Gnula :
|
||||
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return if (response.request.url.toString().contains("/movies/")) {
|
||||
listOf(
|
||||
SEpisode.create().apply {
|
||||
@@ -126,40 +122,39 @@ class Gnula :
|
||||
)
|
||||
} else {
|
||||
val jsonString = document.selectFirst("script:containsData({\"props\":{\"pageProps\":)")?.data() ?: return emptyList()
|
||||
val jsonData = jsonString.parseTo<SeasonModel>().props.pageProps
|
||||
val jsonData = jsonString.parseAs<SeasonModel>().props.pageProps
|
||||
var episodeCounter = 1F
|
||||
jsonData.post.seasons
|
||||
.flatMap { season ->
|
||||
season.episodes.map { ep ->
|
||||
val episode = SEpisode.create().apply {
|
||||
SEpisode.create().apply {
|
||||
episode_number = episodeCounter++
|
||||
name = "T${season.number} - E${ep.number} - ${ep.title}"
|
||||
date_upload = ep.releaseDate?.let(DATE_FORMATTER::tryParse) ?: 0L
|
||||
setUrlWithoutDomain("$baseUrl/series/${ep.slug.name}/seasons/${ep.slug.season}/episodes/${ep.slug.episode}")
|
||||
}
|
||||
episode
|
||||
}
|
||||
}
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val document = response.useAsJsoup()
|
||||
val jsonString = document.selectFirst("script:containsData({\"props\":{\"pageProps\":)")?.data() ?: return emptyList()
|
||||
|
||||
if (response.request.url.toString().contains("/movies/")) {
|
||||
val pageProps = jsonString.parseTo<SeasonModel>().props.pageProps
|
||||
pageProps.post.players.latino.toVideoList("[LAT]").also(videoList::addAll)
|
||||
pageProps.post.players.spanish.toVideoList("[CAST]").also(videoList::addAll)
|
||||
pageProps.post.players.english.toVideoList("[SUB]").also(videoList::addAll)
|
||||
val pageProps = jsonString.parseAs<SeasonModel>().props.pageProps
|
||||
val players = pageProps.post.players.latino.map { it to "[LAT]" } +
|
||||
pageProps.post.players.spanish.map { it to "[CAST]" } +
|
||||
pageProps.post.players.english.map { it to "[SUB]" }
|
||||
return players.parallelCatchingFlatMapBlocking { (region, lang) -> getVideos(region, lang) }
|
||||
} else {
|
||||
val pageProps = jsonString.parseTo<EpisodeModel>().props.pageProps
|
||||
pageProps.episode.players.latino.toVideoList("[LAT]").also(videoList::addAll)
|
||||
pageProps.episode.players.spanish.toVideoList("[CAST]").also(videoList::addAll)
|
||||
pageProps.episode.players.english.toVideoList("[SUB]").also(videoList::addAll)
|
||||
val pageProps = jsonString.parseAs<EpisodeModel>().props.pageProps
|
||||
val players = pageProps.episode.players.latino.map { it to "[LAT]" } +
|
||||
pageProps.episode.players.spanish.map { it to "[CAST]" } +
|
||||
pageProps.episode.players.english.map { it to "[SUB]" }
|
||||
return players.parallelCatchingFlatMapBlocking { (region, lang) -> getVideos(region, lang) }
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
/*--------------------------------Video extractors------------------------------------*/
|
||||
@@ -182,46 +177,44 @@ class Gnula :
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private suspend fun serverVideoResolver(url: String, prefix: String = "", serverName: String? = ""): List<Video> {
|
||||
return runCatching {
|
||||
val source = serverName?.ifEmpty { url } ?: url
|
||||
val matched = conventions.firstOrNull { (_, names) -> names.any { it.lowercase() in source.lowercase() } }?.first
|
||||
when (matched) {
|
||||
"voe" -> voeExtractor.videosFromUrl(url, "$prefix ")
|
||||
"okru" -> okruExtractor.videosFromUrl(url, prefix)
|
||||
"filemoon" -> filemoonExtractor.videosFromUrl(url, prefix = "$prefix Filemoon:")
|
||||
"amazon" -> {
|
||||
val body = client.newCall(GET(url)).execute().asJsoup()
|
||||
return if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
|
||||
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
|
||||
.substringAfter("shareId = \"").substringBefore("\"")
|
||||
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
|
||||
.execute().asJsoup()
|
||||
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
|
||||
val amazonApi =
|
||||
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
|
||||
.execute().asJsoup()
|
||||
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
|
||||
listOf(Video(videoUrl, "$prefix Amazon", videoUrl))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val source = serverName?.ifEmpty { url } ?: url
|
||||
val matched = conventions.firstOrNull { (_, names) -> names.any { it.lowercase() in source.lowercase() } }?.first
|
||||
return when (matched) {
|
||||
"voe" -> voeExtractor.videosFromUrl(url, "$prefix ")
|
||||
"okru" -> okruExtractor.videosFromUrl(url, prefix)
|
||||
"filemoon" -> filemoonExtractor.videosFromUrl(url, prefix = "$prefix Filemoon:")
|
||||
"amazon" -> {
|
||||
val body = client.newCall(GET(url)).awaitSuccess().useAsJsoup()
|
||||
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
|
||||
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
|
||||
.substringAfter("shareId = \"").substringBefore("\"")
|
||||
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
|
||||
.awaitSuccess().useAsJsoup()
|
||||
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
|
||||
val amazonApi =
|
||||
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
|
||||
.awaitSuccess().useAsJsoup()
|
||||
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
|
||||
listOf(Video(videoUrl, "$prefix Amazon", videoUrl))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
"uqload" -> uqloadExtractor.videosFromUrl(url, prefix)
|
||||
"mp4upload" -> mp4uploadExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
"streamwish" -> streamWishExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
|
||||
"doodstream" -> doodExtractor.videosFromUrl(url, "$prefix DoodStream")
|
||||
"streamlare" -> streamlareExtractor.videosFromUrl(url, prefix)
|
||||
"yourupload" -> yourUploadExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
"burstcloud" -> burstCloudExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
"fastream" -> fastreamExtractor.videosFromUrl(url, prefix = "$prefix Fastream:")
|
||||
"upstream" -> upstreamExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
"streamsilk" -> streamSilkExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamSilk:$it" })
|
||||
"streamtape" -> streamTapeExtractor.videosFromUrl(url, quality = "$prefix StreamTape")
|
||||
"vidhide" -> vidHideExtractor.videosFromUrl(url, videoNameGen = { "$prefix VidHide:$it" })
|
||||
"vidguard" -> vidGuardExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
else -> universalExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
}
|
||||
}.getOrNull() ?: emptyList()
|
||||
"uqload" -> uqloadExtractor.videosFromUrl(url, prefix)
|
||||
"mp4upload" -> mp4uploadExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
"streamwish" -> streamWishExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
|
||||
"doodstream" -> doodExtractor.videosFromUrl(url, "$prefix DoodStream")
|
||||
"streamlare" -> streamlareExtractor.videosFromUrl(url, prefix)
|
||||
"yourupload" -> yourUploadExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
"burstcloud" -> burstCloudExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
"fastream" -> fastreamExtractor.videosFromUrl(url, prefix = "$prefix Fastream:")
|
||||
"upstream" -> upstreamExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
"streamsilk" -> streamSilkExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamSilk:$it" })
|
||||
"streamtape" -> streamTapeExtractor.videosFromUrl(url, quality = "$prefix StreamTape")
|
||||
"vidhide" -> vidHideExtractor.videosFromUrl(url, videoNameGen = { "$prefix VidHide:$it" })
|
||||
"vidguard" -> vidGuardExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
else -> universalExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
}
|
||||
}
|
||||
|
||||
private val conventions = listOf(
|
||||
@@ -277,7 +270,7 @@ class Gnula :
|
||||
|
||||
override fun animeDetailsParse(document: Document): SAnime {
|
||||
val jsonString = document.selectFirst("script:containsData({\"props\":{\"pageProps\":)")?.data() ?: return SAnime.create()
|
||||
val json = jsonString.parseTo<SeasonModel>()
|
||||
val json = jsonString.parseAs<SeasonModel>()
|
||||
val post = json.props.pageProps.post
|
||||
return SAnime.create().apply {
|
||||
title = post.titles.name ?: ""
|
||||
@@ -289,15 +282,14 @@ class Gnula :
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Region>.toVideoList(lang: String): List<Video> = catchingFlatMapBlocking {
|
||||
client.newCall(GET(it.result)).awaitSuccess().useAsJsoup().select("script")
|
||||
.map { sc -> sc.data() }
|
||||
.firstOrNull { data -> data.contains("var url = '") }
|
||||
?.let { data ->
|
||||
val url = data.substringAfter("var url = '").substringBefore("';")
|
||||
serverVideoResolver(url, lang)
|
||||
} ?: emptyList()
|
||||
}
|
||||
private suspend fun getVideos(region: Region, lang: String): List<Video> = client.newCall(GET(region.result)).awaitSuccess().useAsJsoup()
|
||||
.select("script")
|
||||
.map { sc -> sc.data() }
|
||||
.firstOrNull { data -> data.contains("var url = '") }
|
||||
?.let { data ->
|
||||
val url = data.substringAfter("var url = '").substringBefore("';")
|
||||
serverVideoResolver(url, lang)
|
||||
} ?: emptyList()
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
|
||||
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
|
||||
@@ -330,8 +322,6 @@ class Gnula :
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private inline fun <reified T> String.parseTo(): T = json.decodeFromString<T>(this)
|
||||
|
||||
private fun urlSolverByType(type: String, slug: String): String = when (type) {
|
||||
"PaginatedMovie", "PaginatedGenre" -> "$baseUrl/movies/$slug"
|
||||
"PaginatedSerie" -> "$baseUrl/series/$slug"
|
||||
@@ -346,13 +336,6 @@ class Gnula :
|
||||
entryValues = LANGUAGE_LIST
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -362,13 +345,6 @@ class Gnula :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -378,13 +354,6 @@ class Gnula :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -205,13 +205,6 @@ class Lacartoons :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -221,13 +214,6 @@ class Lacartoons :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package eu.kanade.tachiyomi.animeextension.es.lamovie
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import aniyomi.lib.doodextractor.DoodExtractor
|
||||
@@ -12,6 +10,7 @@ import aniyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import aniyomi.lib.vidhideextractor.VidHideExtractor
|
||||
import aniyomi.lib.voeextractor.VoeExtractor
|
||||
import aniyomi.lib.youruploadextractor.YourUploadExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.BuildConfig
|
||||
import eu.kanade.tachiyomi.animeextension.es.lamovie.extractors.LaMovieEmbedExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
@@ -20,10 +19,11 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dopeflix.DopeFlix
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import keiyoushi.utils.tryParse
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -37,8 +37,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
@@ -54,7 +52,6 @@ class LaMovie :
|
||||
) {
|
||||
override val id: Long = 5419283741928374105
|
||||
|
||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client, headers) }
|
||||
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||
@@ -65,10 +62,6 @@ class LaMovie :
|
||||
private val goodStreamExtractor by lazy { GoodStreamExtractor(client, headers) }
|
||||
private val lamovieEmbedExtractor by lazy { LaMovieEmbedExtractor(client, headers) }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int): Request {
|
||||
val type = preferredListingType()
|
||||
@@ -140,7 +133,7 @@ class LaMovie :
|
||||
|
||||
if (payload.isEmpty()) return null
|
||||
|
||||
return json.encodeToString(JsonObject(payload))
|
||||
return JsonObject(payload).toJsonString()
|
||||
}
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
@@ -157,7 +150,7 @@ class LaMovie :
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val data = response.parseData(PostDto.serializer())
|
||||
val data = response.parseData<PostDto>()
|
||||
return data.toSAnime()
|
||||
}
|
||||
|
||||
@@ -165,7 +158,7 @@ class LaMovie :
|
||||
override fun episodeListRequest(anime: SAnime): Request = animeDetailsRequest(anime)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val data = response.parseData(PostDto.serializer())
|
||||
val data = response.parseData<PostDto>()
|
||||
val context = AnimeContext(
|
||||
type = data.postType,
|
||||
slug = data.slug.ifBlank { data.id.toString() },
|
||||
@@ -198,7 +191,7 @@ class LaMovie :
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val data = response.parseData(PlayerDataDto.serializer())
|
||||
val data = response.parseData<PlayerDataDto>()
|
||||
|
||||
val embeds = data.parseEmbeds()
|
||||
if (embeds.isEmpty()) return emptyList()
|
||||
@@ -216,16 +209,16 @@ class LaMovie :
|
||||
)
|
||||
}
|
||||
|
||||
return prioritizedEmbeds.flatMap(::resolveEmbedVideos)
|
||||
return prioritizedEmbeds.parallelCatchingFlatMapBlocking(::resolveEmbedVideos)
|
||||
}
|
||||
|
||||
private fun resolveEmbedVideos(embed: EmbedItem): List<Video> = runCatching {
|
||||
private suspend fun resolveEmbedVideos(embed: EmbedItem): List<Video> {
|
||||
val prefix = buildString {
|
||||
embed.language?.takeIf(String::isNotBlank)?.let { append("${it.uppercase(Locale.US)} | ") }
|
||||
embed.quality?.takeIf(String::isNotBlank)?.let { append(" - $it") }
|
||||
}
|
||||
|
||||
when (embed.serverKey()) {
|
||||
return when (embed.serverKey()) {
|
||||
SERVER_KEY_DOOD -> doodExtractor.videosFromUrl(embed.url, "$prefix - Doodstream")
|
||||
SERVER_KEY_VOE -> voeExtractor.videosFromUrl(embed.url, "$prefix - Voe")
|
||||
SERVER_KEY_MP4UPLOAD -> mp4uploadExtractor.videosFromUrl(embed.url, headers, "$prefix - Mp4upload")
|
||||
@@ -237,7 +230,7 @@ class LaMovie :
|
||||
SERVER_KEY_LAMOVIE -> lamovieEmbedExtractor.videosFromUrl(embed.url, "$prefix - HLS")
|
||||
else -> emptyList()
|
||||
}
|
||||
}.getOrElse { emptyList() }
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val preferredQuality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT) ?: PREF_QUALITY_DEFAULT
|
||||
@@ -333,8 +326,7 @@ class LaMovie :
|
||||
val combined = texts
|
||||
.asSequence()
|
||||
.filterNotNull()
|
||||
.map { it.lowercase(Locale.US) }
|
||||
.joinToString(" ")
|
||||
.joinToString(" ") { it.lowercase(Locale.US) }
|
||||
|
||||
SERVER_KEYWORDS.forEach { (key, keywords) ->
|
||||
if (keywords.any { it in combined }) return key
|
||||
@@ -368,7 +360,7 @@ class LaMovie :
|
||||
.addQueryParameter("page", page.toString())
|
||||
|
||||
private fun Response.parseListing(allowShortQueryFallback: Boolean = false): AnimesPage {
|
||||
val root = json.parseToJsonElement(body.string()).jsonObject
|
||||
val root = parseAs<JsonElement>().jsonObject
|
||||
if (root["error"]?.jsonPrimitive?.booleanOrNull == true) {
|
||||
val message = root["message"]?.jsonPrimitive?.contentOrNull ?: "Error desconocido"
|
||||
if (allowShortQueryFallback && message.contains("muy corta", ignoreCase = true)) {
|
||||
@@ -378,7 +370,7 @@ class LaMovie :
|
||||
}
|
||||
|
||||
val data = root["data"] ?: throw Exception("Respuesta vacía de la API")
|
||||
val listing = json.decodeFromJsonElement(ListingDataDto.serializer(), data)
|
||||
val listing = data.parseAs<ListingDataDto>()
|
||||
val entries = listing.posts.map { it.toSAnime() }
|
||||
val hasNext = listing.pagination?.let { (it.currentPage ?: 0) < (it.lastPage ?: 0) } ?: false
|
||||
return AnimesPage(entries, hasNext)
|
||||
@@ -391,7 +383,7 @@ class LaMovie :
|
||||
var firstSeasonUsed: String? = null
|
||||
|
||||
for (candidate in initialSeasons) {
|
||||
val result = kotlin.runCatching { fetchEpisodesPage(seriesId, candidate, 1) }.getOrNull()
|
||||
val result = runCatching { fetchEpisodesPage(seriesId, candidate, 1) }.getOrNull()
|
||||
if (result != null) {
|
||||
firstResponse = result
|
||||
firstSeasonUsed = candidate
|
||||
@@ -430,10 +422,8 @@ class LaMovie :
|
||||
|
||||
val request = GET(builder.build(), headers)
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
response.use {
|
||||
return it.parseData(EpisodesListDto.serializer())
|
||||
}
|
||||
return client.newCall(request).execute()
|
||||
.parseData<EpisodesListDto>()
|
||||
}
|
||||
|
||||
private fun apiUrlBuilder(vararg segments: String): HttpUrl.Builder {
|
||||
@@ -442,13 +432,13 @@ class LaMovie :
|
||||
return apiBase
|
||||
}
|
||||
|
||||
private fun <T> Response.parseData(serializer: KSerializer<T>): T {
|
||||
private inline fun <reified T> Response.parseData(): T {
|
||||
val element = parseDataElement()
|
||||
return json.decodeFromJsonElement(serializer, element)
|
||||
return element.parseAs<T>()
|
||||
}
|
||||
|
||||
private fun Response.parseDataElement(): JsonElement {
|
||||
val root = json.parseToJsonElement(body.string()).jsonObject
|
||||
val root = bodyString().parseAs<JsonElement>().jsonObject
|
||||
if (root["error"]?.jsonPrimitive?.booleanOrNull == true) {
|
||||
val message = root["message"]?.jsonPrimitive?.contentOrNull ?: "Error desconocido"
|
||||
throw Exception(message)
|
||||
@@ -469,8 +459,7 @@ class LaMovie :
|
||||
val season = (seasonNumber ?: 1).coerceAtLeast(1)
|
||||
val number = (episodeNumber ?: 1).coerceAtLeast(1)
|
||||
val baseName = sequenceOf(name, title)
|
||||
.mapNotNull { it?.takeIf(String::isNotBlank) }
|
||||
.firstOrNull()
|
||||
.firstNotNullOfOrNull { it?.takeIf(String::isNotBlank) }
|
||||
?: "Episodio $number"
|
||||
|
||||
val displayName = "T${season}x$number - $baseName"
|
||||
@@ -815,11 +804,6 @@ class LaMovie :
|
||||
|
||||
val stored = preferences.getString(key, PREF_QUALITY_DEFAULT) ?: PREF_QUALITY_DEFAULT
|
||||
value = stored
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putString(key, newValue as String).apply()
|
||||
true
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -853,11 +837,6 @@ class LaMovie :
|
||||
|
||||
val stored = preferences.getString(key, PREF_SERVER_DEFAULT) ?: PREF_SERVER_DEFAULT
|
||||
value = stored
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putString(key, newValue as String).apply()
|
||||
true
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.jsunpacker.JsUnpacker
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
@@ -17,10 +20,9 @@ class LaMovieEmbedExtractor(
|
||||
private val client: OkHttpClient,
|
||||
private val headers: Headers,
|
||||
) {
|
||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||
|
||||
fun videosFromUrl(url: String, prefix: String): List<Video> {
|
||||
suspend fun videosFromUrl(url: String, prefix: String): List<Video> {
|
||||
val parsedUrl = url.toHttpUrlOrNull()
|
||||
val origin = parsedUrl?.let { "${it.scheme}://${it.host}" } ?: DEFAULT_ORIGIN
|
||||
val referer = "$origin/"
|
||||
@@ -30,9 +32,7 @@ class LaMovieEmbedExtractor(
|
||||
set("Referer", referer)
|
||||
}.build()
|
||||
|
||||
val body = client.newCall(GET(url, embedHeaders)).execute().use { response ->
|
||||
response.body.string()
|
||||
}
|
||||
val body = client.newCall(GET(url, embedHeaders)).awaitSuccess().bodyString()
|
||||
|
||||
var playlistUrl: String? = null
|
||||
val subtitleAccumulator = linkedSetOf<Pair<String, String>>()
|
||||
@@ -45,7 +45,7 @@ class LaMovieEmbedExtractor(
|
||||
}
|
||||
|
||||
CONFIG_REGEX.find(body)?.groupValues?.getOrNull(1)?.let { configText ->
|
||||
val configJson = runCatching { json.parseToJsonElement(configText).jsonObject }.getOrNull()
|
||||
val configJson = runCatching { configText.parseAs<JsonElement>().jsonObject }.getOrNull()
|
||||
configJson?.get("file")?.jsonPrimitive?.contentOrNull?.takeIf(String::isNotBlank)?.let {
|
||||
playlistUrl = it.unescapeUrl()
|
||||
}
|
||||
@@ -155,7 +155,7 @@ class LaMovieEmbedExtractor(
|
||||
private const val PACKER_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
private val CONFIG_REGEX = Regex(
|
||||
pattern = """<script\s+id['\"]config['\"][^>]*>(\{[\s\S]*?\})</script>""",
|
||||
pattern = """<script\s+id=['"]config['"][^>]*>(\{[\s\S]*?\})</script>""",
|
||||
options = setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL),
|
||||
)
|
||||
private val SCRIPT_REGEX = Regex("""eval\(function\(p,a,c,k,e,d\)[\s\S]*?\.split('\|')\)\)""")
|
||||
@@ -163,7 +163,7 @@ class LaMovieEmbedExtractor(
|
||||
pattern = """eval\(function\(p,a,c,k,e,d\)\{[\s\S]*?\}\('([^']*)',(\\d+),(\\d+),'([^']*)'\.split\('\|'\)\)""",
|
||||
options = setOf(RegexOption.DOT_MATCHES_ALL),
|
||||
)
|
||||
private val M3U8_REGEX = Regex("""https?://[^\s'\"]+\.m3u8[^\s'\"]*""")
|
||||
private val SUBTITLE_REGEX = Regex("""\[(.+?)](https?://[^\s'\"]+)""")
|
||||
private val M3U8_REGEX = Regex("""https?://[^\s'"]+\.m3u8[^\s'"]*""")
|
||||
private val SUBTITLE_REGEX = Regex("""\[(.+?)](https?://[^\s'"]+)""")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ 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.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelFlatMapBlocking
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@@ -29,9 +32,7 @@ import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
@@ -55,8 +56,6 @@ open class MhdFlix :
|
||||
coerceInputValues = true
|
||||
}
|
||||
|
||||
private val jsonMediaType = "application/json".toMediaType()
|
||||
|
||||
private val dateFormatter by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.US) }
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
@@ -132,9 +131,9 @@ open class MhdFlix :
|
||||
val endIndex = minOf(startIndex + popularPageSize, catalogEntries.size)
|
||||
val pageSlice = catalogEntries.subList(startIndex, endIndex)
|
||||
|
||||
val animeList = pageSlice.mapNotNull { entry ->
|
||||
val mediaId = entry.idMedia ?: return@mapNotNull null
|
||||
fetchMediaSummary(mediaId)?.toSAnime()
|
||||
val animeList = pageSlice.parallelCatchingFlatMapBlocking { entry ->
|
||||
val mediaId = entry.idMedia ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
listOfNotNull(fetchMediaSummary(mediaId)?.toSAnime())
|
||||
}
|
||||
|
||||
val hasNext = endIndex < catalogEntries.size
|
||||
@@ -178,7 +177,7 @@ open class MhdFlix :
|
||||
val typeOnly = params.type?.isNotBlank() == true && query.isBlank() && params.genre == null && params.year == null
|
||||
|
||||
if (typeOnly) {
|
||||
val type = params.type!!
|
||||
val type = params.type
|
||||
val url = "$apiUrl/api/seo/medias".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("typeFilter", type)
|
||||
.build()
|
||||
@@ -268,9 +267,9 @@ open class MhdFlix :
|
||||
|
||||
val endIndex = minOf(startIndex + popularPageSize, filteredEntries.size)
|
||||
val pageSlice = filteredEntries.subList(startIndex, endIndex)
|
||||
val animeList = pageSlice.mapNotNull { entry ->
|
||||
val mediaId = entry.idMedia ?: return@mapNotNull null
|
||||
fetchMediaSummary(mediaId)?.toSAnime()
|
||||
val animeList = pageSlice.parallelCatchingFlatMapBlocking { entry ->
|
||||
val mediaId = entry.idMedia ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
listOfNotNull(fetchMediaSummary(mediaId)?.toSAnime())
|
||||
}
|
||||
val hasNext = endIndex < filteredEntries.size
|
||||
return AnimesPage(animeList, hasNext)
|
||||
@@ -316,22 +315,20 @@ open class MhdFlix :
|
||||
val uniqueLinks = payload.data.distinctBy { it.link }
|
||||
if (uniqueLinks.isEmpty()) return emptyList()
|
||||
|
||||
val videos = uniqueLinks.parallelFlatMapBlocking { link ->
|
||||
val videos = uniqueLinks.parallelCatchingFlatMapBlocking { link ->
|
||||
link.toVideos()
|
||||
}.distinctBy { it.url }
|
||||
|
||||
return videos.sort()
|
||||
}
|
||||
|
||||
private fun fetchMediaSummary(mediaId: Int): MediaDto? {
|
||||
private suspend fun fetchMediaSummary(mediaId: Int): MediaDto? {
|
||||
mediaDetailCache[mediaId]?.let { return it }
|
||||
|
||||
return runCatching {
|
||||
client.newCall(apiGet("$apiUrl/api/media/$mediaId")).execute().use { detailResponse ->
|
||||
val payload = json.decodeFromString<MediaDetailResponse>(detailResponse.body.string())
|
||||
payload.data?.also { mediaDetailCache[mediaId] = it }
|
||||
}
|
||||
}.getOrNull()
|
||||
return client.newCall(apiGet("$apiUrl/api/media/$mediaId"))
|
||||
.awaitSuccess()
|
||||
.parseAs<MediaDetailResponse>(json)
|
||||
.data?.also { mediaDetailCache[mediaId] = it }
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
@@ -358,13 +355,6 @@ open class MhdFlix :
|
||||
entryValues = LANGUAGE_LIST
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -374,13 +364,6 @@ open class MhdFlix :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -390,16 +373,7 @@ open class MhdFlix :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
FilemoonExtractor.addSubtitlePref(screen)
|
||||
}
|
||||
|
||||
private val vidHideExtractor by lazy { VidHideExtractor(client, headers) }
|
||||
@@ -432,19 +406,16 @@ open class MhdFlix :
|
||||
val prefixLabel = baseLabel.joinToString(" - ").let { if (it.isNotBlank()) "$it - " else "" }
|
||||
|
||||
return when {
|
||||
lowerServer.contains("streamwish") -> runExtractor { streamWishExtractor.videosFromUrl(url) { q -> nameBuilder("StreamWish", q) } }
|
||||
lowerServer.contains("vidhide") -> runExtractor { vidHideExtractor.videosFromUrl(url) { q -> nameBuilder("VidHide", q) } }
|
||||
lowerServer.contains("voe") -> runExtractor { voeExtractor.videosFromUrl(url, prefix = prefixLabel) }
|
||||
lowerServer.contains("uqload") -> runExtractor { uqloadExtractor.videosFromUrl(url, prefix = nameBuilder("Uqload", null)) }
|
||||
lowerServer.contains("streamtape") -> runExtractor { streamTapeExtractor.videosFromUrl(url, quality = nameBuilder("StreamTape", null)) }
|
||||
lowerServer.contains("dood") -> runExtractor { doodExtractor.videosFromUrl(url, quality = nameBuilder("Doodstream", null)) }
|
||||
lowerServer.contains("mixdrop") -> runExtractor { mixDropExtractor.videosFromUrl(url, prefix = prefixLabel) }
|
||||
lowerServer.contains("filemoon") -> runExtractor { filemoonExtractor.videosFromUrl(url, nameBuilder("Filemoon", null), headers) }
|
||||
lowerServer.contains("lulu") || lowerServer.contains("luluvdo") -> runExtractor { luluExtractor.videosFromUrl(url, prefixLabel) }
|
||||
lowerServer.contains("filelions") -> runExtractor { universalExtractor.videosFromUrl(url, headers, prefix = nameBuilder("Filelions", null)) }
|
||||
lowerServer.contains("hexupload") -> runExtractor { universalExtractor.videosFromUrl(url, headers, prefix = nameBuilder("Hexupload", null)) }
|
||||
lowerServer.contains("netu") -> runExtractor { universalExtractor.videosFromUrl(url, headers, prefix = nameBuilder("Netu", null)) }
|
||||
else -> runExtractor { universalExtractor.videosFromUrl(url, headers, prefix = nameBuilder(serverLabel.ifBlank { "Mirror" }, null)) }
|
||||
lowerServer.contains("streamwish") -> streamWishExtractor.videosFromUrl(url) { q -> nameBuilder("StreamWish", q) }
|
||||
lowerServer.contains("vidhide") -> vidHideExtractor.videosFromUrl(url) { q -> nameBuilder("VidHide", q) }
|
||||
lowerServer.contains("voe") -> voeExtractor.videosFromUrl(url, prefix = prefixLabel)
|
||||
lowerServer.contains("uqload") -> uqloadExtractor.videosFromUrl(url, prefix = nameBuilder("Uqload", null))
|
||||
lowerServer.contains("streamtape") -> streamTapeExtractor.videosFromUrl(url, quality = nameBuilder("StreamTape", null))
|
||||
lowerServer.contains("dood") -> doodExtractor.videosFromUrl(url, quality = nameBuilder("Doodstream", null))
|
||||
lowerServer.contains("mixdrop") -> mixDropExtractor.videosFromUrl(url, prefix = prefixLabel)
|
||||
lowerServer.contains("filemoon") -> filemoonExtractor.videosFromUrl(url, nameBuilder("Filemoon", null), headers)
|
||||
lowerServer.contains("lulu") || lowerServer.contains("luluvdo") -> luluExtractor.videosFromUrl(url, prefixLabel)
|
||||
else -> universalExtractor.videosFromUrl(url, headers, prefix = nameBuilder(serverLabel.ifBlank { "Mirror" }, null))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,8 +476,8 @@ open class MhdFlix :
|
||||
val serieId = details.idMedia ?: return emptyList()
|
||||
val seasonsResponse = client.newCall(apiGet("$apiUrl/api/serie/$serieId/seasons")).execute()
|
||||
val seasons = seasonsResponse.parseAs<SeasonListResponse>().data
|
||||
val episodes = seasons.flatMap { season ->
|
||||
val seasonId = season.idSeasson ?: return@flatMap emptyList<SEpisode>()
|
||||
val episodes = seasons.parallelCatchingFlatMapBlocking { season ->
|
||||
val seasonId = season.idSeasson ?: return@parallelCatchingFlatMapBlocking emptyList<SEpisode>()
|
||||
val seasonNumber = season.num ?: 0
|
||||
fetchSeasonEpisodes(seasonId, seasonNumber, serieId)
|
||||
}
|
||||
@@ -518,12 +489,13 @@ open class MhdFlix :
|
||||
return sortedEpisodes
|
||||
}
|
||||
|
||||
private fun fetchSeasonEpisodes(seasonId: Int, seasonNumber: Int, serieId: Int): List<SEpisode> {
|
||||
private suspend fun fetchSeasonEpisodes(seasonId: Int, seasonNumber: Int, serieId: Int): List<SEpisode> {
|
||||
val collected = mutableListOf<SEpisode>()
|
||||
val seenIds = mutableSetOf<Int>()
|
||||
var page = 1
|
||||
do {
|
||||
val response = client.newCall(apiGet("$apiUrl/api/serie/episodes/$seasonId/$page", page)).execute()
|
||||
val response = client.newCall(apiGet("$apiUrl/api/serie/episodes/$seasonId/$page", page))
|
||||
.awaitSuccess()
|
||||
val payload = response.parseAs<EpisodeListResponse>()
|
||||
payload.data.forEach { episodeDto ->
|
||||
val episodeId = episodeDto.idEpisodios ?: return@forEach
|
||||
@@ -631,7 +603,7 @@ open class MhdFlix :
|
||||
.build()
|
||||
|
||||
private fun apiPost(url: String, page: Int, obj: JsonObject): Request {
|
||||
val body = obj.toString().toRequestBody(jsonMediaType)
|
||||
val body = obj.toJsonRequestBody()
|
||||
return Request.Builder()
|
||||
.url(url)
|
||||
.headers(headers)
|
||||
@@ -653,13 +625,9 @@ open class MhdFlix :
|
||||
val entries: List<EpisodeDto>,
|
||||
)
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T = use { json.decodeFromString(it.body.string()) }
|
||||
|
||||
private val qualityRegex = Regex("""(\d+)p""")
|
||||
private val whitespaceRegex = Regex("\\s+")
|
||||
|
||||
private inline fun runExtractor(block: () -> List<Video>): List<Video> = runCatching(block).getOrElse { emptyList() }
|
||||
|
||||
private fun MediaListResponse.mediaEntries(): List<MediaDto> = when (val element = data) {
|
||||
is JsonArray -> element.mapNotNull { runCatching { json.decodeFromJsonElement<MediaDto>(it) }.getOrNull() }
|
||||
is JsonObject -> listOfNotNull(runCatching { json.decodeFromJsonElement<MediaDto>(element) }.getOrNull())
|
||||
|
||||
@@ -13,16 +13,16 @@ 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.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URLDecoder
|
||||
|
||||
class Pandrama :
|
||||
@@ -39,8 +39,6 @@ class Pandrama :
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
companion object {
|
||||
private const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
private const val PREF_QUALITY_DEFAULT = "1080"
|
||||
@@ -52,7 +50,7 @@ class Pandrama :
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val details = SAnime.create().apply {
|
||||
status = SAnime.UNKNOWN
|
||||
description = document.selectFirst("#height_limit")?.ownText()
|
||||
@@ -71,7 +69,7 @@ class Pandrama :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/explorar/Dramas--------$page---/", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val elements = document.select("a.public-list-exp")
|
||||
val nextPage = document.select("[title=\"Página siguiente\"]").any()
|
||||
val animeList = elements.map { element ->
|
||||
@@ -149,25 +147,25 @@ class Pandrama :
|
||||
)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return document.select(".anthology-list-play li a").groupBy { it.text().trim() }.map {
|
||||
val urlList = json.encodeToString(it.value.map { it.attr("abs:href") })
|
||||
val urlList = it.value.map { it.attr("abs:href") }.toJsonString()
|
||||
SEpisode.create().apply {
|
||||
name = "Episodio ${it.key.substringAfter("Ep.").trim()}"
|
||||
episode_number = it.key.trim().toFloatOrNull() ?: 0F
|
||||
episode_number = it.key.substringAfter("Ep.").trim().toFloatOrNull() ?: 0F
|
||||
url = urlList
|
||||
}
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val serverData = json.decodeFromString<List<String>>(episode.url)
|
||||
return serverData.parallelCatchingFlatMapBlocking {
|
||||
val page = client.newCall(GET(it)).execute().asJsoup()
|
||||
val serverData = episode.url.parseAs<List<String>>()
|
||||
return serverData.parallelCatchingFlatMap {
|
||||
val page = client.newCall(GET(it)).awaitSuccess().useAsJsoup()
|
||||
val jsonData = page.selectFirst("script:containsData(var player_aaaa)")
|
||||
?.data()?.substringAfter("var player_aaaa=")?.trim()
|
||||
?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
val player = json.decodeFromString<PlayerDto>(jsonData)
|
||||
?: return@parallelCatchingFlatMap emptyList()
|
||||
val player = jsonData.parseAs<PlayerDto>()
|
||||
val url = if (player.encrypt == 2) {
|
||||
URLDecoder.decode(base64decode(player.url ?: ""), "UTF-8")
|
||||
} else {
|
||||
@@ -209,13 +207,6 @@ class Pandrama :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%d"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -225,13 +216,6 @@ class Pandrama :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%d"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,14 @@ 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.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
import kotlin.text.ifEmpty
|
||||
import kotlin.text.lowercase
|
||||
|
||||
@@ -72,7 +73,7 @@ open class PelisForte :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/todas-las-peliculas/page/$page", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val elements = document.select("#movies-a li[id*=post-]")
|
||||
val nextPage = document.select(".pagination .nav-links .current ~ a:not(.page-link)").any()
|
||||
val animeList = elements.map { element ->
|
||||
@@ -85,7 +86,7 @@ open class PelisForte :
|
||||
return AnimesPage(animeList, nextPage)
|
||||
}
|
||||
|
||||
protected open fun org.jsoup.nodes.Element.getImageUrl(): String? = if (hasAttr("srcset")) {
|
||||
protected open fun Element.getImageUrl(): String? = if (hasAttr("srcset")) {
|
||||
try {
|
||||
fetchUrls(attr("abs:srcset")).maxOrNull()
|
||||
} catch (_: Exception) {
|
||||
@@ -113,7 +114,7 @@ open class PelisForte :
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val animeDetails = SAnime.create().apply {
|
||||
title = document.selectFirst(".alg-cr .entry-header .entry-title")?.text() ?: ""
|
||||
description = document.select(".alg-cr .description").text()
|
||||
@@ -148,7 +149,7 @@ open class PelisForte :
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return document.select(".video-player iframe").parallelCatchingFlatMapBlocking { iframe ->
|
||||
val id = iframe.parent()?.attr("id")
|
||||
val idTab = document.selectFirst("[href=\"#$id\"]")?.closest(".lrt")?.attr("id")
|
||||
@@ -174,7 +175,6 @@ open class PelisForte :
|
||||
|
||||
/*--------------------------------Video extractors------------------------------------*/
|
||||
private val voeExtractor by lazy { VoeExtractor(client, headers) }
|
||||
private val okruExtractor by lazy { OkruExtractor(client) }
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||
@@ -192,7 +192,7 @@ open class PelisForte :
|
||||
val newHeaders = headers.newBuilder().add("Referer", referer).build()
|
||||
return when (matched) {
|
||||
"voe" -> voeExtractor.videosFromUrl(url, "$prefix ")
|
||||
"okru" -> okruExtractor.videosFromUrl(url, prefix, headers = newHeaders)
|
||||
"okru" -> OkruExtractor(client, newHeaders).videosFromUrl(url, prefix)
|
||||
"filemoon" -> filemoonExtractor.videosFromUrl(url, prefix = "$prefix Filemoon:")
|
||||
"uqload" -> uqloadExtractor.videosFromUrl(url, prefix)
|
||||
"mp4upload" -> mp4uploadExtractor.videosFromUrl(url, newHeaders, prefix = "$prefix ")
|
||||
@@ -284,13 +284,6 @@ open class PelisForte :
|
||||
entryValues = LANGUAGE_LIST
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -300,13 +293,6 @@ open class PelisForte :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -316,13 +302,6 @@ open class PelisForte :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class Pelisplusto : PelisPlus() {
|
||||
val decode = String(Base64.decode(it.attr("data-server"), Base64.DEFAULT))
|
||||
|
||||
val url = if (!REGEX_LINK.containsMatchIn(decode)) {
|
||||
"$baseUrl/player/${String(Base64.encode(it.attr("data-server").toByteArray(), Base64.DEFAULT))}"
|
||||
"$baseUrl/player/${Base64.encodeToString(it.attr("data-server").toByteArray(), Base64.DEFAULT)}"
|
||||
} else {
|
||||
decode
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 keiyoushi.utils.bodyString
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Request
|
||||
@@ -28,7 +29,7 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
override val name = "Samato's Den: Videos"
|
||||
override val baseUrl = "https://samatoden.blogspot.com"
|
||||
override val lang = "es"
|
||||
override val supportsLatest = true
|
||||
override val supportsLatest = false
|
||||
override val versionId = 1
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||
@@ -37,23 +38,23 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
|
||||
override fun popularAnimeRequest(page: Int): Request = GET(feedUrl(page), headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage = parseFeedPage(response.body.string())
|
||||
override fun popularAnimeParse(response: Response): AnimesPage = parseFeedPage(response.bodyString())
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET(feedUrl(page), headers)
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = parseFeedPage(response.body.string())
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET(feedUrl(page, query = query.trim()), headers)
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = parseFeedPage(response.body.string())
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = parseFeedPage(response.bodyString())
|
||||
|
||||
override fun animeDetailsRequest(anime: SAnime): Request = GET(normalizePostFeedUrl(anime.url), headers)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime = entryToAnime(parseSingleEntry(response.body.string()))
|
||||
override fun animeDetailsParse(response: Response): SAnime = entryToAnime(parseSingleEntry(response.bodyString()))
|
||||
|
||||
override fun episodeListRequest(anime: SAnime): Request = GET(normalizePostFeedUrl(anime.url), headers)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = entryToEpisodes(parseSingleEntry(response.body.string()))
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = entryToEpisodes(parseSingleEntry(response.bodyString()))
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AnimeFilterList()
|
||||
|
||||
@@ -98,9 +99,9 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
}
|
||||
}
|
||||
|
||||
val total = feed.optJSONObject("openSearch\$totalResults")?.optString("\$t")?.toIntOrNull() ?: animeList.size
|
||||
val start = feed.optJSONObject("openSearch\$startIndex")?.optString("\$t")?.toIntOrNull() ?: 1
|
||||
val perPage = feed.optJSONObject("openSearch\$itemsPerPage")?.optString("\$t")?.toIntOrNull() ?: animeList.size
|
||||
val total = feed.optJSONObject($$"openSearch$totalResults")?.optString($$"$t")?.toIntOrNull() ?: animeList.size
|
||||
val start = feed.optJSONObject($$"openSearch$startIndex")?.optString($$"$t")?.toIntOrNull() ?: 1
|
||||
val perPage = feed.optJSONObject($$"openSearch$itemsPerPage")?.optString($$"$t")?.toIntOrNull() ?: animeList.size
|
||||
val hasNextPage = (start + perPage - 1) < total
|
||||
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
@@ -120,10 +121,10 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun entryToAnime(entry: JSONObject): SAnime {
|
||||
val html = entry.optJSONObject("content")?.optString("\$t").orEmpty()
|
||||
val html = entry.optJSONObject("content")?.optString($$"$t").orEmpty()
|
||||
val anime = SAnime.create()
|
||||
anime.url = normalizePostFeedUrl(linkHref(entry, "self").orEmpty())
|
||||
anime.title = entry.optJSONObject("title")?.optString("\$t").orEmpty()
|
||||
anime.url = normalizePostFeedUrl(linkHref(entry, "self")!!)
|
||||
anime.title = entry.optJSONObject("title")?.optString($$"$t")!!
|
||||
anime.artist = extractPrimaryCredit(html)
|
||||
anime.author = extractPrimaryCredit(html)
|
||||
anime.description = extractPrimaryCredit(html)
|
||||
@@ -135,9 +136,9 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun entryToEpisodes(entry: JSONObject): List<SEpisode> {
|
||||
val animeTitle = entry.optJSONObject("title")?.optString("\$t").orEmpty()
|
||||
val html = entry.optJSONObject("content")?.optString("\$t").orEmpty()
|
||||
val uploadedAt = parseDateMillis(entry.optJSONObject("published")?.optString("\$t"))
|
||||
val animeTitle = entry.optJSONObject("title")?.optString($$"$t").orEmpty()
|
||||
val html = entry.optJSONObject("content")?.optString($$"$t").orEmpty()
|
||||
val uploadedAt = parseDateMillis(entry.optJSONObject("published")?.optString($$"$t"))
|
||||
val defaultImage = extractThumbnail(entry, html)
|
||||
val referer = linkHref(entry, "alternate") ?: "$baseUrl/"
|
||||
|
||||
@@ -225,7 +226,7 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
return playlistImage
|
||||
}
|
||||
|
||||
val mediaThumb = entry.optJSONObject("media\$thumbnail")?.optString("url")
|
||||
val mediaThumb = entry.optJSONObject($$"media$thumbnail")?.optString("url")
|
||||
return mediaThumb?.substringBefore("=s72")
|
||||
}
|
||||
|
||||
@@ -418,7 +419,7 @@ class SamatoDenVideos : AnimeHttpSource() {
|
||||
|
||||
private val HIDDEN_IMAGE_REGEX = Regex("""<img[^>]+src="([^"]+)"""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val EDITED_BY_REGEX = Regex("""^\s*edited\s+by\s*:?\s*""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val ARTIST_BY_REGEX = Regex("""^\s*artist(?:s)?\s*:?\s*""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val ARTIST_BY_REGEX = Regex("""^\s*artists?\s*:?\s*""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val EDITED_BY_CAPTURE_REGEX = Regex("""<li[^>]*>\s*Edited\s+by\s*([^<]+)</li>""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val STRONG_ARTIST_REGEX = Regex("""<strong>\s*Artists?:\s*</strong>\s*([^<]+)""", setOf(RegexOption.IGNORE_CASE))
|
||||
private val HEADING_ARTISTS_SECTION_REGEX = Regex(
|
||||
|
||||
@@ -28,11 +28,16 @@ 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.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.cryptoaes.CryptoAES
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
@@ -41,9 +46,7 @@ import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.net.URLDecoder
|
||||
import kotlin.text.RegexOption
|
||||
|
||||
open class Serieskao :
|
||||
ParsedAnimeHttpSource(),
|
||||
@@ -59,8 +62,6 @@ open class Serieskao :
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||
|
||||
companion object {
|
||||
const val PREF_QUALITY_KEY = "preferred_quality"
|
||||
const val PREF_QUALITY_DEFAULT = "1080"
|
||||
@@ -82,7 +83,7 @@ open class Serieskao :
|
||||
private const val AES_KEY = "Ak7qrvvH4WKYxV2OgaeHAEg2a5eh16vE"
|
||||
private val DATA_LINK_REGEX = """dataLink\s*=\s*([^;]+);""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
private val VIDEO_SOURCES_REGEX = """var\s+videoSources\s*=\s*\[(.+?)]\s*;""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
private val SOURCE_URL_REGEX = """['\"]([^'\"]+)['\"]""".toRegex()
|
||||
private val SOURCE_URL_REGEX = """['"]([^'"]+)['"]""".toRegex()
|
||||
}
|
||||
|
||||
override fun popularAnimeSelector(): String = "a.poster-card"
|
||||
@@ -104,7 +105,7 @@ open class Serieskao :
|
||||
override fun popularAnimeNextPageSelector(): String = "a.page-link"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val url = response.request.url.toString()
|
||||
|
||||
if (url.contains("/pelicula/")) {
|
||||
@@ -166,7 +167,7 @@ open class Serieskao :
|
||||
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val scriptData = document.select("script")
|
||||
.asSequence()
|
||||
.map(Element::data)
|
||||
@@ -184,36 +185,23 @@ open class Serieskao :
|
||||
|
||||
val referer = response.request.url.toString()
|
||||
val headers = headersBuilder().set("Referer", referer).build()
|
||||
val videos = mutableListOf<Video>()
|
||||
|
||||
videoUrls.forEach { videoUrl ->
|
||||
runCatching {
|
||||
client.newCall(GET(videoUrl, headers)).execute().use { res ->
|
||||
val body = res.body?.string().orEmpty()
|
||||
if (body.isBlank()) return@use
|
||||
return videoUrls.parallelCatchingFlatMapBlocking { videoUrl ->
|
||||
val body = client.newCall(GET(videoUrl, headers)).awaitSuccess().bodyString()
|
||||
if (body.isBlank()) return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
val bodyDoc = Jsoup.parse(body)
|
||||
val parsedLinks = extractNewExtractorLinks(bodyDoc, body)
|
||||
val bodyDoc = Jsoup.parse(body)
|
||||
val parsedLinks = extractNewExtractorLinks(bodyDoc, body)
|
||||
|
||||
if (parsedLinks.isNullOrEmpty()) {
|
||||
Log.w("SeriesKao", "Sin enlaces descifrados para $videoUrl")
|
||||
return@use
|
||||
}
|
||||
if (parsedLinks.isNullOrEmpty()) {
|
||||
Log.w("SeriesKao", "Sin enlaces descifrados para $videoUrl")
|
||||
return@parallelCatchingFlatMapBlocking emptyList()
|
||||
}
|
||||
|
||||
parsedLinks.forEach { (url, lang) ->
|
||||
runCatching {
|
||||
videos += serverVideoResolver(url, lang)
|
||||
}.onFailure {
|
||||
Log.e("SeriesKao", "Error al procesar URL de video: $url", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("SeriesKao", "Error al obtener cuerpo de videoUrl: $videoUrl", it)
|
||||
parsedLinks.parallelCatchingFlatMap { (url, lang) ->
|
||||
serverVideoResolver(url, lang)
|
||||
}
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun extractNewExtractorLinks(doc: Document, htmlContent: String): List<Pair<String, String>>? {
|
||||
@@ -231,7 +219,7 @@ open class Serieskao :
|
||||
val jsonPayload = resolveDataLink(rawExpression) ?: return null
|
||||
|
||||
val items = runCatching {
|
||||
json.decodeFromString<List<Item>>(jsonPayload)
|
||||
jsonPayload.parseAs<List<Item>>()
|
||||
}.getOrElse {
|
||||
Log.e("SeriesKao", "No se pudo parsear dataLink", it)
|
||||
return null
|
||||
@@ -328,7 +316,7 @@ open class Serieskao :
|
||||
|
||||
return runCatching {
|
||||
val decoded = Base64.decode(payload, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
val element = json.parseToJsonElement(String(decoded))
|
||||
val element = String(decoded).parseAs<JsonElement>()
|
||||
val obj = element.jsonObject
|
||||
|
||||
val link = obj["link"]?.jsonPrimitive?.contentOrNull
|
||||
@@ -361,50 +349,63 @@ open class Serieskao :
|
||||
private val streamSilkExtractor by lazy { StreamSilkExtractor(client) }
|
||||
private val vidGuardExtractor by lazy { VidGuardExtractor(client) }
|
||||
|
||||
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
|
||||
return runCatching {
|
||||
when {
|
||||
arrayOf("voe").any(url) -> voeExtractor.videosFromUrl(url, "$prefix ")
|
||||
arrayOf("ok.ru", "okru").any(url) -> okruExtractor.videosFromUrl(url, prefix)
|
||||
arrayOf("filemoon", "moonplayer").any(url) -> filemoonExtractor.videosFromUrl(url, prefix = "$prefix Filemoon:")
|
||||
!url.contains("disable") && (arrayOf("amazon", "amz").any(url)) -> {
|
||||
val body = client.newCall(GET(url)).execute().asJsoup()
|
||||
return if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
|
||||
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
|
||||
.substringAfter("shareId = \"").substringBefore("\"")
|
||||
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
|
||||
.execute().asJsoup()
|
||||
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
|
||||
val amazonApi =
|
||||
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
|
||||
.execute().asJsoup()
|
||||
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
|
||||
listOf(Video(videoUrl, "$prefix Amazon", videoUrl))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
arrayOf("uqload").any(url) -> uqloadExtractor.videosFromUrl(url, prefix)
|
||||
arrayOf("mp4upload").any(url) -> mp4uploadExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
arrayOf("wishembed", "streamwish", "strwish", "wish").any(url) -> {
|
||||
streamWishExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
|
||||
}
|
||||
arrayOf("doodstream", "dood.", "ds2play", "doods.").any(url) -> {
|
||||
val url2 = url.replace("https://doodstream.com/e/", "https://d0000d.com/e/")
|
||||
doodExtractor.videosFromUrl(url2, "$prefix DoodStream")
|
||||
}
|
||||
arrayOf("streamlare").any(url) -> streamlareExtractor.videosFromUrl(url, prefix)
|
||||
arrayOf("yourupload", "upload").any(url) -> yourUploadExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
arrayOf("burstcloud", "burst").any(url) -> burstCloudExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
arrayOf("fastream").any(url) -> fastreamExtractor.videosFromUrl(url, prefix = "$prefix Fastream:")
|
||||
arrayOf("upstream").any(url) -> upstreamExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
arrayOf("streamsilk").any(url) -> streamSilkExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamSilk:$it" })
|
||||
arrayOf("streamtape", "stp", "stape").any(url) -> streamTapeExtractor.videosFromUrl(url, quality = "$prefix StreamTape")
|
||||
arrayOf("ahvsh", "streamhide", "guccihide", "streamvid", "vidhide").any(url) -> streamHideVidExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamHideVid:$it" })
|
||||
arrayOf("vembed", "guard", "listeamed", "bembed", "vgfplay").any(url) -> vidGuardExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
else -> emptyList()
|
||||
private suspend fun serverVideoResolver(url: String, prefix: String = ""): List<Video> = when {
|
||||
arrayOf("voe").any(url) -> voeExtractor.videosFromUrl(url, "$prefix ")
|
||||
|
||||
arrayOf("ok.ru", "okru").any(url) -> okruExtractor.videosFromUrl(url, prefix)
|
||||
|
||||
arrayOf("filemoon", "moonplayer").any(url) -> filemoonExtractor.videosFromUrl(url, prefix = "$prefix Filemoon:")
|
||||
|
||||
!url.contains("disable") && (arrayOf("amazon", "amz").any(url)) -> {
|
||||
val body = client.newCall(GET(url)).awaitSuccess().useAsJsoup()
|
||||
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
|
||||
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
|
||||
.substringAfter("shareId = \"").substringBefore("\"")
|
||||
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
|
||||
.awaitSuccess().useAsJsoup()
|
||||
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
|
||||
val amazonApi =
|
||||
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
|
||||
.awaitSuccess().useAsJsoup()
|
||||
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
|
||||
listOf(Video(videoUrl, "$prefix Amazon", videoUrl))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.getOrNull() ?: emptyList()
|
||||
}
|
||||
|
||||
arrayOf("uqload").any(url) -> uqloadExtractor.videosFromUrl(url, prefix)
|
||||
|
||||
arrayOf("mp4upload").any(url) -> mp4uploadExtractor.videosFromUrl(url, headers, prefix = "$prefix ")
|
||||
|
||||
arrayOf("wishembed", "streamwish", "strwish", "wish").any(url) -> {
|
||||
streamWishExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" })
|
||||
}
|
||||
|
||||
arrayOf("doodstream", "dood.", "ds2play", "doods.").any(url) -> {
|
||||
val url2 = url.replace("https://doodstream.com/e/", "https://d0000d.com/e/")
|
||||
doodExtractor.videosFromUrl(url2, "$prefix DoodStream")
|
||||
}
|
||||
|
||||
arrayOf("streamlare").any(url) -> streamlareExtractor.videosFromUrl(url, prefix)
|
||||
|
||||
arrayOf("yourupload", "upload").any(url) -> yourUploadExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
|
||||
arrayOf("burstcloud", "burst").any(url) -> burstCloudExtractor.videoFromUrl(url, headers = headers, prefix = "$prefix ")
|
||||
|
||||
arrayOf("fastream").any(url) -> fastreamExtractor.videosFromUrl(url, prefix = "$prefix Fastream:")
|
||||
|
||||
arrayOf("upstream").any(url) -> upstreamExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
|
||||
arrayOf("streamsilk").any(url) -> streamSilkExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamSilk:$it" })
|
||||
|
||||
arrayOf("streamtape", "stp", "stape").any(url) -> streamTapeExtractor.videosFromUrl(url, quality = "$prefix StreamTape")
|
||||
|
||||
arrayOf("ahvsh", "streamhide", "guccihide", "streamvid", "vidhide").any(url) -> vidHideExtractor.videosFromUrl(url, videoNameGen = { "$prefix StreamHideVid:$it" })
|
||||
|
||||
arrayOf("vembed", "guard", "listeamed", "bembed", "vgfplay").any(url) -> vidGuardExtractor.videosFromUrl(url, prefix = "$prefix ")
|
||||
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
private fun getFirstMatch(regex: Regex, input: String): String = regex.find(input)?.groupValues?.get(1) ?: ""
|
||||
@@ -451,8 +452,7 @@ open class Serieskao :
|
||||
title = document.selectFirst("h1.m-b-5")?.text()?.ifBlank { "Sin título" } ?: "Sin título"
|
||||
thumbnail_url = document.selectFirst("div.card-body div.row div.col-sm-3 img.img-fluid")
|
||||
?.attr("src")?.replace("/w154/", "/w500/")
|
||||
?: ""
|
||||
description = document.selectFirst("div.col-sm-4 div.text-large")?.ownText() ?: ""
|
||||
description = document.selectFirst("div.col-sm-4 div.text-large")?.ownText()
|
||||
genre = document.select("div.p-v-20.p-h-15.text-center a span").joinToString { it.text() }
|
||||
status = SAnime.COMPLETED
|
||||
}
|
||||
@@ -538,13 +538,6 @@ open class Serieskao :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -554,13 +547,6 @@ open class Serieskao :
|
||||
entryValues = QUALITY_LIST
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -570,13 +556,6 @@ open class Serieskao :
|
||||
entryValues = LANGUAGE_LIST
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,17 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.cryptoaes.CryptoAES
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.tryParse
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
@@ -46,7 +47,6 @@ class SoloLatino :
|
||||
"SoloLatino",
|
||||
"https://sololatino.net",
|
||||
) {
|
||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/tendencias/page/$page")
|
||||
@@ -70,7 +70,7 @@ class SoloLatino :
|
||||
override fun popularAnimeNextPageSelector(): String = "div.pagMovidy a"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val seasonList = doc.select("div#seasons div.se-c")
|
||||
return if (seasonList.isEmpty()) {
|
||||
SEpisode.create().apply {
|
||||
@@ -119,7 +119,7 @@ class SoloLatino :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val path = response.request.url.toString()
|
||||
val path = response.use { it.request.url.toString() }
|
||||
val links = mutableListOf<Pair<String, String>>()
|
||||
|
||||
runBlocking {
|
||||
@@ -146,16 +146,9 @@ class SoloLatino :
|
||||
.findAll(result)
|
||||
.toList()
|
||||
|
||||
coroutineScope {
|
||||
val deferredResults = linkPages.map { matchResult ->
|
||||
async {
|
||||
processLinkPage(matchResult, path)
|
||||
}
|
||||
}
|
||||
deferredResults.awaitAll().forEach { newLinks ->
|
||||
links.addAll(newLinks)
|
||||
}
|
||||
}
|
||||
linkPages.parallelCatchingFlatMap { matchResult ->
|
||||
processLinkPage(matchResult, path)
|
||||
}.let(links::addAll)
|
||||
|
||||
if (links.isEmpty()) {
|
||||
handleEmptyLinks(result, links, path)
|
||||
@@ -167,7 +160,7 @@ class SoloLatino :
|
||||
}
|
||||
}
|
||||
|
||||
private fun processLinkPage(matchResult: MatchResult, path: String): List<Pair<String, String>> = try {
|
||||
private suspend fun processLinkPage(matchResult: MatchResult, path: String): List<Pair<String, String>> = try {
|
||||
val postParams = mapOf(
|
||||
"action" to "doo_player_ajax",
|
||||
"post" to (matchResult.groups[2]?.value ?: ""),
|
||||
@@ -183,7 +176,7 @@ class SoloLatino :
|
||||
emptyList()
|
||||
}
|
||||
|
||||
private fun handleEmptyLinks(result: String, links: MutableList<Pair<String, String>>, referer: String) {
|
||||
private suspend fun handleEmptyLinks(result: String, links: MutableList<Pair<String, String>>, referer: String) {
|
||||
val iframeUrl = Regex("""pframe"><iframe class="[^"]+" src="([^"]+)""").find(result)?.groups?.get(1)?.value
|
||||
iframeUrl?.let { web ->
|
||||
val newResult = httpGet(web, referer)
|
||||
@@ -195,18 +188,16 @@ class SoloLatino :
|
||||
}
|
||||
}
|
||||
|
||||
private fun httpGet(url: String, referer: String? = null): String {
|
||||
private suspend fun httpGet(url: String, referer: String? = null): String {
|
||||
val headers = headersBuilder().apply {
|
||||
referer?.let { set("Referer", it) }
|
||||
}.build()
|
||||
|
||||
val request = GET(url, headers)
|
||||
return client.newCall(request).execute().use { response ->
|
||||
response.body.string()
|
||||
}
|
||||
return client.newCall(request).awaitSuccess().bodyString()
|
||||
}
|
||||
|
||||
private fun httpPost(url: String, params: Map<String, String>, referer: String): String {
|
||||
private suspend fun httpPost(url: String, params: Map<String, String>, referer: String): String {
|
||||
val formBody = FormBody.Builder().apply {
|
||||
params.forEach { (key, value) -> add(key, value) }
|
||||
}.build()
|
||||
@@ -223,9 +214,7 @@ class SoloLatino :
|
||||
.post(formBody)
|
||||
.build()
|
||||
|
||||
return client.newCall(request).execute().use { response ->
|
||||
response.body.string()
|
||||
}
|
||||
return client.newCall(request).awaitSuccess().bodyString()
|
||||
}
|
||||
|
||||
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||
@@ -350,7 +339,7 @@ class SoloLatino :
|
||||
val jsonPayload = resolveDataLink(rawExpression) ?: return null
|
||||
|
||||
val items = runCatching {
|
||||
json.decodeFromString<List<Item>>(jsonPayload)
|
||||
jsonPayload.parseAs<List<Item>>()
|
||||
}.getOrElse {
|
||||
Log.e("SoloLatino", "No se pudo parsear dataLink", it)
|
||||
return null
|
||||
@@ -395,7 +384,7 @@ class SoloLatino :
|
||||
|
||||
return runCatching {
|
||||
val decoded = Base64.decode(payload, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
val element = json.parseToJsonElement(String(decoded))
|
||||
val element = String(decoded).parseAs<JsonElement>()
|
||||
val obj = element.jsonObject
|
||||
|
||||
val link = obj["link"]?.jsonPrimitive?.contentOrNull
|
||||
@@ -544,13 +533,6 @@ class SoloLatino :
|
||||
entryValues = SERVER_LIST
|
||||
setDefaultValue(PREF_SERVER_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
val langPref = ListPreference(screen.context).apply {
|
||||
@@ -560,13 +542,6 @@ class SoloLatino :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(langPref)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -196,15 +197,14 @@ class NeoNime :
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val hosterSelection = preferences.getStringSet(
|
||||
"hoster_selection",
|
||||
setOf("blogger", "linkbox", "okru", "yourupload", "gdriveplayer"),
|
||||
)!!
|
||||
|
||||
document.select("div.player2 > div.embed2 > div").forEach {
|
||||
val iframe = it.selectFirst("iframe") ?: return@forEach
|
||||
return document.select("div.player2 > div.embed2 > div").parallelCatchingFlatMapBlocking {
|
||||
val iframe = it.selectFirst("iframe") ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
var link = iframe.attr("data-src")
|
||||
if (!link.startsWith("http")) {
|
||||
@@ -213,19 +213,19 @@ class NeoNime :
|
||||
|
||||
when {
|
||||
hosterSelection.contains("linkbox") && link.contains("linkbox.to") -> {
|
||||
videoList.addAll(LinkBoxExtractor(client).videosFromUrl(link, it.text()))
|
||||
LinkBoxExtractor(client).videosFromUrl(link, it.text())
|
||||
}
|
||||
|
||||
hosterSelection.contains("okru") && link.contains("ok.ru") -> {
|
||||
runBlocking { videoList.addAll(OkruExtractor(client).videosFromUrl(link)) }
|
||||
OkruExtractor(client).videosFromUrl(link)
|
||||
}
|
||||
|
||||
hosterSelection.contains("yourupload") && link.contains("blogger.com") -> {
|
||||
videoList.addAll(BloggerExtractor(client).videosFromUrl(link, headers, it.text()))
|
||||
BloggerExtractor(client).videosFromUrl(link, headers, it.text())
|
||||
}
|
||||
|
||||
hosterSelection.contains("linkbox") && link.contains("yourupload.com") -> {
|
||||
videoList.addAll(YourUploadExtractor(client).videoFromUrl(link, headers, it.text(), "Original - "))
|
||||
YourUploadExtractor(client).videoFromUrl(link, headers, it.text(), "Original - ")
|
||||
}
|
||||
|
||||
hosterSelection.contains("gdriveplayer") && link.contains("neonime.fun") -> {
|
||||
@@ -239,7 +239,7 @@ class NeoNime :
|
||||
)
|
||||
val iframe = client.newCall(
|
||||
GET(link, headers = headers),
|
||||
).execute().asJsoup()
|
||||
).awaitSuccess().useAsJsoup()
|
||||
|
||||
var iframeUrl = iframe.selectFirst("iframe")!!.attr("src")
|
||||
|
||||
@@ -247,17 +247,16 @@ class NeoNime :
|
||||
iframeUrl = "https:$iframeUrl"
|
||||
}
|
||||
|
||||
when {
|
||||
iframeUrl.contains("gdriveplayer.to") -> {
|
||||
val newHeaders = headersBuilder().add("Referer", baseUrl).build()
|
||||
videoList.addAll(GdrivePlayerExtractor(client).videosFromUrl(iframeUrl, it.text(), headers = newHeaders))
|
||||
}
|
||||
if (iframeUrl.contains("gdriveplayer.to")) {
|
||||
val newHeaders = headersBuilder().add("Referer", baseUrl).build()
|
||||
GdrivePlayerExtractor(client).videosFromUrl(iframeUrl, it.text(), headers = newHeaders)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
|
||||
@@ -234,8 +234,6 @@ class Samehadaku :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBloggerVideos(server: String, link: String): List<Video> = bloggerExtractor.videosFromUrl(link, headers, server)
|
||||
|
||||
private suspend fun getVideosFromEmbed(server: String, link: String): List<Video> {
|
||||
val videoHeaders = headers.newBuilder()
|
||||
.set("User-Agent", USER_AGENT)
|
||||
@@ -266,9 +264,7 @@ class Samehadaku :
|
||||
}
|
||||
|
||||
// Blogger — now delegates entirely to BloggerExtractor
|
||||
"blogger" in link -> {
|
||||
getBloggerVideos(server, link)
|
||||
}
|
||||
"blogger" in link -> bloggerExtractor.videosFromUrl(link, headers, server)
|
||||
|
||||
else -> {
|
||||
val doc = client.newCall(GET(link, videoHeaders)).awaitSuccess().useAsJsoup()
|
||||
|
||||
@@ -21,16 +21,14 @@ 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.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -48,8 +46,6 @@ class Docchi :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
// ============================== Popular ===============================
|
||||
@@ -81,7 +77,7 @@ class Docchi :
|
||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseApiUrl/v1/series/related/$query")
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val animeArray: List<ApiSearch> = response.body.string().parseAs()
|
||||
val animeArray = response.parseAs<List<ApiSearch>>()
|
||||
val entries = animeArray.map { animeDetail ->
|
||||
SAnime.create().apply {
|
||||
title = animeDetail.title
|
||||
@@ -96,7 +92,7 @@ class Docchi :
|
||||
override fun episodeListRequest(anime: SAnime): Request = GET("$baseApiUrl/v1/episodes/count/${anime.url.substringAfterLast("/")}")
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val episodeList: List<EpisodeList> = response.body.string().parseAs()
|
||||
val episodeList = response.parseAs<List<EpisodeList>>()
|
||||
return episodeList.map { episode ->
|
||||
SEpisode.create().apply {
|
||||
name = "${episode.anime_episode_number.toInt()} Odcinek"
|
||||
@@ -113,11 +109,11 @@ class Docchi :
|
||||
// override fun animeDetailsRequest(anime: SAnime): Request = GET("$baseApiUrl/v1/series/find/${anime.url.substringAfterLast("/")}")
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val documentDocchi = client.newCall(
|
||||
GET("$baseApiUrl/v1/series/find/${response.asJsoup().location().substringAfterLast("/")}"),
|
||||
val location = response.useAsJsoup().location().substringAfterLast("/")
|
||||
val animeDetail = client.newCall(
|
||||
GET("$baseApiUrl/v1/series/find/$location"),
|
||||
).execute()
|
||||
|
||||
val animeDetail = documentDocchi.body.string().parseAs<ApiDetail>()
|
||||
.parseAs<ApiDetail>()
|
||||
val myanimeListDetail = myanimelistApi(animeDetail.mal_id)
|
||||
|
||||
return SAnime.create().apply {
|
||||
@@ -150,7 +146,7 @@ class Docchi :
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val videolist: List<VideoList> = response.body.string().parseAs()
|
||||
val videolist = response.parseAs<List<VideoList>>()
|
||||
val serverList = videolist.mapNotNull { player ->
|
||||
val sub = player.translator_title.uppercase()
|
||||
|
||||
@@ -250,10 +246,10 @@ class Docchi :
|
||||
}
|
||||
|
||||
private fun myanimelistApi(id: Int): MyAnimeListResponse {
|
||||
val document = client.newCall(
|
||||
val response = client.newCall(
|
||||
GET("https://api.jikan.moe/v4/anime/$id"),
|
||||
).execute()
|
||||
return document.body.string().parseAs<MyAnimeListResponse>()
|
||||
return response.parseAs<MyAnimeListResponse>()
|
||||
}
|
||||
|
||||
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) }
|
||||
@@ -272,13 +268,6 @@ class Docchi :
|
||||
entryValues = arrayOf("1080", "720", "480", "360")
|
||||
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()
|
||||
}
|
||||
}
|
||||
val videoServerPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_server"
|
||||
@@ -287,13 +276,6 @@ class Docchi :
|
||||
entryValues = arrayOf("cda.pl", "Dailymotion", "Mp4upload", "Sibnet", "vk.com")
|
||||
setDefaultValue("cda.pl")
|
||||
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)
|
||||
|
||||
@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -22,7 +22,6 @@ import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class OgladajAnime :
|
||||
ParsedAnimeHttpSource(),
|
||||
@@ -36,8 +35,6 @@ class OgladajAnime :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val apiHeaders = Headers.Builder()
|
||||
.set("Accept", "application/json, text/plain, */*")
|
||||
.set("Referer", "$baseUrl/")
|
||||
@@ -54,11 +51,11 @@ class OgladajAnime :
|
||||
.add("page", "$page")
|
||||
.add("search_type", "page")
|
||||
.build()
|
||||
return POST("https://ogladajanime.pl/manager.php?action=get_search", apiHeaders, body)
|
||||
return POST("$baseUrl/manager.php?action=get_search", apiHeaders, body)
|
||||
}
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val animeParse: FetchAnime = json.decodeFromString(response.body.string())
|
||||
val animeParse = response.parseAs<FetchAnime>()
|
||||
|
||||
val cleanHtml = animeParse.data.replace(Regex("[\\t\\n\\r]+"), "")
|
||||
var counter = 0
|
||||
@@ -87,7 +84,7 @@ class OgladajAnime :
|
||||
.add("page", "$page")
|
||||
.add("search_type", "new")
|
||||
.build()
|
||||
return POST("https://ogladajanime.pl/manager.php?action=get_search", apiHeaders, body)
|
||||
return POST("$baseUrl/manager.php?action=get_search", apiHeaders, body)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage = popularAnimeParse(response)
|
||||
@@ -106,7 +103,7 @@ class OgladajAnime :
|
||||
.add("search_type", "name")
|
||||
.add("search", query)
|
||||
.build()
|
||||
return POST("https://ogladajanime.pl/manager.php?action=get_search", apiHeaders, body)
|
||||
return POST("$baseUrl/manager.php?action=get_search", apiHeaders, body)
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = popularAnimeParse(response)
|
||||
@@ -172,7 +169,7 @@ class OgladajAnime :
|
||||
override fun videoListRequest(episode: SEpisode): Request = GET("$baseUrl:8443/Player/${episode.url}", apiHeaders)
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val players = json.decodeFromString<List<ApiPlayer>>(response.body.string())
|
||||
val players = response.parseAs<List<ApiPlayer>>()
|
||||
|
||||
return players.map { player ->
|
||||
val host = Regex("""https?://(?:www\.)?([^/]+)""")
|
||||
@@ -229,9 +226,7 @@ class OgladajAnime :
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString("preferred_quality", "1080")!!
|
||||
return this.sortedWith(
|
||||
compareBy(
|
||||
{ it.quality.contains(quality) },
|
||||
),
|
||||
compareBy { it.quality.contains(quality) },
|
||||
).reversed()
|
||||
}
|
||||
|
||||
@@ -243,13 +238,6 @@ class OgladajAnime :
|
||||
entryValues = arrayOf("1080", "720", "480", "360")
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ apply plugin: "kei.plugins.extension.legacy"
|
||||
dependencies {
|
||||
implementation(project(':lib:dailymotionextractor'))
|
||||
implementation(project(':lib:mp4uploadextractor'))
|
||||
implementation(project(':lib:vkextractor'))
|
||||
implementation(project(':lib:sibnetextractor'))
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import androidx.preference.SwitchPreferenceCompat
|
||||
import aniyomi.lib.dailymotionextractor.DailymotionExtractor
|
||||
import aniyomi.lib.mp4uploadextractor.Mp4uploadExtractor
|
||||
import aniyomi.lib.sibnetextractor.SibnetExtractor
|
||||
import aniyomi.lib.vkextractor.VkExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.pl.wbijam.extractors.CdaPlExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.pl.wbijam.extractors.VkExtractor
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
@@ -17,18 +17,19 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingMapNotNull
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -44,8 +45,6 @@ class Wbijam :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
companion object {
|
||||
@@ -88,15 +87,15 @@ class Wbijam :
|
||||
filters: AnimeFilterList,
|
||||
): AnimesPage = client.newCall(searchAnimeRequest(page, query, filters))
|
||||
.awaitSuccess()
|
||||
.let { response ->
|
||||
.use { response ->
|
||||
searchAnimeParse(response, query)
|
||||
}
|
||||
|
||||
private fun searchAnimeParse(response: Response, query: String): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val animes = document.select(searchAnimeSelector(query)).map { element ->
|
||||
searchAnimeFromElement(element)
|
||||
val animes = document.select(searchAnimeSelector(query)).mapNotNull { element ->
|
||||
runCatching { searchAnimeFromElement(element) }.getOrNull()
|
||||
}
|
||||
|
||||
return AnimesPage(animes, false)
|
||||
@@ -123,14 +122,14 @@ class Wbijam :
|
||||
override fun episodeListRequest(anime: SAnime): Request = GET(anime.url, headers = headers)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val episodeList = mutableListOf<SEpisode>()
|
||||
var counter = 1
|
||||
|
||||
document.select("button:not(:contains(Wychodzące)):not(:contains(Warsztat)):not(:contains(Lista anime)) + div.dropdown-content > a").forEach seasons@{ season ->
|
||||
val seasonDoc = client.newCall(
|
||||
GET(response.request.url.toString() + "/${season.attr("href")}", headers = headers),
|
||||
).execute().asJsoup()
|
||||
).execute().useAsJsoup()
|
||||
seasonDoc.select("table.lista > tbody > tr").reversed().forEach { ep ->
|
||||
val episode = SEpisode.create()
|
||||
|
||||
@@ -163,7 +162,7 @@ class Wbijam :
|
||||
scanlator
|
||||
}
|
||||
episode.episode_number = counter.toFloat()
|
||||
episode.date_upload = ep.selectFirst("td:matches(\\d+\\.\\d+\\.\\d)")?.let { parseDate(it.text()) } ?: 0L
|
||||
episode.date_upload = ep.selectFirst("td:matches(\\d+\\.\\d+\\.\\d)")?.text().let(DATE_FORMATTER::tryParse)
|
||||
val urls = ep.select("td > span[class*=link]").map {
|
||||
"https://${response.request.url.host}/${it.className().substringBefore("_link")}-${it.attr("rel")}.html"
|
||||
}
|
||||
@@ -188,7 +187,7 @@ class Wbijam :
|
||||
|
||||
episode.name = name
|
||||
episode.episode_number = counter.toFloat()
|
||||
episode.date_upload = ep.selectFirst("td:matches(\\d+\\.\\d+\\.\\d)")?.let { parseDate(it.text()) } ?: 0L
|
||||
episode.date_upload = ep.selectFirst("td:matches(\\d+\\.\\d+\\.\\d)")?.text().let(DATE_FORMATTER::tryParse)
|
||||
episode.scanlator = if (notUploaded) {
|
||||
"(Jeszcze nie przesłane) $scanlator"
|
||||
} else {
|
||||
@@ -216,27 +215,23 @@ class Wbijam :
|
||||
// ============================ Video Links =============================
|
||||
|
||||
override suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val parsed = json.decodeFromString<EpisodeType>(episode.url)
|
||||
val parsed = episode.url.parseAs<EpisodeType>()
|
||||
val serverList = mutableListOf<String>()
|
||||
|
||||
parsed.url.forEach {
|
||||
val document = client.newCall(GET(it)).execute().asJsoup()
|
||||
parsed.url.parallelCatchingMapNotNull {
|
||||
val document = client.newCall(GET(it)).awaitSuccess().useAsJsoup()
|
||||
|
||||
if (parsed.type == "single") {
|
||||
serverList.add(
|
||||
document.selectFirst("iframe")?.attr("src")
|
||||
?: document.selectFirst("span.odtwarzaj_vk")?.let { t -> "https://vk.com/video${t.attr("rel")}_${t.attr("id")}" } ?: "",
|
||||
)
|
||||
document.selectFirst("iframe")?.attr("src")
|
||||
?: document.selectFirst("span.odtwarzaj_vk")?.let { t -> "https://vk.com/video${t.attr("rel")}_${t.attr("id")}" }!!
|
||||
} else if (parsed.type == "multi") {
|
||||
document.select("table.lista > tbody > tr.lista_hover").forEach { server ->
|
||||
val urlSpan = server.selectFirst("span[class*=link]")!!
|
||||
val serverDoc = client.newCall(
|
||||
GET("https://${it.toHttpUrl().host}/${urlSpan.className().substringBefore("_link")}-${urlSpan.attr("rel")}.html"),
|
||||
).execute().asJsoup()
|
||||
serverList.add(
|
||||
serverDoc.selectFirst("iframe")?.attr("src")
|
||||
?: serverDoc.selectFirst("span.odtwarzaj_vk")?.let { t -> "https://vk.com/video${t.attr("rel")}_${t.attr("id")}" } ?: "",
|
||||
)
|
||||
).awaitSuccess().useAsJsoup()
|
||||
serverDoc.selectFirst("iframe")?.attr("src")
|
||||
?: serverDoc.selectFirst("span.odtwarzaj_vk")?.let { t -> "https://vk.com/video${t.attr("rel")}_${t.attr("id")}" }!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +251,7 @@ class Wbijam :
|
||||
}
|
||||
|
||||
serverUrl.contains("vk.com") -> {
|
||||
VkExtractor(client).getVideosFromUrl(serverUrl, headers)
|
||||
VkExtractor(client, headers).videosFromUrl(serverUrl)
|
||||
}
|
||||
|
||||
serverUrl.contains("dailymotion") -> {
|
||||
@@ -278,8 +273,6 @@ class Wbijam :
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun EpisodeType.toJsonString(): String = json.encodeToString(this)
|
||||
|
||||
@Serializable
|
||||
data class EpisodeType(
|
||||
val type: String,
|
||||
@@ -298,9 +291,6 @@ class Wbijam :
|
||||
).reversed()
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String): Long = runCatching { DATE_FORMATTER.parse(dateStr)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val videoQualityPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
@@ -309,13 +299,6 @@ class Wbijam :
|
||||
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()
|
||||
}
|
||||
}
|
||||
val videoServerPref = ListPreference(screen.context).apply {
|
||||
key = "preferred_server"
|
||||
@@ -324,34 +307,17 @@ class Wbijam :
|
||||
entryValues = arrayOf("cda.pl", "Dailymotion", "Mp4upload", "Sibnet", "vk.com")
|
||||
setDefaultValue("cda.pl")
|
||||
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 seasonViewPref = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = "preferred_season_view"
|
||||
title = "Przenieś nazwę sezonu do skanera"
|
||||
setDefaultValue(false)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val new = newValue as Boolean
|
||||
preferences.edit().putBoolean(key, new).commit()
|
||||
}
|
||||
}
|
||||
val openEndPref = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = "preferred_opening"
|
||||
title = "Usuń zakończenia i otwory"
|
||||
summary = "Usuń zakończenia i otwarcia z listy odcinków"
|
||||
setDefaultValue(false)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val new = newValue as Boolean
|
||||
preferences.edit().putBoolean(key, new).commit()
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(videoQualityPref)
|
||||
|
||||
@@ -3,25 +3,21 @@ package eu.kanade.tachiyomi.animeextension.pl.wbijam.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 eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.parallelCatchingMapNotNull
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class CdaPlExtractor(private val client: OkHttpClient) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
fun getVideosFromUrl(url: String, headers: Headers): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
suspend fun getVideosFromUrl(url: String, headers: Headers): List<Video> {
|
||||
val embedHeaders = headers.newBuilder()
|
||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
|
||||
.add("Host", url.toHttpUrl().host)
|
||||
@@ -29,18 +25,16 @@ class CdaPlExtractor(private val client: OkHttpClient) {
|
||||
|
||||
val document = client.newCall(
|
||||
GET(url, headers = embedHeaders),
|
||||
).execute().asJsoup()
|
||||
).awaitSuccess().useAsJsoup()
|
||||
|
||||
val data = json.decodeFromString<PlayerData>(
|
||||
document.selectFirst("div[player_data]")!!.attr("player_data"),
|
||||
)
|
||||
val data = document.selectFirst("div[player_data]")!!
|
||||
.attr("player_data")
|
||||
.parseAs<PlayerData>()
|
||||
|
||||
data.video.qualities.forEach { quality ->
|
||||
return data.video.qualities.asIterable().parallelCatchingMapNotNull { quality ->
|
||||
if (quality.value == data.video.quality) {
|
||||
val videoUrl = decryptFile(data.video.file)
|
||||
videoList.add(
|
||||
Video(videoUrl, "cda.pl - ${quality.key}", videoUrl),
|
||||
)
|
||||
Video(videoUrl, "cda.pl - ${quality.key}", videoUrl)
|
||||
} else {
|
||||
val jsonBody = """
|
||||
{
|
||||
@@ -64,17 +58,11 @@ class CdaPlExtractor(private val client: OkHttpClient) {
|
||||
)
|
||||
val response = client.newCall(
|
||||
POST("https://www.cda.pl/", headers = postHeaders, body = jsonBody),
|
||||
).execute()
|
||||
val parsed = json.decodeFromString<PostResponse>(
|
||||
response.body.string(),
|
||||
)
|
||||
videoList.add(
|
||||
Video(parsed.result.resp, "cda.pl - ${quality.key}", parsed.result.resp),
|
||||
)
|
||||
).awaitSuccess()
|
||||
val parsed = response.parseAs<PostResponse>()
|
||||
Video(parsed.result.resp, "cda.pl - ${quality.key}", parsed.result.resp)
|
||||
}
|
||||
}
|
||||
|
||||
return videoList
|
||||
}
|
||||
|
||||
// Credit: https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/cda.py
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pl.wbijam.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class VkExtractor(private val client: OkHttpClient) {
|
||||
fun getVideosFromUrl(url: String, headers: Headers): List<Video> {
|
||||
val videoList = mutableListOf<Video>()
|
||||
|
||||
val documentHeaders = headers.newBuilder()
|
||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
|
||||
.add("Host", "vk.com")
|
||||
.build()
|
||||
|
||||
val data = client.newCall(
|
||||
GET(url, headers = documentHeaders),
|
||||
).execute().body.string()
|
||||
|
||||
val videoRegex = """\"url(\d+)\":\"(.*?)\"""".toRegex()
|
||||
videoRegex.findAll(data).forEach {
|
||||
val quality = it.groupValues[1]
|
||||
val videoUrl = it.groupValues[2].replace("\\/", "/")
|
||||
val videoHeaders = headers.newBuilder()
|
||||
.add("Accept", "*/*")
|
||||
.add("Host", videoUrl.toHttpUrl().host)
|
||||
.add("Origin", "https://vk.com")
|
||||
.add("Referer", "https://vk.com/")
|
||||
.build()
|
||||
|
||||
videoList.add(
|
||||
Video(videoUrl, "vk.com - ${quality}p", videoUrl, headers = videoHeaders),
|
||||
)
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class Anikyuu :
|
||||
private val byseExtractor by lazy { ByseExtractor(client, headers, baseUrl) }
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val strmupExtractor by lazy { StrmupExtractor(client, headers) }
|
||||
|
||||
override suspend fun getVideoList(url: String, name: String): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $url")
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.anikyuu.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.flatMapCatching
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.Headers
|
||||
@@ -23,11 +26,12 @@ class ByseExtractor(
|
||||
) {
|
||||
|
||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
|
||||
suspend fun videosFromUrl(url: String): List<Video> {
|
||||
val id = url.split("/")[4]
|
||||
val embedUrl =
|
||||
client.newCall(GET("https://${url.toHttpUrl().host}/api/videos/$id/embed/details"))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("embed_frame_url", "")
|
||||
.substringAfter(":")
|
||||
.substringAfter('"')
|
||||
@@ -54,21 +58,21 @@ class ByseExtractor(
|
||||
set("Sec-Fetch-Storage-Access", "active")
|
||||
}.build()
|
||||
|
||||
return client.newCall(GET(playbackUrl, playbackHeader)).execute()
|
||||
return client.newCall(GET(playbackUrl, playbackHeader)).awaitSuccess()
|
||||
.parseAs<PlaybackResponseDto>()
|
||||
.let { decrypt(it.playback) }
|
||||
.substringAfter("sources")
|
||||
.substringAfter("[")
|
||||
.substringBefore("]")
|
||||
.split("},")
|
||||
.mapNotNull {
|
||||
val videoUrl = it.substringAfter("\"url\":\"", "")
|
||||
.flatMapCatching { entry ->
|
||||
val videoUrl = entry.substringAfter("\"url\":\"", "")
|
||||
.substringBefore('"')
|
||||
.takeIf(String::isNotBlank)
|
||||
?.replace("\\u0026", "&")
|
||||
?: return@mapNotNull null
|
||||
?: return@flatMapCatching emptyList()
|
||||
|
||||
return playlistUtils.extractFromHls(
|
||||
playlistUtils.extractFromHls(
|
||||
videoUrl,
|
||||
videoNameGen = { "Byse - $it" },
|
||||
)
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.anikyuu.extractors
|
||||
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class StrmupExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
|
||||
private val playlistUtils by lazy { PlaylistUtils(client) }
|
||||
fun videosFromUrl(url: String, name: String = "NOA"): List<Video> {
|
||||
|
||||
suspend fun videosFromUrl(url: String, name: String = "Strmup"): List<Video> {
|
||||
val id = url.split("/").last()
|
||||
val body = client.newCall(GET("https://strmup.to/ajax/stream?filecode=$id", headers)).execute()
|
||||
.body.string()
|
||||
val body = client.newCall(GET("https://strmup.to/ajax/stream?filecode=$id", headers))
|
||||
.awaitSuccess().bodyString()
|
||||
|
||||
return when {
|
||||
"streaming_url" in body -> {
|
||||
@@ -23,7 +26,7 @@ class StrmupExtractor(private val client: OkHttpClient, private val headers: Hea
|
||||
|
||||
playlistUtils.extractFromHls(
|
||||
videoUrl,
|
||||
videoNameGen = { "Strmup - $it" },
|
||||
videoNameGen = { "$name - $it" },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,12 +179,6 @@ class AnimeFire :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ ext {
|
||||
extClass = '.AnimeIto'
|
||||
themePkg = 'animestream'
|
||||
baseUrl = 'https://animesonline.io'
|
||||
overrideVersionCode = 2
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply plugin: "kei.plugins.extension.legacy"
|
||||
|
||||
@@ -20,14 +20,15 @@ class AnimeIto :
|
||||
|
||||
override fun videoListSelector() = "ul.tabs_videos li"
|
||||
|
||||
override fun getHosterUrl(element: Element): String {
|
||||
override suspend fun getHosterUrl(element: Element): String {
|
||||
val encodedData = element.attr("value")
|
||||
|
||||
return getHosterUrl(encodedData)
|
||||
}
|
||||
|
||||
private val animeitoExtractor by lazy { AnimeItoExtractor(client, headers) }
|
||||
override fun getVideoList(url: String, name: String): List<Video> = when {
|
||||
|
||||
override suspend fun getVideoList(url: String, name: String): List<Video> = when {
|
||||
"anidrive.click" in url -> animeitoExtractor.videosFromUrl(url)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animeito.extractors
|
||||
|
||||
import android.util.Base64
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.unpacker.Unpacker
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class AnimeItoExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
val playerDoc = client.newCall(GET(url, headers)).execute().asJsoup()
|
||||
suspend fun videosFromUrl(url: String): List<Video> {
|
||||
val playerDoc = client.newCall(GET(url, headers)).awaitSuccess().useAsJsoup()
|
||||
val encodedScript = playerDoc.selectFirst("script:containsData(AnimeiTo.Run)")
|
||||
?.data()
|
||||
|
||||
@@ -21,8 +22,7 @@ class AnimeItoExtractor(private val client: OkHttpClient, private val headers: H
|
||||
val decodedData = encodedScript.substringAfter("(").substringBefore(")")
|
||||
.replace(Regex("\"\\s*\\+\\s*\""), "") // Remove concatenation
|
||||
.replace(Regex("[^A-Za-z0-9+/=]"), "") // Remove non-base64 characters
|
||||
.let { Base64.decode(it, Base64.DEFAULT) }
|
||||
.let(::String)
|
||||
.let { String(Base64.decode(it, Base64.DEFAULT)) }
|
||||
Unpacker.unpack(decodedData).ifEmpty { return emptyList() }
|
||||
} else {
|
||||
playerDoc.selectFirst("script:containsData(const player)")?.data()
|
||||
|
||||
@@ -9,9 +9,11 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -105,7 +107,7 @@ class AnimePlay :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -113,7 +115,7 @@ class AnimePlay :
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
.run {
|
||||
when (this.uppercase()) {
|
||||
@@ -153,7 +155,7 @@ class AnimePlay :
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val body = FormBody.Builder()
|
||||
.add("action", "doo_player_ajax")
|
||||
.add("post", player.attr("data-post"))
|
||||
@@ -162,13 +164,14 @@ class AnimePlay :
|
||||
.build()
|
||||
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
@Volatile
|
||||
private var hasFetchedGenresArray = false
|
||||
|
||||
override val genreFilterHeader = "Apenas um tipo de filtro por vez"
|
||||
@@ -189,6 +192,7 @@ class AnimePlay :
|
||||
AnimeFilterList()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun fetchGenresList() {
|
||||
if (hasFetchedGenresArray || !fetchGenres) return
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animeplay.extractors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -11,20 +10,20 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import keiyoushi.utils.applicationContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UniversalExtractor(private val client: OkHttpClient) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String, origRequestHeader: Headers, name: String?): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val host = origRequestUrl.toHttpUrl().host.substringBefore(".").proper()
|
||||
@@ -35,7 +34,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
val headers = origRequestHeader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
|
||||
@@ -6,7 +6,8 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
@@ -78,7 +79,7 @@ class AnimePlayer :
|
||||
override val prefQualityEntries = prefQualityValues
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val playerUrl = document
|
||||
.selectFirst("div.playex iframe")
|
||||
?.absUrl("src")
|
||||
@@ -92,11 +93,12 @@ class AnimePlayer :
|
||||
?: "Default"
|
||||
|
||||
val url = playerUrl.queryParameter("link") ?: playerUrl.toString()
|
||||
return getVideosFromURL(url, quality)
|
||||
return runBlocking { runCatching { getVideosFromURL(url, quality) }.getOrElse { emptyList() } }
|
||||
}
|
||||
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private fun getVideosFromURL(url: String, quality: String): List<Video> = when {
|
||||
|
||||
private suspend fun getVideosFromURL(url: String, quality: String): List<Video> = when {
|
||||
"cdn.animeson.com.br" in url -> {
|
||||
listOf(
|
||||
Video(url, quality, url, headers),
|
||||
|
||||
@@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
@@ -103,7 +105,7 @@ class AnimeQ :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -111,7 +113,7 @@ class AnimeQ :
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
.run {
|
||||
when (this.uppercase()) {
|
||||
@@ -151,18 +153,19 @@ class AnimeQ :
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val type = player.attr("data-type")
|
||||
val id = player.attr("data-post")
|
||||
val num = player.attr("data-nume")
|
||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
@Volatile
|
||||
private var hasFetchedGenresArray = false
|
||||
|
||||
override val genreFilterHeader = "Apenas um tipo de filtro por vez"
|
||||
@@ -183,6 +186,7 @@ class AnimeQ :
|
||||
AnimeFilterList()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun fetchGenresList() {
|
||||
if (hasFetchedGenresArray || !fetchGenres) return
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animeq.extractors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -9,22 +8,22 @@ import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||
import keiyoushi.utils.applicationContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UniversalExtractor(private val client: OkHttpClient) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String, origRequestHeader: Headers, name: String?): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val host = origRequestUrl.toHttpUrl().host.substringBefore(".").proper()
|
||||
@@ -35,7 +34,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
val headers = origRequestHeader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
|
||||
@@ -15,9 +15,11 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -104,7 +106,7 @@ class AnimesDigital :
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -113,7 +115,7 @@ class AnimesDigital :
|
||||
}
|
||||
|
||||
private val searchToken by lazy {
|
||||
client.newCall(GET("$baseUrl/animes-legendados-online")).execute().asJsoup()
|
||||
client.newCall(GET("$baseUrl/animes-legendados-online")).execute().useAsJsoup()
|
||||
.selectFirst("div.menu_filter_box")!!
|
||||
.attr("data-secury")
|
||||
}
|
||||
@@ -193,24 +195,20 @@ class AnimesDigital :
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val doc = getRealDoc(response.asJsoup())
|
||||
val pagination = doc.selectFirst("ul.content-pagination")
|
||||
return if (pagination != null) {
|
||||
val episodes = mutableListOf<SEpisode>()
|
||||
episodes += doc.select(episodeListSelector()).map(::episodeFromElement)
|
||||
val lastPage =
|
||||
doc.selectFirst("ul.content-pagination > li:nth-last-child(2) > a")!!.text()
|
||||
.toInt()
|
||||
for (i in 2..lastPage) {
|
||||
val doc = getRealDoc(response.useAsJsoup())
|
||||
val episodes = mutableListOf<SEpisode>()
|
||||
episodes += doc.select(episodeListSelector()).map(::episodeFromElement)
|
||||
val lastPage =
|
||||
doc.selectFirst("ul.content-pagination > li:nth-last-child(2) > a")?.text()
|
||||
?.toIntOrNull()
|
||||
episodes += lastPage?.let { 2..it }
|
||||
?.parallelCatchingFlatMapBlocking { i ->
|
||||
val request = GET(doc.location() + "/page/$i", headers)
|
||||
val res = client.newCall(request).execute()
|
||||
val pageDoc = res.asJsoup()
|
||||
episodes += pageDoc.select(episodeListSelector()).map(::episodeFromElement)
|
||||
}
|
||||
episodes
|
||||
} else {
|
||||
doc.select(episodeListSelector()).map(::episodeFromElement)
|
||||
}
|
||||
val res = client.newCall(request).awaitSuccess()
|
||||
val pageDoc = res.useAsJsoup()
|
||||
pageDoc.select(episodeListSelector()).map(::episodeFromElement)
|
||||
} ?: emptyList()
|
||||
return episodes
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = "div.item_ep > a"
|
||||
@@ -225,12 +223,10 @@ class AnimesDigital :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val player = response.asJsoup().selectFirst("div#player")!!
|
||||
return player.select("div.tab-video").flatMap { div ->
|
||||
div.select(videoListSelector()).flatMap { element ->
|
||||
runCatching {
|
||||
videosFromElement(element)
|
||||
}.onFailure { it.printStackTrace() }.getOrElse { emptyList() }
|
||||
val player = response.useAsJsoup().selectFirst("div#player")!!
|
||||
return player.select("div.tab-video").parallelCatchingFlatMapBlocking { div ->
|
||||
div.select(videoListSelector()).parallelCatchingFlatMap { element ->
|
||||
videosFromElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,16 +234,16 @@ class AnimesDigital :
|
||||
private val protectorExtractor by lazy { ProtectorExtractor(client) }
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
|
||||
private fun videosFromElement(element: Element): List<Video> = when (element.tagName()) {
|
||||
private suspend fun videosFromElement(element: Element): List<Video> = when (element.tagName()) {
|
||||
"iframe" -> {
|
||||
val url = element.absUrl("data-lazy-src").ifEmpty { element.absUrl("src") }
|
||||
when {
|
||||
"blogger.com" in url -> bloggerExtractor.videosFromUrl(url, headers)
|
||||
else -> {
|
||||
client.newCall(GET(url, headers)).execute()
|
||||
.asJsoup()
|
||||
client.newCall(GET(url, headers)).awaitSuccess()
|
||||
.useAsJsoup()
|
||||
.select(videoListSelector())
|
||||
.flatMap(::videosFromElement)
|
||||
.parallelCatchingFlatMap(::videosFromElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,12 +273,6 @@ class AnimesDigital :
|
||||
entryValues = PREF_QUALITY_ENTRIES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
@@ -297,7 +287,7 @@ class AnimesDigital :
|
||||
private fun getRealDoc(document: Document): Document = document.selectFirst("div.subitem > a:contains(menu)")?.let { link ->
|
||||
client.newCall(GET(link.attr("href")))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
} ?: document
|
||||
|
||||
private fun Element.getInfo(key: String): String? = selectFirst("div.info:has(span:containsOwn($key))")?.run {
|
||||
|
||||
@@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilter.TriState
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
@@ -14,6 +14,7 @@ class AnimesDigitalFilters(
|
||||
) {
|
||||
private var error = false
|
||||
|
||||
@Volatile
|
||||
private lateinit var filterList: AnimeFilterList
|
||||
|
||||
fun filterInitialized(): Boolean = this::filterList.isInitialized
|
||||
@@ -40,8 +41,8 @@ class AnimesDigitalFilters(
|
||||
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
|
||||
.groupBy { it.first } // group by state
|
||||
.let { dict ->
|
||||
val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
|
||||
val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
|
||||
val included = dict[TriState.STATE_INCLUDE]?.map { it.second }.orEmpty()
|
||||
val excluded = dict[TriState.STATE_EXCLUDE]?.map { it.second }.orEmpty()
|
||||
listOf(included, excluded)
|
||||
}
|
||||
|
||||
@@ -66,13 +67,14 @@ class AnimesDigitalFilters(
|
||||
AnimeFilterList(AnimeFilter.Header("Aperte 'Redefinir' para tentar mostrar os filtros"))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun fetchFilters() {
|
||||
if (!filterInitialized()) {
|
||||
runCatching {
|
||||
error = false
|
||||
val document = client.newCall(GET("$baseUrl/animes-legendados-online"))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
filterList = filtersParse(document)
|
||||
}.onFailure {
|
||||
error = true
|
||||
|
||||
@@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.animeextension.pt.animesdigital.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -10,11 +11,11 @@ import okhttp3.OkHttpClient
|
||||
private const val HOST = "https://sabornutritivo.com"
|
||||
|
||||
class ProtectorExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String): List<Video> {
|
||||
suspend fun videosFromUrl(url: String): List<Video> {
|
||||
val fixedUrl = if (!url.startsWith("https")) "https:$url" else url
|
||||
val token = fixedUrl.toHttpUrl().queryParameter("token")!!
|
||||
val headers = Headers.headersOf("cookie", "token=$token;")
|
||||
val doc = client.newCall(GET("$HOST/social.php", headers)).execute().asJsoup()
|
||||
val doc = client.newCall(GET("$HOST/social.php", headers)).awaitSuccess().useAsJsoup()
|
||||
val videoHeaders = Headers.headersOf("referer", doc.location())
|
||||
val iframeUrl = doc.selectFirst("iframe")!!.attr("src").trim()
|
||||
return listOf(Video(iframeUrl, "Animes Digital", iframeUrl, videoHeaders))
|
||||
|
||||
@@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
@@ -104,7 +106,7 @@ class AnimesDrive :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -112,7 +114,7 @@ class AnimesDrive :
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
.run {
|
||||
when (this.uppercase()) {
|
||||
@@ -153,18 +155,19 @@ class AnimesDrive :
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val type = player.attr("data-type")
|
||||
val id = player.attr("data-post")
|
||||
val num = player.attr("data-nume")
|
||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
@Volatile
|
||||
private var hasFetchedGenresArray = false
|
||||
|
||||
override val genreFilterHeader = "Apenas um tipo de filtro por vez"
|
||||
@@ -185,6 +188,7 @@ class AnimesDrive :
|
||||
AnimeFilterList()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun fetchGenresList() {
|
||||
if (hasFetchedGenresArray || !fetchGenres) return
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesdrive.extractors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -11,20 +10,20 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import keiyoushi.utils.applicationContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UniversalExtractor(private val client: OkHttpClient) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String, origRequestHeader: Headers, name: String?): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val host = origRequestUrl.toHttpUrl().host.substringBefore(".").proper()
|
||||
@@ -35,7 +34,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
val headers = origRequestHeader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
|
||||
@@ -8,4 +8,5 @@ apply plugin: "kei.plugins.extension.legacy"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:bloggerextractor'))
|
||||
implementation(project(':lib:playlistutils'))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesgames
|
||||
|
||||
import aniyomi.lib.bloggerextractor.BloggerExtractor
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
@@ -10,8 +11,9 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -85,7 +87,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -102,7 +104,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
|
||||
private val searchToken by lazy {
|
||||
client.newCall(GET("$baseUrl/lista-de-animes", headers)).execute()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
.selectFirst("div.menu_filter_box")!!
|
||||
.attr("data-secury")
|
||||
}
|
||||
@@ -134,14 +136,14 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
return POST("$baseUrl/func/listanime", body = body, headers = headers)
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage = runCatching {
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val data = response.parseAs<SearchResponseDto>()
|
||||
val animes = data.results.map(Jsoup::parse)
|
||||
.mapNotNull { it.selectFirst(searchAnimeSelector()) }
|
||||
.map(::searchAnimeFromElement)
|
||||
val hasNext = data.total_page > data.page
|
||||
AnimesPage(animes, hasNext)
|
||||
}.getOrElse { AnimesPage(emptyList(), false) }
|
||||
return AnimesPage(animes, hasNext)
|
||||
}
|
||||
|
||||
override fun searchAnimeSelector() = "section.animeItem > a"
|
||||
|
||||
@@ -180,7 +182,7 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = getRealDoc(response.asJsoup())
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = getRealDoc(response.useAsJsoup())
|
||||
.select(episodeListSelector())
|
||||
.map(::episodeFromElement)
|
||||
.reversed()
|
||||
@@ -198,19 +200,21 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
|
||||
// ============================ Video Links =============================
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val url = doc.selectFirst("div.Link > a")
|
||||
?.attr("href")
|
||||
?: return emptyList()
|
||||
|
||||
val playerDoc = client.newCall(GET(url, headers)).execute()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
|
||||
val iframe = playerDoc.selectFirst("iframe")
|
||||
return when {
|
||||
iframe != null -> {
|
||||
bloggerExtractor.videosFromUrl(iframe.attr("src"), headers)
|
||||
runBlocking { bloggerExtractor.videosFromUrl(iframe.attr("src"), headers) }
|
||||
}
|
||||
|
||||
else -> parseDefaultVideo(playerDoc)
|
||||
@@ -227,18 +231,11 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
.replace("\\", "")
|
||||
|
||||
return when {
|
||||
playlistUrl.endsWith("m3u8") -> {
|
||||
val separator = "#EXT-X-STREAM-INF:"
|
||||
client.newCall(GET(playlistUrl, headers)).execute()
|
||||
.body.string()
|
||||
.substringAfter(separator)
|
||||
.split(separator)
|
||||
.map {
|
||||
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p"
|
||||
val videoUrl = it.substringAfter("\n").substringBefore("\n")
|
||||
Video(videoUrl, quality, videoUrl)
|
||||
}
|
||||
}
|
||||
playlistUrl.endsWith("m3u8") -> playlistUtils.extractFromHls(
|
||||
playlistUrl = playlistUrl,
|
||||
masterHeaders = headers,
|
||||
videoHeaders = headers,
|
||||
)
|
||||
|
||||
else -> listOf(Video(playlistUrl, "Default", playlistUrl, headers))
|
||||
}
|
||||
@@ -255,9 +252,9 @@ class AnimesGames : ParsedAnimeHttpSource() {
|
||||
private fun getRealDoc(document: Document): Document {
|
||||
if (!document.location().contains("/video/")) return document
|
||||
|
||||
return document.selectFirst("div.linksEP > a:has(li.episodio)")?.let {
|
||||
client.newCall(GET(it.attr("href"), headers)).execute()
|
||||
.asJsoup()
|
||||
return document.selectFirst("div.linksEP > a:has(li.episodio)")?.let { link: Element ->
|
||||
client.newCall(GET(link.attr("href"), headers)).execute()
|
||||
.useAsJsoup()
|
||||
} ?: document
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ object AnimesGamesFilters {
|
||||
.map { filter -> filter.state to options.find { it.first == filter.name }!!.second }
|
||||
.groupBy { it.first } // group by state
|
||||
.let { dict ->
|
||||
val included = dict.get(TriState.STATE_INCLUDE)?.map { it.second }.orEmpty()
|
||||
val excluded = dict.get(TriState.STATE_EXCLUDE)?.map { it.second }.orEmpty()
|
||||
val included = dict[TriState.STATE_INCLUDE]?.map { it.second }.orEmpty()
|
||||
val excluded = dict[TriState.STATE_EXCLUDE]?.map { it.second }.orEmpty()
|
||||
listOf(included, excluded)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
extName = 'AnimesOnlineCC'
|
||||
extName = 'Animes Online CC'
|
||||
extClass = '.AnimesOnlineCC'
|
||||
themePkg = 'dooplay'
|
||||
baseUrl = 'https://animesonlinecc.to'
|
||||
|
||||
@@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
@@ -57,7 +57,7 @@ class AnimesOnlineCC :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("#playex iframe")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -67,11 +67,11 @@ class AnimesOnlineCC :
|
||||
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val url = player.attr("src")
|
||||
|
||||
val id = player.parent()!!.attr("id")
|
||||
var language =
|
||||
val language =
|
||||
player.ownerDocument()!!
|
||||
.selectFirst("a.options[href=\"#$id\"]")
|
||||
?.text()
|
||||
@@ -98,12 +98,6 @@ class AnimesOnlineCC :
|
||||
entryValues = PREF_LANGUAGE_VALUES
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(videoLanguagePref)
|
||||
@@ -1,16 +1,18 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesgratis
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesonlinecloud
|
||||
|
||||
import aniyomi.lib.bloggerextractor.BloggerExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.pt.animesgratis.extractors.UniversalExtractor
|
||||
import eu.kanade.tachiyomi.animeextension.pt.animesonlinecloud.extractors.UniversalExtractor
|
||||
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.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
@@ -103,7 +105,7 @@ class AnimesOnlineCloud :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -111,7 +113,7 @@ class AnimesOnlineCloud :
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val name = player.selectFirst("span.title")!!.text()
|
||||
.run {
|
||||
when (this.uppercase()) {
|
||||
@@ -152,18 +154,19 @@ class AnimesOnlineCloud :
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val type = player.attr("data-type")
|
||||
val id = player.attr("data-post")
|
||||
val num = player.attr("data-nume")
|
||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
@Volatile
|
||||
private var hasFetchedGenresArray = false
|
||||
|
||||
override val genreFilterHeader = "Apenas um tipo de filtro por vez"
|
||||
@@ -184,6 +187,7 @@ class AnimesOnlineCloud :
|
||||
AnimeFilterList()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun fetchGenresList() {
|
||||
if (hasFetchedGenresArray || !fetchGenres) return
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesgratis.extractors
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesonlinecloud.extractors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -11,21 +10,20 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import keiyoushi.utils.applicationContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.getValue
|
||||
|
||||
class UniversalExtractor(private val client: OkHttpClient) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String, origRequestHeader: Headers, name: String?): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val host = origRequestUrl.toHttpUrl().host.substringBefore(".").proper()
|
||||
@@ -36,7 +34,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
val headers = origRequestHeader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
@@ -12,9 +12,9 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -92,7 +92,7 @@ class AnimesOnlineVip :
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class AnimesOnlineVip :
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = getRealDoc(response.asJsoup())
|
||||
override fun episodeListParse(response: Response): List<SEpisode> = getRealDoc(response.useAsJsoup())
|
||||
.select(episodeListSelector())
|
||||
.map(::episodeFromElement)
|
||||
.reversed()
|
||||
@@ -148,7 +148,7 @@ class AnimesOnlineVip :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
return document.select("#video source,div.post-video iframe")
|
||||
.parallelCatchingFlatMapBlocking {
|
||||
@@ -158,7 +158,7 @@ class AnimesOnlineVip :
|
||||
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
|
||||
private fun getVideosFromURL(url: String): List<Video> = when {
|
||||
private suspend fun getVideosFromURL(url: String): List<Video> = when {
|
||||
"assistonapi.link" in url -> bloggerExtractor.videosFromUrl(url, headers)
|
||||
else -> listOf(
|
||||
Video(url, "Default", videoUrl = url, headers),
|
||||
@@ -180,12 +180,6 @@ class AnimesOnlineVip :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
@@ -205,7 +199,7 @@ class AnimesOnlineVip :
|
||||
if (menu != null) {
|
||||
val originalUrl = menu.parent()!!.attr("href")
|
||||
val response = client.newCall(GET(originalUrl, headers)).execute()
|
||||
return response.asJsoup()
|
||||
return response.useAsJsoup()
|
||||
}
|
||||
|
||||
return document
|
||||
|
||||
@@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
@@ -148,7 +148,7 @@ class AnimeCore : AnimeHttpSource() {
|
||||
private val episodeToAnimeUrlRegex by lazy { Regex("""/watch/([^/]+)-episodio-\d+/?""") }
|
||||
|
||||
override fun relatedAnimeListParse(response: Response): List<SAnime> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val script = document.selectFirst("script:containsData(current_post_data_id)") ?: return emptyList()
|
||||
val animeId = regexId.find(script.data())?.groupValues?.get(1) ?: return emptyList()
|
||||
val recommendedResponseDto = client.newCall(
|
||||
@@ -183,18 +183,18 @@ class AnimeCore : AnimeHttpSource() {
|
||||
|
||||
// =========================== Anime Details ============================
|
||||
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
|
||||
val document = getRealDoc(response.asJsoup())
|
||||
val document = getRealDoc(response.useAsJsoup())
|
||||
|
||||
setUrlWithoutDomain(document.location())
|
||||
thumbnail_url = document.selectFirst("img.wp-post-image")?.attr("abs:src")
|
||||
title = document.selectFirst("title")!!.text().cleanTitle()
|
||||
document.selectFirst("title")?.text()?.let { title = it.cleanTitle() }
|
||||
genre = document.select("div.flex a.hover\\:text-white").joinToString { it.text() }
|
||||
description = document.selectFirst("section p")?.text()
|
||||
}
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = getRealDoc(response.asJsoup())
|
||||
val document = getRealDoc(response.useAsJsoup())
|
||||
val animeId = document.selectFirst("#seasonContent")!!.attr("data-season")
|
||||
|
||||
return client.newCall(
|
||||
@@ -211,12 +211,12 @@ class AnimeCore : AnimeHttpSource() {
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("div.episode-player-box iframe")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val url = player.attr("abs:src")
|
||||
|
||||
return when {
|
||||
@@ -235,7 +235,7 @@ class AnimeCore : AnimeHttpSource() {
|
||||
private fun getRealDoc(document: Document): Document {
|
||||
val menu = document.selectFirst("div.anime-information h4 a") ?: return document
|
||||
val originalUrl = menu.attr("abs:href").ifBlank { return document }
|
||||
return client.newCall(GET(originalUrl, headers)).execute().use { it.asJsoup() }
|
||||
return client.newCall(GET(originalUrl, headers)).execute().useAsJsoup()
|
||||
}
|
||||
|
||||
private fun String.cleanTitle(): String = this.replace(titleCleanRegex, "")
|
||||
|
||||
@@ -9,8 +9,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
@@ -88,7 +90,7 @@ class AnimesROLL :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
@@ -99,10 +101,10 @@ class AnimesROLL :
|
||||
private val anrollOnlineExtractor by lazy { AnrollOnlineExtractor(client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val fullName = player.selectFirst("span.title")!!.text()
|
||||
val realName = fullName.substringAfter("(").substringBefore(")")
|
||||
val url = getPlayerUrl(player) ?: return emptyList()
|
||||
val url = getPlayerUrl(player)
|
||||
Log.d(tag, "Fetching videos from: $url")
|
||||
|
||||
var videos: List<Video> = when {
|
||||
@@ -124,7 +126,7 @@ class AnimesROLL :
|
||||
return videos
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val body = FormBody.Builder()
|
||||
.add("action", "doo_player_ajax")
|
||||
.add("post", player.attr("data-post"))
|
||||
@@ -133,7 +135,7 @@ class AnimesROLL :
|
||||
.build()
|
||||
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||
.execute().body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
@@ -158,11 +160,6 @@ class AnimesROLL :
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private fun Element.tryGetAttr(vararg attributeKeys: String): String? {
|
||||
val attributeKey = attributeKeys.first { hasAttr(it) }
|
||||
return attr(attributeKey)
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(videoSortPrefKey, videoSortPrefDefault)!!
|
||||
return sortedWith(
|
||||
|
||||
@@ -2,16 +2,17 @@ package eu.kanade.tachiyomi.animeextension.pt.animesroll.extractors
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.jsunpacker.JsUnpacker
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class AnrollOnlineExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
|
||||
val doc = client.newCall(GET(url)).execute().asJsoup()
|
||||
suspend fun videosFromUrl(url: String, prefix: String = ""): List<Video> {
|
||||
val doc = client.newCall(GET(url)).awaitSuccess().useAsJsoup()
|
||||
|
||||
val script = doc.selectFirst("script:containsData(eval):containsData(p,a,c,k,e,d)")?.data()
|
||||
?.let(JsUnpacker::unpackAndCombine)
|
||||
@@ -26,11 +27,15 @@ class AnrollOnlineExtractor(private val client: OkHttpClient) {
|
||||
val now = System.currentTimeMillis()
|
||||
val apiUrl = "https://${url.toHttpUrl().host}/api?$kaken&_=$now"
|
||||
|
||||
return client.newCall(GET(apiUrl)).execute().parseAs<Response>().sources.map { source ->
|
||||
return client.newCall(GET(apiUrl)).awaitSuccess().parseAs<Response>().sources.map { source ->
|
||||
val videoUrl = source.file
|
||||
val quality = source.label
|
||||
val videoName = listOfNotNull(
|
||||
prefix.takeIf { it.isNotBlank() },
|
||||
quality,
|
||||
).joinToString(" - ")
|
||||
|
||||
Video(videoUrl, "$prefix: $quality ".trim(), videoUrl)
|
||||
Video(videoUrl, videoName, videoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.animesroll.extractors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
@@ -11,20 +10,20 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import keiyoushi.utils.applicationContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UniversalExtractor(private val client: OkHttpClient) {
|
||||
private val tag by lazy { javaClass.simpleName }
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Synchronized
|
||||
fun videosFromUrl(origRequestUrl: String, origRequestHeader: Headers, name: String?): List<Video> {
|
||||
Log.d(tag, "Fetching videos from: $origRequestUrl")
|
||||
val host = origRequestUrl.toHttpUrl().host.substringBefore(".").proper()
|
||||
@@ -35,7 +34,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
val headers = origRequestHeader.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||
|
||||
handler.post {
|
||||
val newView = WebView(context)
|
||||
val newView = WebView(applicationContext)
|
||||
webView = newView
|
||||
with(newView.settings) {
|
||||
javaScriptEnabled = true
|
||||
@@ -93,7 +92,7 @@ class UniversalExtractor(private val client: OkHttpClient) {
|
||||
resultUrl,
|
||||
"$prefix: MP4",
|
||||
resultUrl,
|
||||
Headers.Companion.headersOf("referer", origRequestUrl),
|
||||
Headers.headersOf("referer", origRequestUrl),
|
||||
).let(::listOf)
|
||||
}
|
||||
else -> emptyList()
|
||||
|
||||
@@ -13,10 +13,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -107,7 +107,7 @@ class Anitube :
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -178,11 +178,11 @@ class Anitube :
|
||||
override fun episodeListSelector() = "div.animepag_episodios_item > a"
|
||||
|
||||
override fun episodeListParse(response: Response) = buildList {
|
||||
var doc = getRealDoc(response.asJsoup())
|
||||
var doc = getRealDoc(response.useAsJsoup())
|
||||
do {
|
||||
if (isNotEmpty()) {
|
||||
val path = doc.selectFirst(popularAnimeNextPageSelector())!!.attr("href")
|
||||
doc = client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
|
||||
doc = client.newCall(GET(baseUrl + path, headers)).execute().useAsJsoup()
|
||||
}
|
||||
doc.select(episodeListSelector())
|
||||
.map(::episodeFromElement)
|
||||
@@ -207,7 +207,7 @@ class Anitube :
|
||||
private val anitubeExtractor by lazy { AnitubeExtractor(headers, client, preferences) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val videoLinks = document
|
||||
.select("div.video_container > a, div.playerContainer > a")
|
||||
@@ -239,12 +239,6 @@ class Anitube :
|
||||
entryValues = PREF_QUALITY_ENTRIES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
// Auth Code
|
||||
@@ -274,7 +268,7 @@ class Anitube :
|
||||
return document.selectFirst("div.controles_ep > a[href]:has(i.spr.listaEP)")
|
||||
?.let {
|
||||
val path = it.attr("href")
|
||||
client.newCall(GET(baseUrl + path, headers)).execute().asJsoup()
|
||||
client.newCall(GET(baseUrl + path, headers)).execute().useAsJsoup()
|
||||
} ?: document
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.util.Log
|
||||
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 eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -25,12 +27,6 @@ class AnitubeExtractor(
|
||||
val videoUrl: String,
|
||||
)
|
||||
|
||||
private data class VideoInfo(
|
||||
val path: String,
|
||||
val url: String,
|
||||
val quality: String,
|
||||
)
|
||||
|
||||
private fun buildApiHeaders(referer: String): Headers = headers.newBuilder()
|
||||
.set("Referer", "https://${referer.toHttpUrl().host}/")
|
||||
.add("Accept", "*/*")
|
||||
@@ -49,13 +45,13 @@ class AnitubeExtractor(
|
||||
|
||||
private fun normalizeLink(link: String): String = if (link.startsWith("//")) "https:$link" else link
|
||||
|
||||
private fun fetchPlayerInfo(
|
||||
private suspend fun fetchPlayerInfo(
|
||||
link: String,
|
||||
linkHeaders: Headers,
|
||||
): PlayerInfo {
|
||||
val finalLink = normalizeLink(link)
|
||||
val response = client.newCall(GET(finalLink, headers = linkHeaders)).execute()
|
||||
val docLink = response.asJsoup()
|
||||
val response = client.newCall(GET(finalLink, headers = linkHeaders)).awaitSuccess()
|
||||
val docLink = response.useAsJsoup()
|
||||
|
||||
// Handle meta refresh redirect
|
||||
docLink.selectFirst("meta[http-equiv=refresh]")?.attr("content")
|
||||
@@ -73,7 +69,7 @@ class AnitubeExtractor(
|
||||
val newLink = data
|
||||
.substringAfter("redirectUrl = `")
|
||||
.substringBefore("`")
|
||||
.replace("\${token}", finalLink.toHttpUrl().queryParameter("t") ?: "")
|
||||
.replace($$"${token}", finalLink.toHttpUrl().queryParameter("t") ?: "")
|
||||
val newHeaders = linkHeaders.newBuilder().set("Referer", finalLink).build()
|
||||
Log.d(tag, "Redirecting using JavaScript to $newLink")
|
||||
return fetchPlayerInfo(newLink, newHeaders)
|
||||
@@ -90,7 +86,7 @@ class AnitubeExtractor(
|
||||
throw Exception("Configure para o novo domínio: $newLink")
|
||||
}
|
||||
|
||||
val referer = docLink.location() ?: link
|
||||
val referer = docLink.location()
|
||||
|
||||
val playerUrl = docLink.selectFirst("iframe")?.attr("src")!!
|
||||
|
||||
@@ -104,7 +100,7 @@ class AnitubeExtractor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun fetchVideoToken(playerInfo: PlayerInfo): String? {
|
||||
private suspend fun fetchVideoToken(playerInfo: PlayerInfo): String? {
|
||||
Log.d(tag, "Fetching new auth code")
|
||||
|
||||
val (adsUrl, adblockUrl) = try {
|
||||
@@ -117,7 +113,7 @@ class AnitubeExtractor(
|
||||
playerInfo.playerUrl,
|
||||
headers = newHeaders,
|
||||
),
|
||||
).execute().body.string()
|
||||
).awaitSuccess().bodyString()
|
||||
|
||||
val ads = ADS_URL_REGEX.find(body)?.groups?.get(1)?.value
|
||||
?.takeIf { it.startsWith("http") }
|
||||
@@ -126,7 +122,7 @@ class AnitubeExtractor(
|
||||
val adblock = body.substringAfter("$.post", "")
|
||||
.substringAfter("'")
|
||||
.substringBefore("'")
|
||||
?.takeIf { it.startsWith("http") }
|
||||
.takeIf { it.startsWith("http") }
|
||||
?: throw IllegalStateException("No valid ADBLOCK URL found")
|
||||
|
||||
ads to adblock
|
||||
@@ -135,7 +131,7 @@ class AnitubeExtractor(
|
||||
"https://widgets.outbrain.com/outbrain.js" to "https://ads.anitube.vip/adblock2.php"
|
||||
}
|
||||
|
||||
val adsContent = client.newCall(GET(adsUrl)).execute().body.string()
|
||||
val adsContent = client.newCall(GET(adsUrl)).awaitSuccess().bodyString()
|
||||
|
||||
val videoUrl = playerInfo.playerUrl.toHttpUrl().queryParameter("url")!!
|
||||
|
||||
@@ -149,15 +145,14 @@ class AnitubeExtractor(
|
||||
val apiHeaders = buildApiHeaders(playerInfo.referer)
|
||||
|
||||
val response = client.newCall(POST(adblockUrl, headers = apiHeaders, body = body))
|
||||
.execute()
|
||||
.body.string()
|
||||
.awaitSuccess().bodyString()
|
||||
|
||||
val token = extractPublicidadeCode(response).ifBlank { "undefined" }
|
||||
|
||||
return try {
|
||||
val response = client.newCall(
|
||||
GET("$adblockUrl?token=$token&url=$videoUrl", headers = apiHeaders),
|
||||
).execute().body.string()
|
||||
).awaitSuccess().bodyString()
|
||||
|
||||
val videoToken = extractPublicidadeCode(response)
|
||||
|
||||
@@ -173,7 +168,7 @@ class AnitubeExtractor(
|
||||
}
|
||||
}
|
||||
|
||||
fun getVideosFromUrl(url: String, quality: String): List<Video> {
|
||||
suspend fun getVideosFromUrl(url: String, quality: String): List<Video> {
|
||||
val playerInfo = fetchPlayerInfo(url, headers)
|
||||
val videoToken = fetchVideoToken(playerInfo)
|
||||
return if (!videoToken.isNullOrBlank()) {
|
||||
|
||||
@@ -4,14 +4,14 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -23,9 +23,7 @@ class BetterAnimeIo :
|
||||
) {
|
||||
private val contentUrl = "$baseUrl/animes"
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val extractor by lazy { BetterAnimeIoExtractor(client, json) }
|
||||
private val extractor by lazy { BetterAnimeIoExtractor(client) }
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularAnimeSelector() = "div#featured-titles article.item div.poster"
|
||||
@@ -75,12 +73,12 @@ class BetterAnimeIo :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val players = document.select("ul#playeroptionsul li")
|
||||
return players.parallelCatchingFlatMapBlocking(::getPlayerVideos)
|
||||
}
|
||||
|
||||
private fun getPlayerVideos(player: Element): List<Video> {
|
||||
private suspend fun getPlayerVideos(player: Element): List<Video> {
|
||||
val url = getPlayerUrl(player)
|
||||
if (url.isEmpty()) return emptyList()
|
||||
|
||||
@@ -93,20 +91,18 @@ class BetterAnimeIo :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val type = player.attr("data-type")
|
||||
val id = player.attr("data-post")
|
||||
val num = player.attr("data-nume")
|
||||
return try {
|
||||
client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num", headers))
|
||||
.execute()
|
||||
.use { response ->
|
||||
response.body.string()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
.awaitSuccess()
|
||||
.bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,18 @@ package eu.kanade.tachiyomi.animeextension.pt.betteranimeio
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import kotlinx.serialization.json.Json
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class BetterAnimeIoExtractor(
|
||||
private val client: OkHttpClient,
|
||||
private val json: Json,
|
||||
) {
|
||||
fun extractVideosFromApi(encodedSource: String): List<Video> {
|
||||
suspend fun extractVideosFromApi(encodedSource: String): List<Video> {
|
||||
val apiUrl = "$API_URL$encodedSource"
|
||||
return try {
|
||||
val response = client.newCall(GET(apiUrl)).execute()
|
||||
val responseBody = response.body.string()
|
||||
val videoResponse = json.decodeFromString<VideoApiResponse>(responseBody)
|
||||
val videoResponse = client.newCall(GET(apiUrl)).awaitSuccess()
|
||||
.parseAs<VideoApiResponse>()
|
||||
|
||||
if (videoResponse.status == "success") {
|
||||
videoResponse.play.map { video ->
|
||||
@@ -23,7 +22,7 @@ class BetterAnimeIoExtractor(
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ 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.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONArray
|
||||
@@ -32,7 +35,7 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
override fun popularAnimeRequest(page: Int): Request = GET(baseUrl, headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val animes = document
|
||||
.select("div.ultimosAnimesHomeItem")
|
||||
@@ -69,7 +72,7 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
): Request = GET("$baseUrl/busca?busca=$query&page=$page", headers)
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val animes = document
|
||||
.select("div.ultimosAnimesHomeItem")
|
||||
@@ -100,7 +103,7 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
|
||||
// Details
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
return SAnime.create().apply {
|
||||
title = document.selectFirst(".tituloPage h1")
|
||||
@@ -159,7 +162,7 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
episode_number = episodeNumber ?: 0f
|
||||
date_upload = element.selectFirst(".lancaster_episodio_info_data")?.text().let(dateFormatter::tryParse)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -182,8 +185,7 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
}
|
||||
|
||||
val responsePage = client.newCall(GET(pageUrl, headers)).execute()
|
||||
val document = responsePage.asJsoup()
|
||||
responsePage.close()
|
||||
val document = responsePage.useAsJsoup()
|
||||
|
||||
val pageEpisodes = document.select("div.ultimosEpisodiosHomeItem")
|
||||
if (pageEpisodes.isEmpty()) break
|
||||
@@ -222,56 +224,50 @@ class DattebayoBR : AnimeHttpSource() {
|
||||
|
||||
// Videos
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videos = mutableListOf<Video>()
|
||||
val document = response.useAsJsoup()
|
||||
val episodeUrl = response.request.url.toString()
|
||||
|
||||
// 1. Select only the tabs that exist in HTML. This prevents the code from attempting to process “id 2” if it does not exist in AbasBox.
|
||||
val activeAbas = document.select("div.AbasBox div.Aba")
|
||||
|
||||
activeAbas.forEach { aba ->
|
||||
return activeAbas.parallelCatchingFlatMapBlocking { aba ->
|
||||
val abaType = aba.attr("aba-type")
|
||||
val qualityName = aba.text().trim()
|
||||
val qualityName = aba.text()
|
||||
|
||||
// 2. Locate the player container corresponding to that tab.
|
||||
val container = document.getElementById(abaType) ?: return@forEach
|
||||
val container = document.getElementById(abaType) ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
// 3. Extract the specific script from this container.
|
||||
val scriptData = container.selectFirst("script")?.data() ?: ""
|
||||
val vidRegex = """var vid\s*=\s*['"](.*?)['"]""".toRegex()
|
||||
val urlQualidade = vidRegex.find(scriptData)?.groupValues?.get(1) ?: return@forEach
|
||||
val urlQualidade = vidRegex.find(scriptData)?.groupValues?.get(1) ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
try {
|
||||
val encodedUrl = java.net.URLEncoder.encode(urlQualidade, "UTF-8")
|
||||
val adUrl = "https://ads.animeyabu.net?url=$encodedUrl"
|
||||
val encodedUrl = java.net.URLEncoder.encode(urlQualidade, "UTF-8")
|
||||
val adUrl = "https://ads.animeyabu.net?url=$encodedUrl"
|
||||
|
||||
val adHeaders = headersBuilder()
|
||||
.add("Referer", episodeUrl)
|
||||
.add("Origin", "https://www.dattebayo-br.com")
|
||||
.build()
|
||||
val adHeaders = headersBuilder()
|
||||
.add("Referer", episodeUrl)
|
||||
.add("Origin", "https://www.dattebayo-br.com")
|
||||
.build()
|
||||
|
||||
val adRequest = Request.Builder().url(adUrl).headers(adHeaders).build()
|
||||
val adResponse = client.newCall(adRequest).execute()
|
||||
val adBody = adResponse.body?.string() ?: ""
|
||||
adResponse.close()
|
||||
val adRequest = Request.Builder().url(adUrl).headers(adHeaders).build()
|
||||
val adResponse = client.newCall(adRequest).awaitSuccess()
|
||||
val adBody = adResponse.bodyString()
|
||||
|
||||
if (adBody.contains("publicidade")) {
|
||||
val jsonArray = JSONArray(adBody)
|
||||
if (jsonArray.length() > 0) {
|
||||
val obj = jsonArray.getJSONObject(0)
|
||||
val assinatura = obj.optString("publicidade", "")
|
||||
if (adBody.contains("publicidade")) {
|
||||
val jsonArray = JSONArray(adBody)
|
||||
if (jsonArray.length() > 0) {
|
||||
val obj = jsonArray.getJSONObject(0)
|
||||
val assinatura = obj.optString("publicidade", "")
|
||||
|
||||
if (assinatura.isNotBlank()) {
|
||||
val urlFinal = urlQualidade + assinatura
|
||||
videos.add(Video(urlFinal, qualityName, urlFinal, headers = adHeaders))
|
||||
}
|
||||
if (assinatura.isNotBlank()) {
|
||||
val urlFinal = urlQualidade + assinatura
|
||||
return@parallelCatchingFlatMapBlocking Video(urlFinal, qualityName, urlFinal, headers = adHeaders).let(::listOf)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Silent log so as not to interrupt the search for other qualities.
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return videos.sortedByDescending { it.quality }
|
||||
.sortedByDescending { it.quality }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.funanimetv
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Base64
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
@@ -18,9 +17,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
@@ -29,9 +29,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.security.MessageDigest
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -51,11 +48,7 @@ class FunAnimeTV :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder().apply {
|
||||
set("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 16; M2007J20CG Build/BP3A.250905.014)")
|
||||
@@ -213,8 +206,7 @@ class FunAnimeTV :
|
||||
|
||||
val request = POST(apiUrl, headers, form)
|
||||
|
||||
val data = client
|
||||
.newCall(request)
|
||||
val data = client.newCall(request)
|
||||
.execute()
|
||||
.getByArrayKey<List<SingleVideoItemDto>>()
|
||||
.first()
|
||||
@@ -311,13 +303,6 @@ class FunAnimeTV :
|
||||
entryValues = PREF_QUALITY_ENTRIES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
@@ -327,13 +312,6 @@ class FunAnimeTV :
|
||||
entryValues = PREF_LANGUAGE_VALUES
|
||||
setDefaultValue(PREF_LANGUAGE_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
@@ -383,7 +361,7 @@ class FunAnimeTV :
|
||||
}
|
||||
}
|
||||
|
||||
val jsonString = json.encodeToString(JsonObject.serializer(), jsonObject)
|
||||
val jsonString = jsonObject.toJsonString()
|
||||
|
||||
val base64Data = Base64.encodeToString(
|
||||
jsonString.toByteArray(Charsets.UTF_8),
|
||||
@@ -410,8 +388,7 @@ class FunAnimeTV :
|
||||
|
||||
val request = POST("$baseUrl/valid_g.php", headers, form)
|
||||
|
||||
val appDetails = client
|
||||
.newCall(request)
|
||||
val appDetails = client.newCall(request)
|
||||
.execute()
|
||||
.getByArrayKey<List<GetAppDetailsResponse>>(NAME_INIT)
|
||||
.first()
|
||||
|
||||
@@ -14,7 +14,7 @@ object FunAnimeTVFilters {
|
||||
fun toQueryPart() = vals[state].second
|
||||
}
|
||||
|
||||
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
|
||||
open class CheckBoxFilterList(name: String, pairs: Array<Pair<String, String>>) : AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
|
||||
|
||||
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
|
||||
|
||||
@@ -51,26 +51,26 @@ object FunAnimeTVFilters {
|
||||
}
|
||||
|
||||
private object FunAnimeTVFiltersData {
|
||||
private val SELECT = Pair("<Selecione>", "")
|
||||
private val SELECT = "<Selecione>" to ""
|
||||
|
||||
val GENRES_LIST = arrayOf(
|
||||
Pair("", "Todos"),
|
||||
Pair("aventura", "Aventura"),
|
||||
Pair("ação", "Ação"),
|
||||
Pair("comédia", "Comédia"),
|
||||
Pair("drama", "Drama"),
|
||||
Pair("ecchi", "Ecchi"),
|
||||
Pair("esportes", "Esportes"),
|
||||
Pair("fantasia", "Fantasia"),
|
||||
Pair("mecha", "Mecha"),
|
||||
Pair("mistério", "Mistério"),
|
||||
Pair("música", "Música"),
|
||||
Pair("romance", "Romance"),
|
||||
Pair("sci-fi", "Sci-Fi"),
|
||||
Pair("shoujo", "Shoujo"),
|
||||
Pair("shounen", "Shounen"),
|
||||
Pair("slice of life", "Slice of Life"),
|
||||
Pair("sobrenatural", "Sobrenatural"),
|
||||
SELECT,
|
||||
"Aventura" to "aventura",
|
||||
"Ação" to "ação",
|
||||
"Comédia" to "comédia",
|
||||
"Drama" to "drama",
|
||||
"Ecchi" to "ecchi",
|
||||
"Esportes" to "esportes",
|
||||
"Fantasia" to "fantasia",
|
||||
"Mecha" to "mecha",
|
||||
"Mistério" to "mistério",
|
||||
"Música" to "música",
|
||||
"Romance" to "romance",
|
||||
"Sci-Fi" to "sci-fi",
|
||||
"Shoujo" to "shoujo",
|
||||
"Shounen" to "shounen",
|
||||
"Slice of Life" to "slice of life",
|
||||
"Sobrenatural" to "sobrenatural",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.goyabu
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Base64
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
@@ -14,9 +13,11 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -25,8 +26,6 @@ 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.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -46,9 +45,7 @@ class Goyabu :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", baseUrl)
|
||||
@@ -91,7 +88,7 @@ class Goyabu :
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val animes = document.select(latestUpdatesSelector())
|
||||
.map(::latestUpdatesFromElement)
|
||||
|
||||
@@ -112,17 +109,33 @@ class Goyabu :
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeFilterList,
|
||||
): AnimesPage = if (query.startsWith(PREFIX_SEARCH)) {
|
||||
val path = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/$path"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
} else {
|
||||
super.getSearchAnime(page, query, filters)
|
||||
): AnimesPage {
|
||||
if (query.startsWith("https://")) {
|
||||
val url = query.toHttpUrl()
|
||||
if (url.host != baseUrl.toHttpUrl().host) {
|
||||
throw Exception("Unsupported url")
|
||||
}
|
||||
val searchQuery = if (url.pathSegments.size > 1) {
|
||||
"${url.pathSegments[0]}/${url.pathSegments[1]}"
|
||||
} else {
|
||||
url.pathSegments.getOrNull(0)?.takeIf(String::isNotBlank)
|
||||
?: throw Exception("Unsupported url")
|
||||
}
|
||||
return getSearchAnime(page, "${PREFIX_SEARCH}$searchQuery", filters)
|
||||
}
|
||||
|
||||
if (query.startsWith(PREFIX_SEARCH)) {
|
||||
val path = query.removePrefix(PREFIX_SEARCH)
|
||||
return client.newCall(GET("$baseUrl/$path"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
}
|
||||
|
||||
return super.getSearchAnime(page, query, filters)
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -164,7 +177,7 @@ class Goyabu :
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = getRealDoc(response.asJsoup())
|
||||
val document = getRealDoc(response.useAsJsoup())
|
||||
val script = document.selectFirst("script:containsData(const allEpisodes)")
|
||||
?: return emptyList()
|
||||
|
||||
@@ -174,7 +187,7 @@ class Goyabu :
|
||||
.substringBefore(";")
|
||||
.trim()
|
||||
|
||||
val episodes = json.decodeFromString<List<EpisodeDto>>(jsonString)
|
||||
val episodes = jsonString.parseAs<List<EpisodeDto>>(json)
|
||||
return episodes.reversed().map { it.toSEpisode() }
|
||||
}
|
||||
|
||||
@@ -184,7 +197,7 @@ class Goyabu :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
return document.select("[data-blogger-url-encrypted]")
|
||||
.parallelCatchingFlatMapBlocking {
|
||||
@@ -196,7 +209,8 @@ class Goyabu :
|
||||
}
|
||||
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
private fun getVideosFromURL(url: String): List<Video> = when {
|
||||
|
||||
private suspend fun getVideosFromURL(url: String): List<Video> = when {
|
||||
"blogger.com" in url -> bloggerExtractor.videosFromUrl(url, headers)
|
||||
else -> emptyList()
|
||||
}
|
||||
@@ -216,12 +230,6 @@ class Goyabu :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
@@ -241,7 +249,7 @@ class Goyabu :
|
||||
if (menu != null) {
|
||||
val originalUrl = menu.parent()!!.attr("href")
|
||||
val response = client.newCall(GET(originalUrl, headers)).execute()
|
||||
return response.asJsoup()
|
||||
return response.useAsJsoup()
|
||||
}
|
||||
|
||||
return document
|
||||
@@ -271,7 +279,7 @@ class Goyabu :
|
||||
name = "Episódio $episodio" + if (episodeName.isNotEmpty()) " - $episodeName" else ""
|
||||
episode_number = episodio.toFloatOrNull() ?: 1F
|
||||
date_upload = DATE_FORMATTER.tryParse(update.trim())
|
||||
scanlator = "Áudio: $audio"
|
||||
scanlator = audio?.takeIf { it.isNotBlank() }?.let { "Áudio: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,11 @@ 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.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -101,10 +100,10 @@ class HentaisTube :
|
||||
val filtered = animeList.applyFilterParams(params)
|
||||
val results = filtered.chunked(30).toList()
|
||||
val hasNextPage = results.size > page
|
||||
val currentPage = if (results.size == 0) {
|
||||
emptyList<SAnime>()
|
||||
val currentPage = if (results.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
results.get(page - 1).map {
|
||||
results[page - 1].map {
|
||||
SAnime.create().apply {
|
||||
title = it.title.substringBefore("- Episódios")
|
||||
url = "/" + it.url
|
||||
@@ -118,7 +117,7 @@ class HentaisTube :
|
||||
override fun getFilterList(): AnimeFilterList = HentaisTubeFilters.FILTER_LIST
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -157,16 +156,16 @@ class HentaisTube :
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> = response.asJsoup().select(videoListSelector())
|
||||
override fun videoListParse(response: Response): List<Video> = response.useAsJsoup().select(videoListSelector())
|
||||
.parallelCatchingFlatMapBlocking {
|
||||
client.newCall(GET(it.attr("src"), headers)).await().let { res ->
|
||||
extractVideosFromIframe(res.asJsoup())
|
||||
}
|
||||
client.newCall(GET(it.attr("src"), headers))
|
||||
.awaitSuccess()
|
||||
.let { response -> extractVideosFromIframe(response.useAsJsoup()) }
|
||||
}
|
||||
|
||||
private val bloggerExtractor by lazy { BloggerExtractor(client) }
|
||||
|
||||
private fun extractVideosFromIframe(iframe: Document): List<Video> {
|
||||
private suspend fun extractVideosFromIframe(iframe: Document): List<Video> {
|
||||
val url = iframe.location()
|
||||
return when {
|
||||
url.contains("/hd.php") -> {
|
||||
@@ -183,7 +182,7 @@ class HentaisTube :
|
||||
|
||||
url.contains("/player.php") -> {
|
||||
val ahref = iframe.selectFirst("a")!!.attr("href")
|
||||
val internal = client.newCall(GET(ahref, headers)).execute().asJsoup()
|
||||
val internal = client.newCall(GET(ahref, headers)).awaitSuccess().useAsJsoup()
|
||||
val videoUrl = internal.selectFirst("video > source")!!.attr("src")
|
||||
listOf(Video(videoUrl, "Alternativo", videoUrl, headers))
|
||||
}
|
||||
|
||||
@@ -6,3 +6,7 @@ ext {
|
||||
}
|
||||
|
||||
apply plugin: "kei.plugins.extension.legacy"
|
||||
|
||||
dependencies {
|
||||
implementation(project(':lib:bloggerextractor'))
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class BloggerExtractor(private val client: OkHttpClient) {
|
||||
fun videosFromUrl(url: String, headers: Headers, suffix: String = ""): List<Video> {
|
||||
return client.newCall(GET(url, headers)).execute()
|
||||
.body.string()
|
||||
.takeIf { !it.contains("errorContainer") }
|
||||
.let { it ?: return emptyList() }
|
||||
.substringAfter("\"streams\":[")
|
||||
.substringBefore("]")
|
||||
.split("},")
|
||||
.mapNotNull {
|
||||
val videoUrl = it.substringAfter("\"play_url\":\"").substringBefore('"')
|
||||
.takeIf(String::isNotBlank)
|
||||
?: return@mapNotNull null
|
||||
val format = it.substringAfter("\"format_id\":").substringBefore('}')
|
||||
val quality = when (format) {
|
||||
"7" -> "240p"
|
||||
"18" -> "360p"
|
||||
"22" -> "720p"
|
||||
"37" -> "1080p"
|
||||
else -> "Unknown"
|
||||
}
|
||||
Video(videoUrl, "Blogger - $quality $suffix".trimEnd(), videoUrl, headers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.meusanimes
|
||||
|
||||
import aniyomi.lib.bloggerextractor.BloggerExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
@@ -7,15 +8,14 @@ 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.util.asJsoup
|
||||
import okhttp3.OkHttpClient
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
@@ -24,20 +24,10 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
override val lang = "pt-BR"
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient = OkHttpClient()
|
||||
|
||||
// RegEx patterns to extract video player URLs
|
||||
private val playerLegRegex = """ "player_leg"\s*:\s*"(https://www\.blogger\.com/video\.g\?token=[^"]+)""".toRegex()
|
||||
private val playerDubRegex = """ "player_dub"\s*:\s*"(https://www\.blogger\.com/video\.g\?token=[^"]+)""".toRegex()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||||
.add("Referer", baseUrl)
|
||||
|
||||
private val dateFormat by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||
}
|
||||
|
||||
// Requests: Popular anime request
|
||||
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/populares?page=$page", headers)
|
||||
|
||||
@@ -53,7 +43,7 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
// Parse Lists: Parse popular anime list
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val animes = document.select("div.grid > div > a[href^=\"/anime/\"]")
|
||||
.map { element ->
|
||||
SAnime.create().apply {
|
||||
@@ -78,7 +68,7 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
// Parse search results from API
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val body = response.body.string()
|
||||
val body = response.bodyString()
|
||||
val json = JSONObject(body)
|
||||
|
||||
val data = json.optJSONArray("data") ?: return AnimesPage(emptyList(), false)
|
||||
@@ -175,7 +165,7 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
// Main anime details parser
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val json = extractAnimeData(document)
|
||||
?: return parseAnimeFromMeta(document)
|
||||
|
||||
@@ -243,7 +233,7 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
// Episodes: Parse episode list from JSON data
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val json = extractAnimeData(document) ?: return emptyList()
|
||||
val episodes = json.optJSONArray("Episode") ?: return emptyList()
|
||||
|
||||
@@ -264,7 +254,7 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
|
||||
// Parse video list from episode page
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val html = response.body.string()
|
||||
val html = response.bodyString()
|
||||
val videoList = mutableListOf<Video>()
|
||||
val extractor = BloggerExtractor(client)
|
||||
|
||||
@@ -290,16 +280,17 @@ class MeusAnimes : AnimeHttpSource() {
|
||||
if (url.isEmpty() || !url.contains("blogger.com")) return
|
||||
|
||||
runCatching {
|
||||
extractor.videosFromUrl(url, googleHeaders).forEach { video ->
|
||||
videoList.add(
|
||||
Video(
|
||||
video.url,
|
||||
"$prefix: ${video.quality}",
|
||||
video.videoUrl,
|
||||
googleHeaders,
|
||||
),
|
||||
)
|
||||
}
|
||||
runBlocking { extractor.videosFromUrl(url, googleHeaders) }
|
||||
.forEach { video ->
|
||||
videoList.add(
|
||||
Video(
|
||||
video.url,
|
||||
"$prefix: ${video.quality}",
|
||||
video.videoUrl,
|
||||
googleHeaders,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import eu.kanade.tachiyomi.animeextension.pt.smartanimes.extractors.SmartAnimesE
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.animestream.AnimeStream
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Response
|
||||
|
||||
@@ -27,7 +27,7 @@ class SmartAnimes :
|
||||
override fun videoListSelector() = ".dlbox li:not(.head)"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val items = response.asJsoup().select(videoListSelector())
|
||||
val items = response.useAsJsoup().select(videoListSelector())
|
||||
|
||||
return items.parallelCatchingFlatMapBlocking { element ->
|
||||
val name = element.selectFirst(".q")!!.text().trim()
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
package eu.kanade.tachiyomi.animeextension.pt.smartanimes.extractors
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SendNowExtractor(private val client: OkHttpClient, private val headers: Headers) {
|
||||
private val context: Application by injectLazy()
|
||||
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
suspend fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
// Client hints from: https://github.com/keiyoushi/extensions-source/blob/8f70beda06a70f84c79d793367fbdf6b9ea09b5a/src/pt/mangastop/src/eu/kanade/tachiyomi/extension/pt/mangastop/ClientHintsInterceptor.kt#L27
|
||||
val userAgent = headers["User-Agent"]
|
||||
?: "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36"
|
||||
@@ -55,7 +50,7 @@ class SendNowExtractor(private val client: OkHttpClient, private val headers: He
|
||||
set("User-Agent", userAgent)
|
||||
}.build()
|
||||
|
||||
val document = client.newCall(GET(url, newHeaders)).execute().asJsoup()
|
||||
val document = client.newCall(GET(url, newHeaders)).awaitSuccess().useAsJsoup()
|
||||
|
||||
val source = document.selectFirst("source") ?: return emptyList()
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import aniyomi.lib.googledriveplayerextractor.GoogleDrivePlayerExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.FormBody
|
||||
@@ -18,8 +20,8 @@ class SmartAnimesExtractor(private val client: OkHttpClient, private val headers
|
||||
private val gdriveExtractor by lazy { GoogleDrivePlayerExtractor(client, headers) }
|
||||
private val sendNowExtractor by lazy { SendNowExtractor(client, headers) }
|
||||
|
||||
fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val content = client.newCall(GET(url, headers)).execute().body.string()
|
||||
suspend fun videosFromUrl(url: String, name: String): List<Video> {
|
||||
val content = client.newCall(GET(url, headers)).awaitSuccess().bodyString()
|
||||
|
||||
val item = content.substringAfter("var item = ", "")
|
||||
.substringBefore(";")
|
||||
@@ -47,7 +49,7 @@ class SmartAnimesExtractor(private val client: OkHttpClient, private val headers
|
||||
|
||||
val sourceUrl =
|
||||
noRedirectClient.newCall(POST(options.soralink_ajaxurl, newHeaders, formBody))
|
||||
.execute().use { it.header("location") }
|
||||
.awaitSuccess().use { it.header("location") }
|
||||
?: return emptyList()
|
||||
|
||||
return when {
|
||||
|
||||
@@ -9,7 +9,9 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
@@ -69,17 +71,32 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: AnimeFilterList,
|
||||
): AnimesPage = if (query.startsWith(PREFIX_SEARCH)) {
|
||||
val path = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/$path"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
} else {
|
||||
super.getSearchAnime(page, query, filters)
|
||||
): AnimesPage {
|
||||
if (query.startsWith("https://")) {
|
||||
val url = query.toHttpUrl()
|
||||
if (url.host != baseUrl.toHttpUrl().host) {
|
||||
throw Exception("Unsupported url")
|
||||
}
|
||||
val searchQuery = if (url.pathSegments.size > 1) {
|
||||
"${url.pathSegments[0]}/${url.pathSegments[1]}"
|
||||
} else {
|
||||
url.pathSegments.getOrNull(0)?.takeIf(String::isNotBlank)
|
||||
?: throw Exception("Unsupported url")
|
||||
}
|
||||
return getSearchAnime(page, "${PREFIX_SEARCH}$searchQuery", filters)
|
||||
}
|
||||
|
||||
if (query.startsWith(PREFIX_SEARCH)) {
|
||||
val path = query.removePrefix(PREFIX_SEARCH)
|
||||
return client.newCall(GET("$baseUrl/$path"))
|
||||
.awaitSuccess()
|
||||
.use(::searchAnimeByIdParse)
|
||||
}
|
||||
return super.getSearchAnime(page, query, filters)
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -123,7 +140,7 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = getRealDoc(response.asJsoup())
|
||||
val document = getRealDoc(response.useAsJsoup())
|
||||
val script = document.selectFirst("script[type=\"application/ld+json\"]")
|
||||
?: return emptyList()
|
||||
|
||||
@@ -141,7 +158,7 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
val jsonString = script.data().trim()
|
||||
.let(::sanitizeLdJsonNames)
|
||||
|
||||
val anime = json.decodeFromString<AnimeDto>(jsonString)
|
||||
val anime = jsonString.parseAs<AnimeDto>(json)
|
||||
|
||||
val episodes = anime.containsSeason.flatMap { season ->
|
||||
season.episode.map { episode ->
|
||||
@@ -162,7 +179,7 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val id = document.selectFirst("[data-embed]")?.attr("data-embed") ?: return emptyList()
|
||||
|
||||
@@ -171,7 +188,7 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
.build()
|
||||
|
||||
val request = POST("$baseUrl/ajax/embed", headers, formBody)
|
||||
val body = client.newCall(request).execute().body.string()
|
||||
val body = client.newCall(request).execute().bodyString()
|
||||
|
||||
val videoUrl = body.substringAfterLast("playerEmbed", "")
|
||||
.substringAfter("\"")
|
||||
@@ -193,7 +210,7 @@ class SushiAnimes : ParsedAnimeHttpSource() {
|
||||
if (menu != null) {
|
||||
val originalUrl = menu.attr("href")
|
||||
val response = client.newCall(GET(originalUrl, headers)).execute()
|
||||
return response.asJsoup()
|
||||
return response.useAsJsoup()
|
||||
}
|
||||
|
||||
return document
|
||||
|
||||
@@ -17,27 +17,17 @@ class SushiAnimesUrlActivity : Activity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 0) {
|
||||
val searchQuery = if (pathSegments.size > 1) {
|
||||
"${pathSegments[0]}/${pathSegments[1]}"
|
||||
} else {
|
||||
pathSegments[0]
|
||||
}
|
||||
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", "${SushiAnimes.PREFIX_SEARCH}$searchQuery")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.ANIMESEARCH"
|
||||
putExtra("query", intent.data.toString())
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e(tag, "could not parse uri from intent $intent")
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(tag, "Unable to launch activity", e)
|
||||
}
|
||||
|
||||
finish()
|
||||
|
||||
@@ -25,12 +25,13 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.toJsonRequestBody
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -111,7 +112,7 @@ class TRAnimeIzle :
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -187,7 +188,7 @@ class TRAnimeIzle :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val episodeId = doc.selectFirst("input#EpisodeId")!!.attr("value")
|
||||
|
||||
val allFansubs = PREF_FANSUB_SELECTION_ENTRIES
|
||||
@@ -198,7 +199,7 @@ class TRAnimeIzle :
|
||||
// Filter-out non-chosen fansubs that were included in the fansub selection preference.
|
||||
// This way we prevent excluding unknown/non-added fansubs.
|
||||
.filter { it.text() in chosenFansubs || it.text() !in allFansubs }
|
||||
.flatMap { fansub ->
|
||||
.parallelCatchingFlatMapBlocking { fansub ->
|
||||
val fansubId = fansub.attr("data-fid")
|
||||
val fansubName = fansub.text()
|
||||
|
||||
@@ -206,12 +207,12 @@ class TRAnimeIzle :
|
||||
.toJsonRequestBody()
|
||||
|
||||
client.newCall(POST("$baseUrl/api/fansubSources", headers, body))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
.awaitSuccess()
|
||||
.useAsJsoup()
|
||||
.select("li.sourceBtn")
|
||||
.toList()
|
||||
.filter { it.selectFirst("p")?.ownText().orEmpty() in chosenHosts }
|
||||
.parallelCatchingFlatMapBlocking {
|
||||
.parallelCatchingFlatMap {
|
||||
getVideosFromId(it.attr("data-id"))
|
||||
}
|
||||
.map {
|
||||
@@ -299,13 +300,6 @@ class TRAnimeIzle :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
MultiSelectListPreference(screen.context).apply {
|
||||
@@ -316,11 +310,6 @@ class TRAnimeIzle :
|
||||
entryValues = it
|
||||
setDefaultValue(it.toSet())
|
||||
}
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
|
||||
EditTextPreference(screen.context).apply {
|
||||
@@ -346,11 +335,6 @@ class TRAnimeIzle :
|
||||
entries = PREF_HOSTS_SELECTION_ENTRIES
|
||||
entryValues = PREF_HOSTS_SELECTION_ENTRIES
|
||||
setDefaultValue(PREF_HOSTS_SELECTION_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
extName = 'Hanime1'
|
||||
extName = 'Hanime1.me'
|
||||
extClass = '.Hanime1'
|
||||
extVersionCode = 6
|
||||
isNsfw = true
|
||||
|
||||
@@ -13,22 +13,22 @@ 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.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@@ -42,27 +42,26 @@ enum class FilterUpdateState {
|
||||
class Hanime1 :
|
||||
AnimeHttpSource(),
|
||||
ConfigurableAnimeSource {
|
||||
override val baseUrl: String
|
||||
get() = "https://hanime1.me"
|
||||
override val lang: String
|
||||
get() = "zh"
|
||||
override val name: String
|
||||
get() = "Hanime1.me"
|
||||
override val baseUrl = "https://hanime1.me"
|
||||
override val lang = "zh"
|
||||
override val name = "Hanime1.me"
|
||||
override val supportsLatest: Boolean
|
||||
get() = true
|
||||
|
||||
override val client =
|
||||
network.client.newBuilder().addInterceptor(::checkFiltersInterceptor).build()
|
||||
override val client = network.client.newBuilder()
|
||||
.addInterceptor(::checkFiltersInterceptor)
|
||||
.build()
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
private val json by injectLazy<Json>()
|
||||
|
||||
private var filterUpdateState = FilterUpdateState.NONE
|
||||
|
||||
private val uploadDateFormat: SimpleDateFormat by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
return SAnime.create().apply {
|
||||
genre = doc.select(".single-video-tag").not("[data-toggle]").eachText().joinToString()
|
||||
author = doc.select("#video-artist-name").text()
|
||||
@@ -91,7 +90,7 @@ class Hanime1 :
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val jsoup = response.asJsoup()
|
||||
val jsoup = response.useAsJsoup()
|
||||
val nodes = jsoup.select("#playlist-scroll").first()!!.select(">div")
|
||||
return nodes.mapIndexed { index, element ->
|
||||
SEpisode.create().apply {
|
||||
@@ -104,15 +103,14 @@ class Hanime1 :
|
||||
val timeStr =
|
||||
jsoup.select("div.video-description-panel > div:first-child").text()
|
||||
.split(" ").last()
|
||||
date_upload =
|
||||
runCatching { uploadDateFormat.parse(timeStr)?.time }.getOrNull() ?: 0L
|
||||
date_upload = uploadDateFormat.tryParse(timeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val sourceList = doc.select("video source")
|
||||
val preferQuality = preferences.getString(PREF_KEY_VIDEO_QUALITY, DEFAULT_QUALITY)
|
||||
return sourceList.map {
|
||||
@@ -143,7 +141,7 @@ class Hanime1 :
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val jsoup = response.asJsoup()
|
||||
val jsoup = response.useAsJsoup()
|
||||
val nodes = jsoup.select(".horizontal-row .video-item-container:not(:has(a.video-link[target]))")
|
||||
val list = if (nodes.isNotEmpty()) {
|
||||
nodes.map {
|
||||
@@ -242,7 +240,7 @@ class Hanime1 :
|
||||
val exceptionHandler =
|
||||
CoroutineExceptionHandler { _, _ -> filterUpdateState = FilterUpdateState.FAILED }
|
||||
scope.launch(exceptionHandler) {
|
||||
val jsoup = client.newCall(GET("$baseUrl/search")).awaitSuccess().asJsoup()
|
||||
val jsoup = client.newCall(GET("$baseUrl/search")).awaitSuccess().useAsJsoup()
|
||||
val genreList = jsoup.select("div.genre-option div.hentai-sort-options").eachText()
|
||||
val sortList =
|
||||
jsoup.select("div.hentai-sort-options-wrapper div.hentai-sort-options").eachText()
|
||||
@@ -269,7 +267,7 @@ class Hanime1 :
|
||||
.putString(PREF_KEY_SORT_LIST, sortList.joinToString())
|
||||
.putString(PREF_KEY_YEAR_LIST, yearList.joinToString())
|
||||
.putString(PREF_KEY_MONTH_LIST, monthList.joinToString())
|
||||
.putString(PREF_KEY_CATEGORY_LIST, json.encodeToString(categoryDict)).apply()
|
||||
.putString(PREF_KEY_CATEGORY_LIST, categoryDict.toJsonString()).apply()
|
||||
filterUpdateState = FilterUpdateState.COMPLETED
|
||||
}
|
||||
}
|
||||
@@ -293,7 +291,7 @@ class Hanime1 :
|
||||
if (savedCategories.isNullOrEmpty()) {
|
||||
return result
|
||||
}
|
||||
json.decodeFromString<Map<String, List<String>>>(savedCategories).forEach {
|
||||
savedCategories.parseAs<Map<String, List<String>>>().forEach {
|
||||
result.add(CategoryFilter(it.key, it.value.map { value -> TagFilter("tags[]", value) }))
|
||||
}
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user