Make the plugin part crossplatform (#1527)

This commit is contained in:
CranberrySoup
2025-01-30 17:39:27 +00:00
committed by GitHub
parent b95e630bd8
commit 7b60246e28
6 changed files with 101 additions and 71 deletions

View File

@ -2,56 +2,21 @@ package com.lagradost.cloudstream3.plugins
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import kotlin.Throws
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import android.util.Log import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.actions.VideoClickAction
import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.actions.VideoClickActionHolder
import kotlin.Throws
const val PLUGIN_TAG = "PluginInstance"
abstract class Plugin { abstract class Plugin : BasePlugin() {
/** /**
* Called when your Plugin is loaded * Called when your Plugin is loaded
* @param context Context * @param context Context
*/ */
@Throws(Throwable::class) @Throws(Throwable::class)
open fun load(context: Context) { open fun load(context: Context) {
} // If not overridden by an extension then try the cross-platform load()
load()
/**
* Called when your Plugin is being unloaded
*/
@Throws(Throwable::class)
open fun beforeUnload() {
}
/**
* Used to register providers instances of MainAPI
* @param element MainAPI provider you want to register
*/
fun registerMainAPI(element: MainAPI) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
element.sourcePlugin = this.filename
// Race condition causing which would case duplicates if not for distinctBy
synchronized(APIHolder.allProviders) {
APIHolder.allProviders.add(element)
}
APIHolder.addPluginMapping(element)
}
/**
* Used to register extractor instances of ExtractorApi
* @param element ExtractorApi provider you want to register
*/
fun registerExtractorAPI(element: ExtractorApi) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) ExtractorApi")
element.sourcePlugin = this.filename
extractorApis.add(element)
} }
/** /**
@ -66,27 +31,10 @@ abstract class Plugin {
} }
} }
class Manifest {
@JsonProperty("name")
var name: String? = null
@JsonProperty("pluginClassName")
var pluginClassName: String? = null
@JsonProperty("version")
var version: Int? = null
@JsonProperty("requiresResources")
var requiresResources: Boolean = false
}
/** /**
* This will contain your resources if you specified requiresResources in gradle * This will contain your resources if you specified requiresResources in gradle
*/ */
var resources: Resources? = null var resources: Resources? = null
/** Full file path to the plugin. */
@Deprecated("Renamed to `filename` to follow conventions", replaceWith = ReplaceWith("filename"))
var __filename: String?
get() = filename
set(value) {filename = value}
var filename: String? = null
/** /**
* This will add a button in the settings allowing you to add custom settings * This will add a button in the settings allowing you to add custom settings

View File

@ -172,15 +172,15 @@ object PluginManager {
var currentlyLoading: String? = null var currentlyLoading: String? = null
// Maps filepath to plugin // Maps filepath to plugin
val plugins: MutableMap<String, Plugin> = val plugins: MutableMap<String, BasePlugin> =
LinkedHashMap<String, Plugin>() LinkedHashMap<String, BasePlugin>()
// Maps urls to plugin // Maps urls to plugin
val urlPlugins: MutableMap<String, Plugin> = val urlPlugins: MutableMap<String, BasePlugin> =
LinkedHashMap<String, Plugin>() LinkedHashMap<String, BasePlugin>()
private val classLoaders: MutableMap<PathClassLoader, Plugin> = private val classLoaders: MutableMap<PathClassLoader, BasePlugin> =
HashMap<PathClassLoader, Plugin>() HashMap<PathClassLoader, BasePlugin>()
var loadedLocalPlugins = false var loadedLocalPlugins = false
private set private set
@ -530,7 +530,7 @@ object PluginManager {
} }
val loader = PathClassLoader(filePath, context.classLoader) val loader = PathClassLoader(filePath, context.classLoader)
var manifest: Plugin.Manifest var manifest: BasePlugin.Manifest
loader.getResourceAsStream("manifest.json").use { stream -> loader.getResourceAsStream("manifest.json").use { stream ->
if (stream == null) { if (stream == null) {
Log.e(TAG, "Failed to load plugin $fileName: No manifest found") Log.e(TAG, "Failed to load plugin $fileName: No manifest found")
@ -539,7 +539,7 @@ object PluginManager {
InputStreamReader(stream).use { reader -> InputStreamReader(stream).use { reader ->
manifest = gson.fromJson( manifest = gson.fromJson(
reader, reader,
Plugin.Manifest::class.java BasePlugin.Manifest::class.java
) )
} }
} }
@ -553,9 +553,9 @@ object PluginManager {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val pluginClass: Class<*> = val pluginClass: Class<*> =
loader.loadClass(manifest.pluginClassName) as Class<out Plugin?> loader.loadClass(manifest.pluginClassName) as Class<out BasePlugin?>
val pluginInstance: Plugin = val pluginInstance: BasePlugin =
pluginClass.getDeclaredConstructor().newInstance() as Plugin pluginClass.getDeclaredConstructor().newInstance() as BasePlugin
// Sets with the proper version // Sets with the proper version
setPluginData(data.copy(version = version)) setPluginData(data.copy(version = version))
@ -575,7 +575,7 @@ object PluginManager {
addAssetPath.invoke(assets, file.absolutePath) addAssetPath.invoke(assets, file.absolutePath)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
pluginInstance.resources = Resources( (pluginInstance as? Plugin)?.resources = Resources(
assets, assets,
context.resources.displayMetrics, context.resources.displayMetrics,
context.resources.configuration context.resources.configuration
@ -584,7 +584,11 @@ object PluginManager {
plugins[filePath] = pluginInstance plugins[filePath] = pluginInstance
classLoaders[loader] = pluginInstance classLoaders[loader] = pluginInstance
urlPlugins[data.url ?: filePath] = pluginInstance urlPlugins[data.url ?: filePath] = pluginInstance
pluginInstance.load(context) if (pluginInstance is Plugin) {
pluginInstance.load(context)
} else {
pluginInstance.load()
}
Log.i(TAG, "Loaded plugin ${data.internalName} successfully") Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
currentlyLoading = null currentlyLoading = null
true true

View File

@ -176,7 +176,8 @@ class PluginAdapter(
if (data.isDownloaded) { if (data.isDownloaded) {
// On local plugins page the filepath is provided instead of url. // On local plugins page the filepath is provided instead of url.
val plugin = val plugin =
PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] (PluginManager.urlPlugins[metadata.url] ?: (PluginManager.plugins[metadata.url])) as? com.lagradost.cloudstream3.plugins.Plugin
if (plugin?.openSettings != null) { if (plugin?.openSettings != null) {
binding.actionSettings.isVisible = true binding.actionSettings.isVisible = true
binding.actionSettings.setOnClickListener { binding.actionSettings.setOnClickListener {

View File

@ -102,7 +102,7 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
if (data.isDownloaded) { if (data.isDownloaded) {
// On local plugins page the filepath is provided instead of url. // On local plugins page the filepath is provided instead of url.
val plugin = val plugin =
PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] (PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url]) as? com.lagradost.cloudstream3.plugins.Plugin
if (plugin?.openSettings != null && context != null) { if (plugin?.openSettings != null && context != null) {
actionSettings.isVisible = true actionSettings.isVisible = true
actionSettings.setOnClickListener { actionSettings.setOnClickListener {

View File

@ -0,0 +1,77 @@
package com.lagradost.cloudstream3.plugins
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.api.Log
import com.lagradost.cloudstream3.utils.extractorApis
const val PLUGIN_TAG = "PluginInstance"
abstract class BasePlugin {
/**
* Used to register providers instances of MainAPI
* @param element MainAPI provider you want to register
*/
fun registerMainAPI(element: MainAPI) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
element.sourcePlugin = this.filename
// Race condition causing which would case duplicates if not for distinctBy
synchronized(APIHolder.allProviders) {
APIHolder.allProviders.add(element)
}
APIHolder.addPluginMapping(element)
}
/**
* Used to register extractor instances of ExtractorApi
* @param element ExtractorApi provider you want to register
*/
fun registerExtractorAPI(element: ExtractorApi) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) ExtractorApi")
element.sourcePlugin = this.filename
extractorApis.add(element)
}
/**
* Called when your Plugin is being unloaded
*/
@Throws(Throwable::class)
open fun beforeUnload() {
}
/**
* Called when your Plugin is loaded
*/
@Throws(Throwable::class)
open fun load() {
}
/** Full file path to the plugin. */
@Deprecated(
"Renamed to `filename` to follow conventions",
replaceWith = ReplaceWith("filename")
)
var __filename: String?
get() = filename
set(value) {
filename = value
}
var filename: String? = null
class Manifest {
@JsonProperty("name")
var name: String? = null
@JsonProperty("pluginClassName")
var pluginClassName: String? = null
@JsonProperty("version")
var version: Int? = null
@JsonProperty("requiresResources")
var requiresResources: Boolean = false
}
}