Merge branch 'Kohi-den:main' into main

This commit is contained in:
Dark25 2024-08-14 18:47:30 +01:00 committed by GitHub
commit 9713f51218
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 839 additions and 221 deletions

View file

@ -48,7 +48,7 @@ body:
required: true required: true
- label: I have written a title with source name. - label: I have written a title with source name.
required: true required: true
- label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/almightyhak/aniyomi-extensions) and verified it does not appear in the code base. - label: I have checked that the extension does not already exist by searching the [GitHub repository](https://github.com/Kohi-den/extensions-source) and verified it does not appear in the code base.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View file

@ -11,8 +11,8 @@ VERSION_STR = "VersionCode ="
VERSION_REGEX = re.compile(f"{VERSION_STR} (\\d+)") VERSION_REGEX = re.compile(f"{VERSION_STR} (\\d+)")
BUMPED_FILES: list[Path] = [] BUMPED_FILES: list[Path] = []
BOT_EMAIL = "134626626+almightyhak@users.noreply.github.com" BOT_EMAIL = "177773202+Kohi-den-Bot@users.noreply.github.com"
BOT_NAME = "almightyhak" BOT_NAME = "Kohi-den-Bot"
def has_match(query: str, file: Path) -> tuple[Path, bool]: def has_match(query: str, file: Path) -> tuple[Path, bool]:
return (file, query in file.read_text()) return (file, query in file.read_text())

View file

@ -2,8 +2,8 @@
set -e set -e
rsync -a --delete --exclude .git --exclude .gitignore ../main/repo/ . rsync -a --delete --exclude .git --exclude .gitignore ../main/repo/ .
git config --global user.email "134626626+almightyhak@users.noreply.github.com" git config --global user.email "177773202+Kohi-den-Bot@users.noreply.github.com"
git config --global user.name "almightyhak" git config --global user.name "Kohi-den-Bot"
git status git status
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
git add . git add .

View file

@ -113,7 +113,7 @@ jobs:
- name: Upload APKs (chunk ${{ matrix.chunk }}) - name: Upload APKs (chunk ${{ matrix.chunk }})
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
if: "github.repository == 'almightyhak/aniyomi-extensions'" if: "github.repository == 'Kohi-den/extensions-source'"
with: with:
name: "individual-apks-${{ matrix.chunk }}" name: "individual-apks-${{ matrix.chunk }}"
path: "**/*.apk" path: "**/*.apk"
@ -126,7 +126,7 @@ jobs:
name: Publish repo name: Publish repo
needs: needs:
- build_individual - build_individual
if: "github.repository == 'almightyhak/aniyomi-extensions'" if: "github.repository == 'Kohi-den/extensions-source'"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download APK artifacts - name: Download APK artifacts
@ -158,7 +158,7 @@ jobs:
- name: Checkout repo branch - name: Checkout repo branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with: with:
repository: almightyhak/aniyomi-anime-repo repository: Kohi-den/extensions
token: ${{ secrets.BOT_PAT }} token: ${{ secrets.BOT_PAT }}
ref: main ref: main
path: repo path: repo
@ -172,6 +172,6 @@ jobs:
with: with:
message: "Update extensions repo" message: "Update extensions repo"
cwd: "./repo" cwd: "./repo"
committer_name: almightyhak committer_name: Kohi-den-Bot
committer_email: 134626626+almightyhak@users.noreply.github.com committer_email: 177773202+Kohi-den-Bot@users.noreply.github.com

View file

@ -2,9 +2,9 @@
just paste this into your anime repo just paste this into your anime repo
``` ```
https://raw.githubusercontent.com/almightyhak/aniyomi-anime-repo/main/index.min.json https://raw.githubusercontent.com/Kohi-den/extensions/main/index.min.json
``` ```
If your interested in installing just the apks they can be found [Here](https://github.com/almightyhak/aniyomi-anime-repo) If your interested in installing just the apks they can be found [Here](https://github.com/Kohi-den/extensions)
## Support Server ## Support Server
@ -16,6 +16,10 @@ and please check the discord BEFORE making an issue
[Template](https://github.com/aniyomiorg/aniyomi-extensions/blob/master/CONTRIBUTING.md) [Template](https://github.com/aniyomiorg/aniyomi-extensions/blob/master/CONTRIBUTING.md)
## Contact ## Disclaimer
almighty_hak on discord This project does not have any affiliation with the content providers available.
This project is not affiliated with Aniyomi.
Don't ask for help about these extensions at the official support means of Aniyomi.
All credits to the codebase goes to the original contributors.

View file

@ -34,7 +34,7 @@ class DoodExtractor(private val client: OkHttpClient) {
), ),
).execute().body.string() ).execute().body.string()
val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry" val videoUrl = "$videoUrlStart$randomString?token=$token&expiry=$expiry"
Video(newUrl, newQuality, videoUrl, headers = doodHeaders(doodHost), subtitleTracks = externalSubs) Video(videoUrl, newQuality, videoUrl, headers = doodHeaders(doodHost), subtitleTracks = externalSubs)
}.getOrNull() }.getOrNull()
} }

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Cuevana' extName = 'Cuevana'
extClass = '.CuevanaFactory' extClass = '.CuevanaFactory'
extVersionCode = 31 extVersionCode = 32
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -24,10 +24,7 @@ import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelMapBlocking
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -151,28 +148,28 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
private fun serverIterator(videos: Videos?): MutableList<Video> { private fun serverIterator(videos: Videos?): MutableList<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
videos?.latino?.parallelMapBlocking { videos?.latino?.forEach {
try { try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup() val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: "" val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[LAT]").let { videoList.addAll(it) } loadExtractor(url, "[LAT]").let { videoList.addAll(it) }
} catch (_: Exception) { } } catch (_: Exception) { }
} }
videos?.spanish?.map { videos?.spanish?.forEach {
try { try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup() val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: "" val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[CAST]").let { videoList.addAll(it) } loadExtractor(url, "[CAST]").let { videoList.addAll(it) }
} catch (_: Exception) { } } catch (_: Exception) { }
} }
videos?.english?.map { videos?.english?.forEach {
try { try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup() val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: "" val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[ENG]").let { videoList.addAll(it) } loadExtractor(url, "[ENG]").let { videoList.addAll(it) }
} catch (_: Exception) { } } catch (_: Exception) { }
} }
videos?.japanese?.map { videos?.japanese?.forEach {
val body = client.newCall(GET(it.result!!)).execute().asJsoup() val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: "" val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[JAP]").let { videoList.addAll(it) } loadExtractor(url, "[JAP]").let { videoList.addAll(it) }
@ -183,31 +180,6 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
private fun loadExtractor(url: String, prefix: String = ""): List<Video> { private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} catch (_: Exception) { }
}
if (embedUrl.contains("yourupload")) { if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers) val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos) videoList.addAll(videos)

