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
- label: I have written a title with source name.
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
- label: I will fill out all of the requested information in this form.
required: true

View file

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

View file

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

View file

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

View file

@ -2,9 +2,9 @@
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
@ -16,6 +16,10 @@ and please check the discord BEFORE making an issue
[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()
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()
}

View file

@ -1,7 +1,7 @@
ext {
extName = 'Cuevana'
extClass = '.CuevanaFactory'
extVersionCode = 31
extVersionCode = 32
}
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.parallelMapBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import okhttp3.Response
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> {
val videoList = mutableListOf<Video>()
videos?.latino?.parallelMapBlocking {
videos?.latino?.forEach {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[LAT]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.spanish?.map {
videos?.spanish?.forEach {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[CAST]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.english?.map {
videos?.english?.forEach {
try {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
loadExtractor(url, "[ENG]").let { videoList.addAll(it) }
} catch (_: Exception) { }
}
videos?.japanese?.map {
videos?.japanese?.forEach {
val body = client.newCall(GET(it.result!!)).execute().asJsoup()
val url = body.selectFirst("script:containsData(var message)")?.data()?.substringAfter("var url = '")?.substringBefore("'") ?: ""
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> {
val videoList = mutableListOf<Video>()
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")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
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 {
extName = 'MetroSeries'
extClass = '.MetroSeries'
extVersionCode = 9
extVersionCode = 10
}
apply from: "$rootDir/common.gradle"

View file

@ -49,6 +49,10 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
}
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")
@ -129,7 +133,7 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
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 {
val post = element.attr("data-post")
val season = element.attr("data-season")
@ -189,6 +193,16 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val videoList = mutableListOf<Video>()
val termId = document.select("#option-players").attr("data-term")
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 formBody = FormBody.Builder()
.add("action", "action_player_series")
@ -217,29 +231,29 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val key = src.split("/").last()
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")) {
UpstreamExtractor(client).videosFromUrl(src).let { videoList.addAll(it) }
UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ").let { videoList.addAll(it) }
}
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")) {
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")) {
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")) {
Mp4uploadExtractor(client).videosFromUrl(src, headers).let { videoList.addAll(it) }
Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
}
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")) {
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) {}
}
@ -250,8 +264,10 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
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 },
@ -260,6 +276,22 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
}
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_QUALITY_KEY
title = "Preferred quality"

View file

@ -1,7 +1,7 @@
ext {
extName = 'Pelisplushd'
extClass = '.PelisplushdFactory'
extVersionCode = 54
extVersionCode = 56
}
apply from: "$rootDir/common.gradle"

View file

@ -28,7 +28,6 @@ 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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
@ -59,6 +58,9 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"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"
@ -105,20 +107,23 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val data = document.selectFirst("script:containsData(video[1] = )")?.data() ?: return emptyList()
val data = document.selectFirst("script:containsData(video[1] = )")?.data()
val apiUrl = data?.substringAfter("video[1] = '", "")?.substringBefore("';", "")
val alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)")
if (!apiUrl.isNullOrEmpty()) {
val apiResponse = client.newCall(GET(apiUrl)).execute()
REGEX_VIDEO_OPTS.findAll(data).map { it.groupValues[1] }.forEach { opt ->
val apiResponse = client.newCall(GET(opt)).execute()
val docResponse = apiResponse.asJsoup()
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 = docResponse.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li")
val encryptedList = if (docResponse.select("iframe").any()) {
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 {
runCatching {
val url = it.attr("onclick")
.substringAfter("go_to_player('")
val url = it.substringAfter("go_to_player('")
.substringAfter("go_to_playerVast('")
.substringBefore("?cover_url=")
.substringBefore("')")
@ -128,7 +133,7 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
.substringBefore("?thumb=")
.substringBefore("#poster=")
val realUrl = if (!regIsUrl.containsMatchIn(url)) {
val realUrl = if (!REGEX_LINK.containsMatchIn(url)) {
String(Base64.decode(url, Base64.DEFAULT))
} else if (url.contains("?data=")) {
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)
}
}
// 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
}
@ -206,8 +190,8 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
}
embedUrl.contains("doodstream") || embedUrl.contains("dood.") || embedUrl.contains("ds2play") || embedUrl.contains("doods.") -> {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)!!)
val url2 = url.replace("https://doodstream.com/e/", "https://d0000d.com/e/")
listOf(DoodExtractor(client).videoFromUrl(url2, "DoodStream")!!)
}
embedUrl.contains("streamlare") -> StreamlareExtractor(client).videosFromUrl(url)
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 {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
setUrlWithoutDomain(jsoup.location())
}
episodes.add(episode)
} else {
var jsonscript = ""
jsoup.select("script[type=text/javascript]").mapNotNull { script ->
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)
val jsonStrData = jsoup.selectFirst("script:containsData(const seasonUrl =)")?.data() ?: return emptyList()
val jsonParse = json.decodeFromString<JsonObject>(jsonStrData.substringAfter("seasonsJson = ").substringBefore(";"))
var index = 0
jsonParse.entries.map {
it.value.jsonArray.reversed().map { element ->
index += 1
val jsonElement = element!!.jsonObject
val season = jsonElement["season"]!!.jsonPrimitive!!.content
val title = jsonElement["title"]!!.jsonPrimitive!!.content
val ep = jsonElement["episode"]!!.jsonPrimitive!!.content
val jsonElement = element.jsonObject
val season = jsonElement["season"]!!.jsonPrimitive.content
val title = jsonElement["title"]!!.jsonPrimitive.content
val ep = jsonElement["episode"]!!.jsonPrimitive.content
val episode = SEpisode.create().apply {
episode_number = index.toFloat()
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 {
extName = 'Oploverz'
extClass = '.Oploverz'
extVersionCode = 26
extVersionCode = 27
}
apply from: "$rootDir/common.gradle"

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.id.oploverz
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
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.online.AnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.Response
import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -39,28 +37,28 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular ===============================
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 =
getAnimeParse(response, "div.relat > article")
getAnimeParse(response, "article[itemscope=itemscope]")
// =============================== Latest ===============================
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 =
getAnimeParse(response, "div.relat > article")
getAnimeParse(response, "article[itemscope=itemscope]")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
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 =
getAnimeParse(response, "div.relat > article")
getAnimeParse(response, "article[itemscope=itemscope]")
// ============================== Filters ===============================
@ -70,16 +68,16 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun animeDetailsParse(response: Response): SAnime {
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 {
author = detail.getInfo("Studio")
status = parseStatus(doc.selectFirst("div.alternati > span:nth-child(2)")!!.text())
title = doc.selectFirst("div.title > h1.entry-title")!!.text()
status = parseStatus(detail.getInfo("Status"))
title = doc.selectFirst("h1.entry-title")!!.text()
thumbnail_url =
doc.selectFirst("div.infoanime.widget_senction > div.thumb > img")!!
doc.selectFirst("div.thumb > img")!!
.attr("src")
description =
doc.select("div.entry-content.entry-content-single > p")
doc.select("div.entry-content > p")
.joinToString("\n\n") { it.text() }
}
}
@ -88,13 +86,13 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun episodeListParse(response: Response): List<SEpisode> {
val doc = response.asJsoup()
return doc.select("div.lstepsiode.listeps > ul.scrolling > li").map {
val episode = it.selectFirst("span.eps > a")!!
return doc.select("div.eplister > ul > li").map {
val episode = it.selectFirst("a")!!
SEpisode.create().apply {
setUrlWithoutDomain(episode.attr("href"))
episode_number = episode.text().trim().toFloatOrNull() ?: 1F
name = it.selectFirst("span.lchx > a")!!.text()
date_upload = it.selectFirst("span.date")!!.text().toDate()
episode_number = it.selectFirst("div.epl-num")!!.text().toFloatOrNull() ?: 1F
name = it.selectFirst("div.epl-title")!!.text()
date_upload = it.selectFirst("div.epl-date")!!.text().toDate()
}
}
}
@ -103,16 +101,26 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> {
val doc = response.asJsoup()
val parseUrl = response.request.url.toUrl()
val url = "${parseUrl.protocol}://${parseUrl.host}"
return doc.select("#server > ul > li > div.east_player_option")
.parallelMapNotNullBlocking {
runCatching { getEmbedLinks(url, it) }.getOrNull()
val videoList = mutableListOf<Video>()
doc.select("select.mirror > option[value]").forEach { opt ->
val decoded = if (opt.attr("value").isEmpty()) {
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 ==============================
@ -140,19 +148,14 @@ class Oploverz : ConfigurableAnimeSource, AnimeHttpSource() {
val doc = response.asJsoup()
val animes = doc.select(query).map {
SAnime.create().apply {
setUrlWithoutDomain(it.selectFirst("div.animposx > a")!!.attr("href"))
title = it.selectFirst("div.title > h2")!!.text()
thumbnail_url = it.selectFirst("div.content-thumb > img")!!.attr("src")
setUrlWithoutDomain(it.selectFirst("a.tip")!!.attr("href"))
title = it.selectFirst("div.tt > h2")!!.text()
thumbnail_url = it.selectFirst("div.limit > img")!!.attr("src")
}
}
val hasNextPage = try {
val pagination = doc.selectFirst("div.pagination")!!
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
}
val hasNextPage = doc.selectFirst("a.next.page-numbers") != null ?: doc.selectFirst("div.hpage > a.r")
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> {
return when {
"blogger" in link -> {

View file

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

View file

@ -30,7 +30,7 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "StreamingCommunity"
override val baseUrl = "https://streamingcommunity.forum"
override val baseUrl = "https://streamingcommunity.photos"
override val lang = "it"
@ -254,72 +254,40 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================ Video Links =============================
override suspend fun getVideoList(episode: SEpisode): List<Video> {
val videoList = mutableListOf<Video>()
val doc =
client
.newCall(
val videoSet = mutableSetOf<Video>()
val doc = client.newCall(
GET("$baseUrl/iframe/${episode.url}", headers),
).execute()
.asJsoup()
val iframeUrl =
doc.selectFirst("iframe[src]")?.attr("abs:src")
).execute().asJsoup()
val iframeUrl = doc.selectFirst("iframe[src]")?.attr("abs:src")
?: error("Failed to extract iframe")
val iframeHeaders =
headers
.newBuilder()
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 =
client
.newCall(
GET(iframeUrl, headers = iframeHeaders),
).execute()
.asJsoup()
val iframe = client.newCall(GET(iframeUrl, headers = iframeHeaders)).execute().asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylist)")!!.data().replace("\n", "\t")
var 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 playlistUrl = PLAYLIST_URL_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&n=1"
val masterPl =
client
.newCall(GET(masterPlUrl))
.execute()
.body
.string()
val subList =
SUBTITLES_REGEX.findAll(masterPl)
.map {
val masterPlUrl = "$playlistUrl&token=$token&expires=$expires&b=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 =
buildString {
append(playlistUrl)
append("?type=video&rendition=")
append(quality)
append("&token=")
append(match.groupValues[2])
append("&expires=$expires")
append("&n=1")
}
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
QUALITY_REGEX.findAll(masterPl).forEach { match ->
val quality = "${match.groupValues[1]}p"
val videoUrl = match.groupValues[2]
videoSet.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
}
require(videoSet.isNotEmpty()) { "Failed to fetch videos" }
require(videoList.isNotEmpty()) { "Failed to fetch videos" }
return videoList.sort()
return videoSet.toList().sortedBy { it.quality }
}
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 EXPIRES_REGEX = Regex("""'expires': ?'(\d+)'""")
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 const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "720"
private const val PREF_QUALITY_DEFAULT = "1080"
}
// ============================== Settings ==============================