refactor: using helper methods, improvement & fix bugs

This commit is contained in:
Cuong-Tran
2026-06-02 18:47:50 +07:00
parent fde267d9fc
commit 918c9c786c
86 changed files with 956 additions and 1442 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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("]")

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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? {

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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("//")) {

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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'"]+)""")
}
}

View File

@@ -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())

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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> {

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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'))
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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")

View File

@@ -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" },
)

View File

@@ -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" },
)
}

View File

@@ -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)
}

View File

@@ -3,7 +3,7 @@ ext {
extClass = '.AnimeIto'
themePkg = 'animestream'
baseUrl = 'https://animesonline.io'
overrideVersionCode = 2
overrideVersionCode = 1
}
apply plugin: "kei.plugins.extension.legacy"

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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),

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -8,4 +8,5 @@ apply plugin: "kei.plugins.extension.legacy"
dependencies {
implementation(project(':lib:bloggerextractor'))
implementation(project(':lib:playlistutils'))
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -1,5 +1,5 @@
ext {
extName = 'AnimesOnlineCC'
extName = 'Animes Online CC'
extClass = '.AnimesOnlineCC'
themePkg = 'dooplay'
baseUrl = 'https://animesonlinecc.to'

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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, "")

View File

@@ -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(

View File

@@ -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)
}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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()) {

View File

@@ -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) {
""
}
}

View File

@@ -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()
}
}

View File

@@ -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 }
}
}

View File

@@ -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()

View File

@@ -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",
)
}
}

View File

@@ -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" }
}
}
}

View File

@@ -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))
}

View File

@@ -6,3 +6,7 @@ ext {
}
apply plugin: "kei.plugins.extension.legacy"
dependencies {
implementation(project(':lib:bloggerextractor'))
}

View File

@@ -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)
}
}
}

View File

@@ -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,
),
)
}
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -1,5 +1,5 @@
ext {
extName = 'Hanime1'
extName = 'Hanime1.me'
extClass = '.Hanime1'
extVersionCode = 6
isNsfw = true

View File

@@ -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