View file

@ -0,0 +1,11 @@
ext {
extName = 'HomeCine'
extClass = '.HomeCine'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:fastream-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,242 @@
package eu.kanade.tachiyomi.animeextension.es.homecine
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class HomeCine : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "HomeCine"
override val baseUrl = "https://www3.homecine.tv"
override val lang = "es"
override val supportsLatest = false
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Fastream"
private val SERVER_LIST = arrayOf("Fastream")
}
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val animeDetails = SAnime.create().apply {
title = document.selectFirst(".mvic-desc h1")?.text()?.trim() ?: ""
status = if (document.location().contains("/series/")) SAnime.UNKNOWN else SAnime.COMPLETED
description = document.selectFirst(".mvic-desc .f-desc")?.ownText()
genre = document.select(".mvic-info [rel='category tag']").joinToString { it.text() }
thumbnail_url = document.selectFirst("[itemprop=image]")?.attr("abs:src")?.replace("/w185/", "/w500/")
document.select(".mvici-left p").map { it.text() }.map { textContent ->
when {
"Director" in textContent -> author = textContent.substringAfter("Director:").trim().split(", ").firstOrNull()
"Actors" in textContent -> artist = textContent.substringAfter("Actors:").trim().split(", ").firstOrNull()
}
}
}
return animeDetails
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/peliculas/page/$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select("[data-movie-id] > a")
val nextPage = document.select(".pagination li a:contains(Last)").any()
val animeList = elements.map { element ->
SAnime.create().apply {
title = element.selectFirst(".mli-info")?.text()?.trim() ?: ""
thumbnail_url = element.selectFirst("img")!!.attr("abs:data-original")
setUrlWithoutDomain(element.attr("abs:href"))
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/page/$page/?s=$query", headers)
genreFilter.state != 0 -> GET("$baseUrl/${genreFilter.toUriPart()}/page/$page", headers)
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
return if (document.location().contains("/series/")) {
var episodeCounter = 1F
document.select(".tvseason").flatMap { season ->
val noSeason = season.select(".les-title strong").text().substringAfter("Temporada").trim()
season.select(".les-content a").map { ep ->
SEpisode.create().apply {
episode_number = episodeCounter++
name = "T$noSeason - ${ep.text().trim()}"
setUrlWithoutDomain(ep.attr("abs:href"))
}
}
}.reversed()
} else {
listOf(
SEpisode.create().apply {
episode_number = 1f
name = "PELÍCULA"
setUrlWithoutDomain(document.location())
},
)
}
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
return document.select(".movieplay iframe").parallelCatchingFlatMapBlocking {
val link = it.attr("abs:src")
val prefix = runCatching {
val tabElement = it.closest("[id*=tab]") ?: return@runCatching ""
val tabName = tabElement.attr("id")
val lang = document.selectFirst("[href=\"#$tabName\"]")?.ownText()?.trim() ?: ""
when {
lang.lowercase().contains("latino") -> "[LAT]"
lang.lowercase().contains("castellano") -> "[CAST]"
lang.lowercase().contains("subtitulado") -> "[SUB]"
else -> ""
}
}.getOrDefault("")
serverVideoResolver(link, prefix)
}
}
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val embedUrl = url.lowercase()
return when {
embedUrl.contains("fastream") -> {
val link = if (url.contains("emb.html")) "https://fastream.to/embed-${url.split("/").last()}.html" else url
FastreamExtractor(client, headers).videosFromUrl(link, prefix = "$prefix Fastream:")
}
else -> emptyList()
}
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Género",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Películas", "peliculas"),
Pair("Series", "series"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
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 {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
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 {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
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,7 @@
ext { ext {
extName = 'MetroSeries' extName = 'MetroSeries'
extClass = '.MetroSeries' extClass = '.MetroSeries'
extVersionCode = 9 extVersionCode = 10
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -49,6 +49,10 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
} }
companion object { companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080" private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360") private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
@ -129,7 +133,7 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
return episodes return episodes
} }
private fun getDetailSeason(element: org.jsoup.nodes.Element, objectNumber: String, referer: String): IntRange { private fun getDetailSeason(element: Element, objectNumber: String, referer: String): IntRange {
try { try {
val post = element.attr("data-post") val post = element.attr("data-post")
val season = element.attr("data-season") val season = element.attr("data-season")
@ -189,6 +193,16 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val termId = document.select("#option-players").attr("data-term") val termId = document.select("#option-players").attr("data-term")
document.select(".player-options-list li a").forEach { document.select(".player-options-list li a").forEach {
val prefix = runCatching {
val lang = it.select(".option").text().lowercase()
when {
lang.contains("latino") -> "[LAT]"
lang.contains("castellano") -> "[CAST]"
lang.contains("sub") -> "[SUB]"
else -> ""
}
}.getOrDefault("")
val ide = it.attr("data-opt") val ide = it.attr("data-opt")
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
.add("action", "action_player_series") .add("action", "action_player_series")
@ -217,29 +231,29 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val key = src.split("/").last() val key = src.split("/").last()
src = "https://fastream.to/embed-$key.html" src = "https://fastream.to/embed-$key.html"
} }
FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false).also(videoList::addAll) FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false, prefix = "$prefix Fastream:").also(videoList::addAll)
} }
if (src.contains("upstream")) { if (src.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(src).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ").let { videoList.addAll(it) }
} }
if (src.contains("yourupload")) { if (src.contains("yourupload")) {
YourUploadExtractor(client).videoFromUrl(src, headers).let { videoList.addAll(it) } YourUploadExtractor(client).videoFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
} }
if (src.contains("voe")) { if (src.contains("voe")) {
VoeExtractor(client).videosFromUrl(src).also(videoList::addAll) VoeExtractor(client).videosFromUrl(src, prefix = "$prefix ").also(videoList::addAll)
} }
if (src.contains("wishembed") || src.contains("streamwish") || src.contains("wish")) { if (src.contains("wishembed") || src.contains("streamwish") || src.contains("wish")) {
StreamWishExtractor(client, headers).videosFromUrl(src) { "StreamWish:$it" }.also(videoList::addAll) StreamWishExtractor(client, headers).videosFromUrl(src) { "$prefix StreamWish:$it" }.also(videoList::addAll)
} }
if (src.contains("mp4upload")) { if (src.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(src, headers).let { videoList.addAll(it) } Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
} }
if (src.contains("burst")) { if (src.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(src, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(src, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
} }
if (src.contains("filemoon") || src.contains("moonplayer")) { if (src.contains("filemoon") || src.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "Filemoon:").let { videoList.addAll(it) } FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "$prefix Filemoon:").let { videoList.addAll(it) }
} }
} catch (_: Exception) {} } catch (_: Exception) {}
} }
@ -250,8 +264,10 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!! val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith( return this.sortedWith(
compareBy( compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) }, { it.quality.contains(server, true) },
{ it.quality.contains(quality) }, { it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 }, { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
@ -260,6 +276,22 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
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 { ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY key = PREF_QUALITY_KEY
title = "Preferred quality" title = "Preferred quality"

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Pelisplushd' extName = 'Pelisplushd'
extClass = '.PelisplushdFactory' extClass = '.PelisplushdFactory'
extVersionCode = 54 extVersionCode = 56
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
@ -24,4 +24,4 @@ dependencies {
implementation(project(':lib:upstream-extractor')) implementation(project(':lib:upstream-extractor'))
implementation(project(':lib:streamhidevid-extractor')) implementation(project(':lib:streamhidevid-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View file

@ -28,7 +28,6 @@ import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -59,6 +58,9 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon", "Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare", "Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
) )
private val REGEX_LINK = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex()
private val REGEX_VIDEO_OPTS = "'(https?://[^']*)'".toRegex()
} }
override fun popularAnimeSelector(): String = "div.Posters a.Posters-link" override fun popularAnimeSelector(): String = "div.Posters a.Posters-link"
@ -105,20 +107,23 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val data = document.selectFirst("script:containsData(video[1] = )")?.data() ?: return emptyList()
val data = document.selectFirst("script:containsData(video[1] = )")?.data() REGEX_VIDEO_OPTS.findAll(data).map { it.groupValues[1] }.forEach { opt ->
val apiUrl = data?.substringAfter("video[1] = '", "")?.substringBefore("';", "") val apiResponse = client.newCall(GET(opt)).execute()
val alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)")
if (!apiUrl.isNullOrEmpty()) {
val apiResponse = client.newCall(GET(apiUrl)).execute()
val docResponse = apiResponse.asJsoup() val docResponse = apiResponse.asJsoup()
if (apiResponse.isSuccessful) { if (apiResponse.isSuccessful) {
val regIsUrl = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex() val encryptedList = if (docResponse.select("iframe").any()) {
val encryptedList = docResponse.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li") listOf(docResponse.select("iframe").attr("src"))
} else {
docResponse.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li")
.map { it.attr("onclick") }
}
encryptedList.flatMap { encryptedList.flatMap {
runCatching { runCatching {
val url = it.attr("onclick") val url = it.substringAfter("go_to_player('")
.substringAfter("go_to_player('")
.substringAfter("go_to_playerVast('") .substringAfter("go_to_playerVast('")
.substringBefore("?cover_url=") .substringBefore("?cover_url=")
.substringBefore("')") .substringBefore("')")
@ -128,7 +133,7 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
.substringBefore("?thumb=") .substringBefore("?thumb=")
.substringBefore("#poster=") .substringBefore("#poster=")
val realUrl = if (!regIsUrl.containsMatchIn(url)) { val realUrl = if (!REGEX_LINK.containsMatchIn(url)) {
String(Base64.decode(url, Base64.DEFAULT)) String(Base64.decode(url, Base64.DEFAULT))
} else if (url.contains("?data=")) { } else if (url.contains("?data=")) {
val apiPageSoup = client.newCall(GET(url)).execute().asJsoup() val apiPageSoup = client.newCall(GET(url)).execute().asJsoup()
@ -142,27 +147,6 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
}.also(videoList::addAll) }.also(videoList::addAll)
} }
} }
// verifier for old series
if (!apiUrl.isNullOrEmpty() && !apiUrl.contains("/video/") || alternativeServers.any()) {
document.select("ul.TbVideoNv.nav.nav-tabs li").parallelCatchingFlatMapBlocking { id ->
val serverName = id.select("a").text().lowercase()
val serverId = id.attr("data-id")
var serverUrl = data?.substringAfter("video[$serverId] = '", "")?.substringBefore("';", "")
if (serverUrl != null && serverUrl.contains("api.mycdn.moe")) {
val urlId = serverUrl.substringAfter("id=")
serverUrl = when (serverName) {
"sbfast" -> { "https://sbfull.com/e/$urlId" }
"plusto" -> { "https://owodeuwu.xyz/v/$urlId" }
"doodstream" -> { "https://dood.to/e/$urlId" }
"upload", "uqload" -> { "https://uqload.com/embed-$urlId.html" }
else -> ""
}
}
serverVideoResolver(serverUrl ?: "")
}.also(videoList::addAll)
}
return videoList return videoList
} }
@ -206,8 +190,8 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }) StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
} }
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> { embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/") val url2 = url.replace("https://doodstream.com/e/", "https://d0000d.com/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)!!) listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream")!!)
} }
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url) embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url)
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers) embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers)

