diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff32133adb..720ed5f7f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: | diff --git a/ExampleProvider/src/main/kotlin/com/example/BlankFragment.kt b/ExampleProvider/src/main/kotlin/com/example/BlankFragment.kt deleted file mode 100644 index 3d35a883cf..0000000000 --- a/ExampleProvider/src/main/kotlin/com/example/BlankFragment.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.example - -import android.annotation.SuppressLint -import android.content.res.ColorStateList -import android.graphics.drawable.Drawable -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.annotation.RequiresApi -import androidx.core.content.res.ResourcesCompat -import androidx.core.widget.TextViewCompat -import androidx.fragment.app.Fragment -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute - -/** - * A simple [Fragment] subclass. - */ -class BlankFragment(private val plugin: ExamplePlugin) : BottomSheetDialogFragment() { - - // Helper function to get a drawable resource by name - @SuppressLint("DiscouragedApi") - @Suppress("SameParameterValue") - private fun getDrawable(name: String): Drawable? { - val id = plugin.resources?.getIdentifier(name, "drawable", BuildConfig.LIBRARY_PACKAGE_NAME) - return id?.let { ResourcesCompat.getDrawable(plugin.resources ?: return null, it, null) } - } - - // Helper function to get a string resource by name - @SuppressLint("DiscouragedApi") - @Suppress("SameParameterValue") - private fun getString(name: String): String? { - val id = plugin.resources?.getIdentifier(name, "string", BuildConfig.LIBRARY_PACKAGE_NAME) - return id?.let { plugin.resources?.getString(it) } - } - - // Generic findView function to find views by name - @SuppressLint("DiscouragedApi") - private fun View.findViewByName(name: String): T? { - val id = plugin.resources?.getIdentifier(name, "id", BuildConfig.LIBRARY_PACKAGE_NAME) - return findViewById(id ?: return null) - } - - @SuppressLint("DiscouragedApi") - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - val layoutId = plugin.resources?.getIdentifier("fragment_blank", "layout", BuildConfig.LIBRARY_PACKAGE_NAME) - return layoutId?.let { - inflater.inflate(plugin.resources?.getLayout(it), container, false) - } - } - - @RequiresApi(Build.VERSION_CODES.M) - override fun onViewCreated( - view: View, - savedInstanceState: Bundle? - ) { - super.onViewCreated(view, savedInstanceState) - - // Initialize views - val imageView: ImageView? = view.findViewByName("imageView") - val imageView2: ImageView? = view.findViewByName("imageView2") - val textView: TextView? = view.findViewByName("textView") - val textView2: TextView? = view.findViewByName("textView2") - - // Set text and styling if the views are found - textView?.apply { - text = getString("hello_fragment") - TextViewCompat.setTextAppearance(this, R.style.ResultInfoText) - } - - textView2?.text = view.context.resources.getText(R.string.legal_notice_text) - - // Set image resources and tint if the views are found - imageView?.apply { - setImageDrawable(getDrawable("ic_android_24dp")) - imageTintList = ColorStateList.valueOf(view.context.getColor(R.color.white)) - } - - imageView2?.apply { - setImageDrawable(getDrawable("ic_android_24dp")) - imageTintList = ColorStateList.valueOf(view.context.colorFromAttribute(R.attr.white)) - } - } -} \ No newline at end of file diff --git a/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt b/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt index 79fffa5ea3..62c16b0fc8 100644 --- a/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt +++ b/ExampleProvider/src/main/kotlin/com/example/ExamplePlugin.kt @@ -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()) } -} \ No newline at end of file +} diff --git a/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt b/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt index e788021aa8..68f555eb71 100644 --- a/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt +++ b/ExampleProvider/src/main/kotlin/com/example/ExampleProvider.kt @@ -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 { - 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 } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7dc3f4709a..98459c9dcb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 { @@ -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") } } @@ -55,13 +58,14 @@ subprojects { targetCompatibility = JavaVersion.VERSION_1_8 } - tasks.withType { + tasks.withType().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" ) } } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000000..6a74a46bce --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,3 @@ + +toolchainVendor=jetbrains +toolchainVersion=21 diff --git a/settings.gradle.kts b/settings.gradle.kts index c733a20898..b0aafe3fd4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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.