Skip to content

Commit

Permalink
Feat(src/es): New Source: Cineplus123
Browse files Browse the repository at this point in the history
  • Loading branch information
Dark25 committed Jul 30, 2024
1 parent 502dbd0 commit c930b98
Show file tree
Hide file tree
Showing 19 changed files with 391 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/es/cineplus123/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions src/es/cineplus123/build.gradle
Original file line number Diff line number Diff line change
@@ -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"))
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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"),
)
}
}

0 comments on commit c930b98

Please sign in to comment.