Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,32 @@ jobs:
- name: Checkout builds
uses: actions/checkout@master
with:
ref: "builds"
ref: "build"
path: "builds"

- name: Clean old builds
run: rm $GITHUB_WORKSPACE/builds/*.cs3
run: rm $GITHUB_WORKSPACE/build/*.cs3 || true

- name: Setup JDK 17
uses: actions/setup-java@v1
- name: Setup JDK 21
uses: actions/setup-java@v3
with:
java-version: 17
java-version: 21
distribution: temurin

- name: Setup Android SDK
uses: android-actions/setup-android@v2


- name: Remove JVM properties
run: rm -f $GITHUB_WORKSPACE/src/gradle/gradle-daemon-jvm.properties

- name: Build Plugins
run: |
cd $GITHUB_WORKSPACE/src
chmod +x gradlew
./gradlew make makePluginsJson
cp **/build/*.cs3 $GITHUB_WORKSPACE/builds
cp build/plugins.json $GITHUB_WORKSPACE/builds
find . -name "*.cs3" -type f
cp **/build/*.cs3 $GITHUB_WORKSPACE/build
cp build/plugins.json $GITHUB_WORKSPACE/build

- name: Push builds
run: |
Expand Down
94 changes: 0 additions & 94 deletions ExampleProvider/src/main/kotlin/com/example/BlankFragment.kt

This file was deleted.

18 changes: 3 additions & 15 deletions ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,8 @@ import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
import com.lagradost.cloudstream3.plugins.Plugin

@CloudstreamPlugin
class ExamplePlugin: Plugin() {
private var activity: AppCompatActivity? = null

class TuDoramaPlugin : Plugin() {
override fun load(context: Context) {
activity = context as? AppCompatActivity

// All providers should be added in this manner
registerMainAPI(ExampleProvider())

openSettings = {
val frag = BlankFragment(this)
activity?.let {
frag.show(it.supportFragmentManager, "Frag")
}
}
registerMainAPI(TuDoramaProvider())
}
}
}
110 changes: 98 additions & 12 deletions ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,107 @@
package com.example

import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import org.jsoup.nodes.Element

class ExampleProvider : MainAPI() { // All providers must be an instance of MainAPI
override var mainUrl = "https://example.com/"
override var name = "Example provider"
override val supportedTypes = setOf(TvType.Movie)
class TuDoramaProvider : MainAPI() {
override var mainUrl = "https://tudorama.com"
override var name = "TuDorama"
override val supportedTypes = setOf(TvType.AsianDrama)
override var lang = "es"
override val hasMainPage = true

override var lang = "en"
// Categorías que aparecen en el home de la app
override val mainPage = mainPageOf(
"$mainUrl/genero/series/page/" to "K-Dramas",
"$mainUrl/genero/cdrama/page/" to "C-Dramas",
"$mainUrl/genero/jdrama/page/" to "J-Dramas",
"$mainUrl/genero/emision/page/" to "En Emisión",
"$mainUrl/genero/peliculas/page/" to "Películas",
)

// Enable this when your provider has a main page
override val hasMainPage = true
// Convierte un elemento HTML de la lista en un SearchResponse
private fun Element.toSearchResponse(): SearchResponse? {
val title = this.selectFirst("h3 a, h2 a")?.text() ?: return null
val href = this.selectFirst("a")?.attr("href") ?: return null
val poster = this.selectFirst("img")?.attr("src")
return newTvSeriesSearchResponse(title, href, TvType.AsianDrama) {
this.posterUrl = poster
}
}

// Carga cada categoría del home con paginación
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
val url = "${request.data}$page/"
val document = app.get(url).document
val items = document.select("article, div.item, div.TPost")
.mapNotNull { it.toSearchResponse() }
return newHomePageResponse(request.name, items)
}

// This function gets called when you search for something
// Búsqueda
override suspend fun search(query: String): List<SearchResponse> {
return listOf()
val document = app.get("$mainUrl/?s=$query").document
return document.select("article, div.item, div.TPost")
.mapNotNull { it.toSearchResponse() }
}

// Página de detalle de la serie
override suspend fun load(url: String): LoadResponse {
val document = app.get(url).document

val title = document.selectFirst("h1, h2.Title")?.text() ?: "Sin título"
val poster = document.selectFirst("div.Image img, img.Poster")?.attr("src")
val plot = document.selectFirst("div.Description p, div.sinopsis")?.text()
val tags = document.select("div.Genre a, a[href*='/genres/']").map { it.text() }
val year = document.selectFirst("span.Date, span.year")?.text()?.trim()?.toIntOrNull()

// Scrapea los episodios
val episodes = document.select("li a[href*='/ver/']").mapNotNull { el ->
val epUrl = el.attr("href")
val epName = el.selectFirst("h3, span")?.text() ?: el.text()
// Intenta extraer numero de temporada y episodio de la URL
val match = Regex("""s(\d+)x(\d+)""").find(epUrl)
val season = match?.groupValues?.get(1)?.toIntOrNull() ?: 1
val epNum = match?.groupValues?.get(2)?.toIntOrNull()
newEpisode(epUrl) {
this.name = epName
this.season = season
this.episode = epNum
}
}

return newTvSeriesLoadResponse(title, url, TvType.AsianDrama, episodes) {
this.posterUrl = poster
this.plot = plot
this.tags = tags
this.year = year
}
}

// Extrae los links de video del episodio
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val document = app.get(data).document

// Busca los iframes del reproductor
document.select("iframe").forEach { iframe ->
val src = iframe.attr("src").ifEmpty { iframe.attr("data-src") }
if (src.isNotEmpty()) {
loadExtractor(src, data, subtitleCallback, callback)
}
}

// También intenta los links de descarga directa como fallback
document.select("table a[href*='cdn.tudorama.com']").forEach { link ->
val href = link.attr("href")
loadExtractor(href, data, subtitleCallback, callback)
}

return true
}
}
12 changes: 8 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import com.android.build.gradle.BaseExtension
import com.lagradost.cloudstream3.gradle.CloudstreamExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("org.jetbrains.kotlin.android") version "2.3.21" apply false
}

buildscript {
repositories {
Expand All @@ -15,7 +19,6 @@ buildscript {
classpath("com.android.tools.build:gradle:8.7.3")
// Cloudstream gradle plugin which makes everything work and builds plugins
classpath("com.github.recloudstream:gradle:-SNAPSHOT")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0")
}
}

Expand Down Expand Up @@ -55,13 +58,14 @@ subprojects {
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<KotlinJvmCompile> {
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8) // Required
freeCompilerArgs.addAll(
"-Xno-call-assertions",
"-Xno-param-assertions",
"-Xno-receiver-assertions"
"-Xno-receiver-assertions",
"-Xskip-metadata-version-check"
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions gradle/gradle-daemon-jvm.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

toolchainVendor=jetbrains
toolchainVersion=21
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
}
rootProject.name = "CloudstreamPlugins"

// This file sets what projects are included.
Expand Down