New Source: Cineplus123 (es) (#107)
22
src/es/cineplus123/AndroidManifest.xml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".es.cineplus123.Cineplus123UrlActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="cineplus123.org"
|
||||||
|
android:pathPattern="/anime/..*"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
14
src/es/cineplus123/build.gradle
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
ext {
|
||||||
|
extName = 'Cineplus123'
|
||||||
|
extClass = '.Cineplus123'
|
||||||
|
themePkg = 'dooplay'
|
||||||
|
baseUrl = 'https://cineplus123.org'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":lib:streamwish-extractor"))
|
||||||
|
implementation(project(":lib:uqload-extractor"))
|
||||||
|
}
|
BIN
src/es/cineplus123/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
src/es/cineplus123/res/mipmap-hdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
src/es/cineplus123/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/es/cineplus123/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/es/cineplus123/res/mipmap-mdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/es/cineplus123/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/es/cineplus123/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 17 KiB |
BIN
src/es/cineplus123/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
BIN
src/es/cineplus123/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 52 KiB |
|
@ -0,0 +1,196 @@
|
||||||
|
package eu.kanade.tachiyomi.animeextension.es.cineplus123
|
||||||
|
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
|
||||||
|
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
|
||||||
|
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
class Cineplus123 : DooPlay(
|
||||||
|
"es",
|
||||||
|
"Cineplus123",
|
||||||
|
"https://cineplus123.org",
|
||||||
|
) {
|
||||||
|
// ============================== Popular ===============================
|
||||||
|
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/tendencias/$page")
|
||||||
|
|
||||||
|
override fun popularAnimeSelector() = latestUpdatesSelector()
|
||||||
|
|
||||||
|
override fun popularAnimeNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/ano/2024/page/$page", headers)
|
||||||
|
|
||||||
|
override fun videoListSelector() = "li.dooplay_player_option" // ul#playeroptionsul
|
||||||
|
|
||||||
|
override val episodeMovieText = "Película"
|
||||||
|
|
||||||
|
override val episodeSeasonPrefix = "Temporada"
|
||||||
|
override val prefQualityTitle = "Calidad preferida"
|
||||||
|
|
||||||
|
private val uqloadExtractor by lazy { UqloadExtractor(client) }
|
||||||
|
private val streamWishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||||
|
|
||||||
|
// ============================ Video Links =============================
|
||||||
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val players = document.select("ul#playeroptionsul li")
|
||||||
|
return players.parallelFlatMapBlocking { player ->
|
||||||
|
val name = player.selectFirst("span.title")!!.text()
|
||||||
|
val url = getPlayerUrl(player)
|
||||||
|
?: return@parallelFlatMapBlocking emptyList<Video>()
|
||||||
|
extractVideos(url, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractVideos(url: String, lang: String): List<Video> {
|
||||||
|
return when {
|
||||||
|
"uqload" in url -> uqloadExtractor.videosFromUrl(url, "$lang -")
|
||||||
|
"strwish" in url -> streamWishExtractor.videosFromUrl(url, lang)
|
||||||
|
else -> null
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPlayerUrl(player: Element): String? {
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("action", "doo_player_ajax")
|
||||||
|
.add("post", player.attr("data-post"))
|
||||||
|
.add("nume", player.attr("data-nume"))
|
||||||
|
.add("type", player.attr("data-type"))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||||
|
.execute().body.string()
|
||||||
|
.substringAfter("\"embed_url\":\"")
|
||||||
|
.substringBefore("\",")
|
||||||
|
.replace("\\", "")
|
||||||
|
.takeIf(String::isNotBlank)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Filters ===============================
|
||||||
|
override val fetchGenres = false
|
||||||
|
|
||||||
|
override fun getFilterList() = Cineplus123Filters.FILTER_LIST
|
||||||
|
|
||||||
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
|
val params = Cineplus123Filters.getSearchParameters(filters)
|
||||||
|
val path = when {
|
||||||
|
params.genre.isNotBlank() -> {
|
||||||
|
if (params.genre in listOf("tendencias", "ratings", "series-de-tv", "peliculas")) {
|
||||||
|
"/${params.genre}"
|
||||||
|
} else {
|
||||||
|
"/genero/${params.genre}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.language.isNotBlank() -> "/genero/${params.language}"
|
||||||
|
params.year.isNotBlank() -> "/ano/${params.year}"
|
||||||
|
params.movie.isNotBlank() -> {
|
||||||
|
if (params.movie == "Peliculas") {
|
||||||
|
"/peliculas"
|
||||||
|
} else {
|
||||||
|
"/genero/${params.movie}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> buildString {
|
||||||
|
append(
|
||||||
|
when {
|
||||||
|
query.isNotBlank() -> "/?s=$query"
|
||||||
|
else -> "/"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
append(
|
||||||
|
when (params.type) {
|
||||||
|
"serie" -> "serie-de-tv"
|
||||||
|
"pelicula" -> "peliculas"
|
||||||
|
else -> "tendencias"
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
if (params.isInverted) append("&orden=asc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (path.startsWith("/?s=")) {
|
||||||
|
GET("$baseUrl/page/$page$path")
|
||||||
|
} else {
|
||||||
|
GET("$baseUrl$path/page/$page")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
super.setupPreferenceScreen(screen) // Quality preference
|
||||||
|
|
||||||
|
val langPref = ListPreference(screen.context).apply {
|
||||||
|
key = PREF_LANG_KEY
|
||||||
|
title = PREF_LANG_TITLE
|
||||||
|
entries = PREF_LANG_ENTRIES
|
||||||
|
entryValues = PREF_LANG_VALUES
|
||||||
|
setDefaultValue(PREF_LANG_DEFAULT)
|
||||||
|
summary = "%s"
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = findIndexOfValue(selected)
|
||||||
|
val entry = entryValues[index] as String
|
||||||
|
preferences.edit().putString(key, entry).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = PREF_SERVER_KEY
|
||||||
|
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)
|
||||||
|
screen.addPreference(langPref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================= Utilities ==============================
|
||||||
|
override fun String.toDate() = 0L
|
||||||
|
|
||||||
|
override fun List<Video>.sort(): List<Video> {
|
||||||
|
val quality = preferences.getString(prefQualityKey, prefQualityDefault)!!
|
||||||
|
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||||
|
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
|
||||||
|
return sortedWith(
|
||||||
|
compareBy(
|
||||||
|
{ it.quality.contains(lang) },
|
||||||
|
{ it.quality.contains(server, true) },
|
||||||
|
{ it.quality.contains(quality) },
|
||||||
|
),
|
||||||
|
).reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val prefQualityValues = arrayOf("480p", "720p", "1080p")
|
||||||
|
override val prefQualityEntries = prefQualityValues
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREF_LANG_KEY = "preferred_lang"
|
||||||
|
private const val PREF_LANG_TITLE = "Preferred language"
|
||||||
|
private const val PREF_LANG_DEFAULT = "LATINO"
|
||||||
|
private const val PREF_SERVER_KEY = "preferred_server"
|
||||||
|
private const val PREF_SERVER_DEFAULT = "Uqload"
|
||||||
|
private val PREF_LANG_ENTRIES = arrayOf("SUBTITULADO", "LATINO", "CASTELLANO")
|
||||||
|
private val PREF_LANG_VALUES = arrayOf("SUBTITULADO", "LATINO", "CASTELLANO")
|
||||||
|
private val SERVER_LIST = arrayOf("StreamWish", "Uqload")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package eu.kanade.tachiyomi.animeextension.es.cineplus123
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
|
||||||
|
object Cineplus123Filters {
|
||||||
|
|
||||||
|
open class UriPartFilter(
|
||||||
|
displayName: String,
|
||||||
|
private val vals: Array<Pair<String, String>>,
|
||||||
|
) : AnimeFilter.Select<String>(
|
||||||
|
displayName,
|
||||||
|
vals.map { it.first }.toTypedArray(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun toUriPart() = vals[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilterList.getFirst(): R {
|
||||||
|
return first { it is R } as R
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilterList.asUriPart(): String {
|
||||||
|
return getFirst<R>().let {
|
||||||
|
(it as UriPartFilter).toUriPart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvertedResultsFilter : AnimeFilter.CheckBox("Invertir resultados", false)
|
||||||
|
class TypeFilter : UriPartFilter("Tipo", AnimesOnlineNinjaData.TYPES)
|
||||||
|
|
||||||
|
class GenreFilter : UriPartFilter("Generos", AnimesOnlineNinjaData.GENRES)
|
||||||
|
class LanguageFilter : UriPartFilter("Idiomas", AnimesOnlineNinjaData.LANGUAGES)
|
||||||
|
class YearFilter : UriPartFilter("Año", AnimesOnlineNinjaData.YEARS)
|
||||||
|
class MovieFilter : UriPartFilter("Peliculas", AnimesOnlineNinjaData.MOVIES)
|
||||||
|
|
||||||
|
class OtherOptionsGroup : AnimeFilter.Group<UriPartFilter>(
|
||||||
|
"Otros filtros",
|
||||||
|
listOf(
|
||||||
|
GenreFilter(),
|
||||||
|
LanguageFilter(),
|
||||||
|
YearFilter(),
|
||||||
|
MovieFilter(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilter.Group<UriPartFilter>.getItemUri(): String {
|
||||||
|
return state.first { it is R }.toUriPart()
|
||||||
|
}
|
||||||
|
|
||||||
|
val FILTER_LIST get() = AnimeFilterList(
|
||||||
|
InvertedResultsFilter(),
|
||||||
|
TypeFilter(),
|
||||||
|
AnimeFilter.Separator(),
|
||||||
|
AnimeFilter.Header("Estos filtros no afectan a la busqueda por texto"),
|
||||||
|
OtherOptionsGroup(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FilterSearchParams(
|
||||||
|
val isInverted: Boolean = false,
|
||||||
|
val type: String = "",
|
||||||
|
val genre: String = "",
|
||||||
|
val language: String = "",
|
||||||
|
val year: String = "",
|
||||||
|
val movie: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||||
|
if (filters.isEmpty()) return FilterSearchParams()
|
||||||
|
|
||||||
|
val others = filters.getFirst<OtherOptionsGroup>()
|
||||||
|
|
||||||
|
return FilterSearchParams(
|
||||||
|
filters.getFirst<InvertedResultsFilter>().state,
|
||||||
|
filters.asUriPart<TypeFilter>(),
|
||||||
|
others.getItemUri<GenreFilter>(),
|
||||||
|
others.getItemUri<LanguageFilter>(),
|
||||||
|
others.getItemUri<YearFilter>(),
|
||||||
|
others.getItemUri<MovieFilter>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object AnimesOnlineNinjaData {
|
||||||
|
val EVERY = Pair("Seleccionar", "")
|
||||||
|
|
||||||
|
val TYPES = arrayOf(
|
||||||
|
Pair("Todos", "todos"),
|
||||||
|
Pair("Series", "serie"),
|
||||||
|
Pair("Peliculas", "pelicula"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val GENRES = arrayOf(
|
||||||
|
EVERY,
|
||||||
|
Pair("accion", "accion"),
|
||||||
|
Pair("action-adventure", "action-adventure"),
|
||||||
|
Pair("animacion", "animacion"),
|
||||||
|
Pair("aventura", "aventura"),
|
||||||
|
Pair("bajalogratis", "bajalogratis"),
|
||||||
|
Pair("belica", "belica"),
|
||||||
|
Pair("ciencia-ficcion", "ciencia-ficcion"),
|
||||||
|
Pair("comedia", "comedia"),
|
||||||
|
Pair("crimen", "crimen"),
|
||||||
|
Pair("disney", "disney"),
|
||||||
|
Pair("documental", "documental"),
|
||||||
|
Pair("don-torrent", "don-torrent"),
|
||||||
|
Pair("drama", "drama"),
|
||||||
|
Pair("familia", "familia"),
|
||||||
|
Pair("fantasia", "fantasia"),
|
||||||
|
Pair("gran-torrent", "gran-torrent"),
|
||||||
|
Pair("hbo", "hbo"),
|
||||||
|
Pair("historia", "historia"),
|
||||||
|
Pair("kids", "kids"),
|
||||||
|
Pair("misterio", "misterio"),
|
||||||
|
Pair("musica", "musica"),
|
||||||
|
Pair("romance", "romance"),
|
||||||
|
Pair("sci-fi-fantasy", "sci-fi-fantasy"),
|
||||||
|
Pair("series-de-amazon-prime-video", "series-de-amazon-prime-video"),
|
||||||
|
Pair("soap", "soap"),
|
||||||
|
Pair("suspense", "suspense"),
|
||||||
|
Pair("talk", "talk"),
|
||||||
|
Pair("terror", "terror"),
|
||||||
|
Pair("war-politics", "war-politics"),
|
||||||
|
Pair("western", "western"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val LANGUAGES = arrayOf(
|
||||||
|
EVERY,
|
||||||
|
Pair("latino", "latino"),
|
||||||
|
Pair("castellano", "castellano"),
|
||||||
|
Pair("subtitulado", "subtitulado"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val YEARS = arrayOf(EVERY) + (2024 downTo 1979).map {
|
||||||
|
Pair(it.toString(), it.toString())
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
val MOVIES = arrayOf(
|
||||||
|
EVERY,
|
||||||
|
Pair("pelicula", "pelicula"),
|
||||||
|
Pair("series", "series de tv"),
|
||||||
|
Pair("pelicula-de-tv", "pelicula-de-tv"),
|
||||||
|
Pair("peliculas-cristianas", "peliculas-cristianas"),
|
||||||
|
Pair("peliculas-de-halloween", "peliculas-de-halloween"),
|
||||||
|
Pair("peliculas-de-navidad", "peliculas-de-navidad"),
|
||||||
|
Pair("peliculas-para-el-dia-de-la-madre", "peliculas-para-el-dia-de-la-madre"),
|
||||||
|
Pair("pelis-play", "pelis-play"),
|
||||||
|
Pair("pelishouse", "pelishouse"),
|
||||||
|
Pair("pelismart-tv", "pelismart-tv"),
|
||||||
|
Pair("pelisnow", "pelisnow"),
|
||||||
|
Pair("pelix-tv", "pelix-tv"),
|
||||||
|
Pair("poseidonhd", "poseidonhd"),
|
||||||
|
Pair("proximamente", "proximamente"),
|
||||||
|
Pair("reality", "reality"),
|
||||||
|
Pair("repelis-go", "repelis-go"),
|
||||||
|
Pair("repelishd-tv", "repelishd-tv"),
|
||||||
|
Pair("repelisplus", "repelisplus"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|