View file

@ -83,26 +83,20 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val episode = SEpisode.create().apply { val episode = SEpisode.create().apply {
episode_number = 1F episode_number = 1F
name = "PELÍCULA" name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString()) setUrlWithoutDomain(jsoup.location())
} }
episodes.add(episode) episodes.add(episode)
} else { } else {
var jsonscript = "" val jsonStrData = jsoup.selectFirst("script:containsData(const seasonUrl =)")?.data() ?: return emptyList()
jsoup.select("script[type=text/javascript]").mapNotNull { script -> val jsonParse = json.decodeFromString<JsonObject>(jsonStrData.substringAfter("seasonsJson = ").substringBefore(";"))
val ssRegex = Regex("(?i)seasons")
val ss = if (script.data().contains(ssRegex)) script.data() else ""
val swaa = ss.substringAfter("seasonsJson = ").substringBefore(";")
jsonscript = swaa
}
val jsonParse = json.decodeFromString<JsonObject>(jsonscript)
var index = 0 var index = 0
jsonParse.entries.map { jsonParse.entries.map {
it.value.jsonArray.reversed().map { element -> it.value.jsonArray.reversed().map { element ->
index += 1 index += 1
val jsonElement = element!!.jsonObject val jsonElement = element.jsonObject
val season = jsonElement["season"]!!.jsonPrimitive!!.content val season = jsonElement["season"]!!.jsonPrimitive.content
val title = jsonElement["title"]!!.jsonPrimitive!!.content val title = jsonElement["title"]!!.jsonPrimitive.content
val ep = jsonElement["episode"]!!.jsonPrimitive!!.content val ep = jsonElement["episode"]!!.jsonPrimitive.content
val episode = SEpisode.create().apply { val episode = SEpisode.create().apply {
episode_number = index.toFloat() episode_number = index.toFloat()
name = "T$season - E$ep - $title" name = "T$season - E$ep - $title"

View file

@ -0,0 +1,15 @@
ext {
extName = 'VerAnimes'
extClass = '.VerAnimes'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:okru-extractor'))
implementation(project(':lib:streamwish-extractor'))
implementation(project(':lib:streamhidevid-extractor'))
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:yourupload-extractor'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,281 @@
package eu.kanade.tachiyomi.animeextension.es.veranimes
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.veranimes.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parseAs
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class VerAnimes : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "VerAnimes"
override val baseUrl = "https://wwv.veranimes.net"
override val lang = "es"
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"StreamWish",
"Voe",
"Okru",
"YourUpload",
"FileLions",
"StreamHideVid",
)
}
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
val animeDetails = SAnime.create().apply {
title = document.selectFirst(".ti h1")?.text()?.trim() ?: ""
status = SAnime.UNKNOWN
description = document.selectFirst(".r .tx p")?.text()
genre = document.select(".gn li a").joinToString { it.text() }
thumbnail_url = document.selectFirst(".info figure img")?.attr("abs:data-src")
document.select(".info .u:not(.sp) > li").map { it.text() }.map { textContent ->
when {
"Estudio" in textContent -> author = textContent.substringAfter("Estudio(s):").trim()
"Producido" in textContent -> artist = textContent.substringAfter("Producido por:").trim()
}
}
}
return animeDetails
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/animes?orden=desc&pag=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select("article.li figure a")
val nextPage = document.select(".pag li a[title*=Siguiente]").any()
val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
title = element.attr("title")
thumbnail_url = element.selectFirst("img")!!.attr("abs:data-src")
}
}
return AnimesPage(animeList, nextPage)
}
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?estado=en-emision&orden=desc&pag=$page", headers)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
return when {
query.isNotBlank() -> GET("$baseUrl/animes?buscar=$query&pag=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/animes?genero=${genreFilter.toUriPart()}&orden=desc&pag=$page", headers)
else -> popularAnimeRequest(page)
}
}
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val scriptEps = document.selectFirst("script:containsData(var eps =)")?.data() ?: return emptyList()
val slug = document.select("*[data-sl]").attr("data-sl")
return scriptEps.substringAfter("var eps = ").substringBefore(";").trim().parseAs<List<String>>().map {
SEpisode.create().apply {
episode_number = it.toFloat()
name = "Episodio $it"
setUrlWithoutDomain("/ver/$slug-$it")
}
}
}
private fun hex2a(hex: String): String {
return StringBuilder(hex.length / 2).apply {
for (i in hex.indices step 2) {
val charCode = hex.substring(i, i + 2).toInt(16)
append(charCode.toChar())
}
}.toString()
}
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val opt = document.select(".opt").attr("data-encrypt")
val mediaType = "application/x-www-form-urlencoded; charset=UTF-8".toMediaType()
val body = "acc=opt&i=$opt".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://wwv.veranimes.net/process")
.post(body)
.addHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
.addHeader("referer", document.location())
.addHeader("x-requested-with", "XMLHttpRequest")
.build()
val serversDocument = client.newCall(request).execute().asJsoup()
return serversDocument.select("li").parallelCatchingFlatMapBlocking {
val link = hex2a(it.attr("encrypt"))
serverVideoResolver(link)
}
}
private fun serverVideoResolver(url: String): List<Video> {
val embedUrl = url.lowercase()
return when {
embedUrl.contains("ok.ru") || embedUrl.contains("okru") -> OkruExtractor(client).videosFromUrl(url)
embedUrl.contains("filelions") || embedUrl.contains("lion") -> StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" })
embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish") -> {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
}
embedUrl.contains("vidhide") || embedUrl.contains("streamhide") ||
embedUrl.contains("guccihide") || embedUrl.contains("streamvid") -> StreamHideVidExtractor(client).videosFromUrl(url)
embedUrl.contains("voe") -> VoeExtractor(client).videosFromUrl(url)
embedUrl.contains("yourupload") || embedUrl.contains("upload") -> YourUploadExtractor(client).videoFromUrl(url, headers = headers)
embedUrl.contains("vidguard") || embedUrl.contains("vgfplay") || embedUrl.contains("listeamed") -> VidGuardExtractor(client).videosFromUrl(url)
else -> emptyList()
}
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Género",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Acción", "accion"),
Pair("Artes Marciales", "artes-marciales"),
Pair("Aventuras", "aventuras"),
Pair("Carreras", "carreras"),
Pair("Ciencia Ficción", "ciencia-ficcion"),
Pair("Comedia", "comedia"),
Pair("Demencia", "demencia"),
Pair("Demonios", "demonios"),
Pair("Deportes", "deportes"),
Pair("Drama", "drama"),
Pair("Ecchi", "ecchi"),
Pair("Escolares", "escolares"),
Pair("Espacial", "espacial"),
Pair("Fantasía", "fantasia"),
Pair("Harem", "harem"),
Pair("Historico", "historico"),
Pair("Infantil", "infantil"),
Pair("Josei", "josei"),
Pair("Juegos", "juegos"),
Pair("Magia", "magia"),
Pair("Mecha", "mecha"),
Pair("Militar", "militar"),
Pair("Misterio", "misterio"),
Pair("Música", "musica"),
Pair("Parodia", "parodia"),
Pair("Policía", "policia"),
Pair("Psicológico", "psicologico"),
Pair("Recuentos de la vida", "recuentos-de-la-vida"),
Pair("Romance", "romance"),
Pair("Samurai", "samurai"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shounen", "shounen"),
Pair("Sobrenatural", "sobrenatural"),
Pair("Superpoderes", "superpoderes"),
Pair("Suspenso", "suspenso"),
Pair("Terror", "terror"),
Pair("Vampiros", "vampiros"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
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 {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
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

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.animeextension.es.veranimes.extractors
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class VidGuardExtractor(private val client: OkHttpClient) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch) {
var payload: String = ""
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().asJsoup()
val scriptUrl = doc.selectFirst("script[src*=ad/plugin]")
?.absUrl("src")
?: return emptyList()
val headers = Headers.headersOf("Referer", url)
val script = client.newCall(GET(scriptUrl, headers)).execute()
.body.string()
val sources = getSourcesFromScript(script, url)
.takeIf { it.isNotBlank() && it != "undefined" }
?: return emptyList()
return sources.substringAfter("stream:[").substringBefore("}]")
.split('{')
.drop(1)
.mapNotNull { line ->
val resolution = line.substringAfter("Label\":\"").substringBefore('"')
val videoUrl = line.substringAfter("URL\":\"").substringBefore('"')
.takeIf(String::isNotBlank)
?.let(::fixUrl)
?: return@mapNotNull null
Video(videoUrl, "VidGuard:$resolution", videoUrl, headers)
}
}
private fun getSourcesFromScript(script: String, url: String): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
val jsinterface = JsObject(latch)
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.clearCache(true)
view?.clearFormData()
view?.evaluateJavascript(script) {}
view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {}
}
}
webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null)
}
latch.await(5, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return jsinterface.payload
}
private fun fixUrl(url: String): String {
val httpUrl = url.toHttpUrl()
val originalSign = httpUrl.queryParameter("sig")!!
val newSign = originalSign.chunked(2).joinToString("") {
Char(it.toInt(16) xor 2).toString()
}
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substring(5)
.chunked(2)
.reversed()
.joinToString("")
.substring(5)
return httpUrl.newBuilder()
.removeAllQueryParameters("sig")
.addQueryParameter("sig", newSign)
.build()
.toString()
}
}

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'Oploverz' extName = 'Oploverz'
extClass = '.Oploverz' extClass = '.Oploverz'
extVersionCode = 26 extVersionCode = 27
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.id.oploverz
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
@ -12,14 +13,11 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking
import eu.kanade.tachiyomi.util.parallelMapNotNullBlocking
import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -39,28 +37,28 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = override fun popularAnimeRequest(page: Int): Request =
GET("$baseUrl/anime-list/page/$page/?order=popular") GET("$baseUrl/anime/?page=$page&status=&type=&sub=&order=popular")
override fun popularAnimeParse(response: Response): AnimesPage = override fun popularAnimeParse(response: Response): AnimesPage =
getAnimeParse(response, "div.relat > article") getAnimeParse(response, "article[itemscope=itemscope]")
// =============================== Latest =============================== // =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/anime-list/page/$page/?order=latest") GET("$baseUrl/anime/?page=$page&status=&type=&sub=&order=latest")
override fun latestUpdatesParse(response: Response): AnimesPage = override fun latestUpdatesParse(response: Response): AnimesPage =
getAnimeParse(response, "div.relat > article") getAnimeParse(response, "article[itemscope=itemscope]")
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val params = OploverzFilters.getSearchParameters(filters) val params = OploverzFilters.getSearchParameters(filters)
return GET("$baseUrl/anime-list/page/$page/?title=$query${params.filter}", headers) return GET("$baseUrl/page/$page/?s=$query${params.filter}", headers)
} }
override fun searchAnimeParse(response: Response): AnimesPage = override fun searchAnimeParse(response: Response): AnimesPage =
getAnimeParse(response, "div.relat > article") getAnimeParse(response, "article[itemscope=itemscope]")
// ============================== Filters =============================== // ============================== Filters ===============================
@ -70,16 +68,16 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun animeDetailsParse(response: Response): SAnime { override fun animeDetailsParse(response: Response): SAnime {
val doc = response.asJsoup() val doc = response.asJsoup()
val detail = doc.selectFirst("div.infox > div.spe")!! val detail = doc.selectFirst("div.info-content > div.spe")!!
return SAnime.create().apply { return SAnime.create().apply {
author = detail.getInfo("Studio") author = detail.getInfo("Studio")
status = parseStatus(doc.selectFirst("div.alternati > span:nth-child(2)")!!.text()) status = parseStatus(detail.getInfo("Status"))
title = doc.selectFirst("div.title > h1.entry-title")!!.text() title = doc.selectFirst("h1.entry-title")!!.text()
thumbnail_url = thumbnail_url =
doc.selectFirst("div.infoanime.widget_senction > div.thumb > img")!! doc.selectFirst("div.thumb > img")!!
.attr("src") .attr("src")
description = description =
doc.select("div.entry-content.entry-content-single > p") doc.select("div.entry-content > p")
.joinToString("\n\n") { it.text() } .joinToString("\n\n") { it.text() }
} }
} }
@ -88,13 +86,13 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup() val doc = response.asJsoup()
return doc.select("div.lstepsiode.listeps > ul.scrolling > li").map { return doc.select("div.eplister > ul > li").map {
val episode = it.selectFirst("span.eps > a")!! val episode = it.selectFirst("a")!!
SEpisode.create().apply { SEpisode.create().apply {
setUrlWithoutDomain(episode.attr("href")) setUrlWithoutDomain(episode.attr("href"))
episode_number = episode.text().trim().toFloatOrNull() ?: 1F episode_number = it.selectFirst("div.epl-num")!!.text().toFloatOrNull() ?: 1F
name = it.selectFirst("span.lchx > a")!!.text() name = it.selectFirst("div.epl-title")!!.text()
date_upload = it.selectFirst("span.date")!!.text().toDate() date_upload = it.selectFirst("div.epl-date")!!.text().toDate()
} }
} }
} }
@ -103,15 +101,25 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup() val doc = response.asJsoup()
val parseUrl = response.request.url.toUrl() val videoList = mutableListOf<Video>()
val url = "${parseUrl.protocol}://${parseUrl.host}"
return doc.select("#server > ul > li > div.east_player_option") doc.select("select.mirror > option[value]").forEach { opt ->
.parallelMapNotNullBlocking { val decoded = if (opt.attr("value").isEmpty()) {
runCatching { getEmbedLinks(url, it) }.getOrNull() doc.selectFirst("iframe")!!.attr("src")
} else {
Jsoup.parse(
String(Base64.decode(opt.attr("value"), Base64.DEFAULT)),
).select("iframe").attr("src")
} }
.parallelCatchingFlatMapBlocking {
getVideosFromEmbed(it.first) when {
decoded.contains("blogger.com") -> {
videoList.addAll(getVideosFromEmbed(decoded))
}
} }
}
return videoList.sort()
} }
// ============================= Utilities ============================== // ============================= Utilities ==============================
@ -140,19 +148,14 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
val doc = response.asJsoup() val doc = response.asJsoup()
val animes = doc.select(query).map { val animes = doc.select(query).map {
SAnime.create().apply { SAnime.create().apply {
setUrlWithoutDomain(it.selectFirst("div.animposx > a")!!.attr("href")) setUrlWithoutDomain(it.selectFirst("a.tip")!!.attr("href"))
title = it.selectFirst("div.title > h2")!!.text() title = it.selectFirst("div.tt > h2")!!.text()
thumbnail_url = it.selectFirst("div.content-thumb > img")!!.attr("src") thumbnail_url = it.selectFirst("div.limit > img")!!.attr("src")
} }
} }
val hasNextPage = try {
val pagination = doc.selectFirst("div.pagination")!! val hasNextPage = doc.selectFirst("a.next.page-numbers") != null ?: doc.selectFirst("div.hpage > a.r")
val totalPage = pagination.selectFirst("span:nth-child(1)")!!.text().split(" ").last()
val currentPage = pagination.selectFirst("span.page-numbers.current")!!.text()
currentPage.toInt() < totalPage.toInt()
} catch (_: Exception) {
false
}
return AnimesPage(animes, hasNextPage) return AnimesPage(animes, hasNextPage)
} }
@ -164,18 +167,6 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
} }
} }
private fun getEmbedLinks(url: String, element: Element): Pair<String, String> {
val form = FormBody.Builder().apply {
add("action", "player_ajax")
add("post", element.attr("data-post"))
add("nume", element.attr("data-nume"))
add("type", element.attr("data-type"))
}.build()
return client.newCall(POST("$url/wp-admin/admin-ajax.php", body = form))
.execute()
.let { Pair(it.asJsoup().selectFirst(".playeriframe")!!.attr("src"), "") }
}
private fun getVideosFromEmbed(link: String): List<Video> { private fun getVideosFromEmbed(link: String): List<Video> {
return when { return when {
"blogger" in link -> { "blogger" in link -> {

View file

@ -1,7 +1,7 @@
ext { ext {
extName = 'StreamingCommunity' extName = 'StreamingCommunity'
extClass = '.StreamingCommunity' extClass = '.StreamingCommunity'
extVersionCode = 4 extVersionCode = 5
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View file

@ -30,7 +30,7 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "StreamingCommunity" override val name = "StreamingCommunity"
override val baseUrl = "https://streamingcommunity.forum" override val baseUrl = "https://streamingcommunity.photos"
override val lang = "it" override val lang = "it"
@ -254,72 +254,40 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> { override suspend fun getVideoList(episode: SEpisode): List<Video> {
val videoList = mutableListOf<Video>() val videoSet = mutableSetOf<Video>()
val doc = val doc = client.newCall(
client GET("$baseUrl/iframe/${episode.url}", headers),
.newCall( ).execute().asJsoup()
GET("$baseUrl/iframe/${episode.url}", headers),
).execute()
.asJsoup()
val iframeUrl =
doc.selectFirst("iframe[src]")?.attr("abs:src")
?: error("Failed to extract iframe")
val iframeHeaders =
headers
.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()
val iframe = val iframeUrl = doc.selectFirst("iframe[src]")?.attr("abs:src")
client ?: error("Failed to extract iframe")
.newCall(
GET(iframeUrl, headers = iframeHeaders), val iframeHeaders = headers.newBuilder()
).execute() .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.asJsoup() .add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()
val iframe = client.newCall(GET(iframeUrl, headers = iframeHeaders)).execute().asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylist)")!!.data().replace("\n", "\t") val script = iframe.selectFirst("script:containsData(masterPlaylist)")!!.data().replace("\n", "\t")
var playlistUrl = PLAYLIST_URL_REGEX.find(script)!!.groupValues[1] val playlistUrl = PLAYLIST_URL_REGEX.find(script)!!.groupValues[1]
val filename = playlistUrl.substringAfterLast("/")
if (!filename.endsWith(".m3u8")) {
playlistUrl = playlistUrl.replace(filename, filename + ".m3u8")
}
val expires = EXPIRES_REGEX.find(script)!!.groupValues[1]
val token = TOKEN_REGEX.find(script)!!.groupValues[1] val token = TOKEN_REGEX.find(script)!!.groupValues[1]
val expires = EXPIRES_REGEX.find(script)!!.groupValues[1]
// Get subtitles val masterPlUrl = "$playlistUrl&token=$token&expires=$expires&b=1"
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&n=1"
val masterPl =
client
.newCall(GET(masterPlUrl))
.execute()
.body
.string()
val subList =
SUBTITLES_REGEX.findAll(masterPl)
.map {
Track(it.groupValues[2], it.groupValues[1])
}.toList()
TOKEN_QUALITY_REGEX.findAll(script).forEach { match ->
val quality = match.groupValues[1]
val videoUrl = val masterPl = client.newCall(GET(masterPlUrl)).execute().body.string()
buildString { val subList = SUBTITLES_REGEX.findAll(masterPl).map {
append(playlistUrl) Track(it.groupValues[2], it.groupValues[1])
append("?type=video&rendition=") }.toList()
append(quality) QUALITY_REGEX.findAll(masterPl).forEach { match ->
append("&token=") val quality = "${match.groupValues[1]}p"
append(match.groupValues[2]) val videoUrl = match.groupValues[2]
append("&expires=$expires") videoSet.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
append("&n=1")
}
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
} }
require(videoSet.isNotEmpty()) { "Failed to fetch videos" }
require(videoList.isNotEmpty()) { "Failed to fetch videos" } return videoSet.toList().sortedBy { it.quality }
return videoList.sort()
} }
override fun videoListRequest(episode: SEpisode): Request = throw Exception("Not used") override fun videoListRequest(episode: SEpisode): Request = throw Exception("Not used")
@ -359,10 +327,10 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
private val PLAYLIST_URL_REGEX = Regex("""url: ?'(.*?)'""") private val PLAYLIST_URL_REGEX = Regex("""url: ?'(.*?)'""")
private val EXPIRES_REGEX = Regex("""'expires': ?'(\d+)'""") private val EXPIRES_REGEX = Regex("""'expires': ?'(\d+)'""")
private val TOKEN_REGEX = Regex("""'token': ?'([\w-]+)'""") private val TOKEN_REGEX = Regex("""'token': ?'([\w-]+)'""")
private val TOKEN_QUALITY_REGEX = Regex("""'token(\d+p?)': ?'([\w-]+)'""") private val QUALITY_REGEX = Regex("""RESOLUTION=.*?x(.*).*?\n(.*)""")
private val SUBTITLES_REGEX = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""") private val SUBTITLES_REGEX = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "720" private const val PREF_QUALITY_DEFAULT = "1080"
} }
// ============================== Settings ============================== // ============================== Settings ==============================