-
-
Notifications
You must be signed in to change notification settings - Fork 3
Plugin Dev Guide
Spooder has a growing list of integrations and a wide set of permissions are given to auth tokens to make the most out of plugins. I, the developer of Spooder, cannot be responsible for the development and/or installation of malicious plugins, but I do have some advice to keep you safe.
Download plugins from trustworthy sources like GitHub, popular indie stores (e.g. Ko-Fi, Gumroad, etc.), and your friends :)
If you have any skepticism about a plugin you're about to install. Upload it here for a free cloud based virus scan: https://www.virustotal.com/gui/home/upload
"With great power comes great responsibility"
— Stan Lee
These are NodeJS modules that are loaded at runtime with Spooder and have all of Spooder's integrations available. With these, you can install and/or experiment with unique stream features with complexity and capability that's not always possible for your average cloud based bot.
The list of capabilities is still growing. Plugins are able to take OSC, Twitch Chat, Twitch Events, Spooder Events, and Discord as input. The output can be a message to Twitch chat, a message to plugin overlays, communication with plugin utilities, and a message to Discord. You can make a simple chat plugin like my Lurker plugin. The Lurker has a default set of messages to pick randomly for those who call !lurk. Each user is recorded to a JSON file with their preference from the list. Subscribers can also set their own lurk message. One of my more complex plugins is the Animal Launcher. Which lets viewers customize an SVG by code with a little GUI hosted on CodePen and shoot them across the screen. Right now I'm developing a Discord overlay plugin called Discord Tuber. This uses an overlay to show voice chat users as avatars ranging from simple profile pictures with rings or full blown PNGTuber avatars. It's the first plugin to utilize both an overlay and a utility. Whereas the utility is an expression controller for those with advanced avatars.
Head to your Web UI, go to Plugins and click Create Plugin. Fill in the new plugin's name, your name as an author, and a little description. Click create and it'll clone the latest Sample Plugin from my GitHub and install the folders and files needed for the plugin.
If you haven't coded in Typescript. This is a good time to get into it. Type files are included for better Intellisense :3
Spooder's plugins folder contains the NodeJS modules to load in on the backend. The web folder contains all the web code, assets, and icons for each plugin.
You can use npm to install dependencies to your plugin as it comes with its own package.json. When exporting, the node_modules folder is excluded and dependencies are installed automatically after the plugin is installed. You can build your plugin into a single JS file to bundle your dependencies with your code when exporting.
The sample plugin shows and documents everything a plugin is capable of as of 0.5.0.
onChat(message: {
userId: string;
username: string;
displayName: string;
platform: string;
channel: string;
message: string;
messageType: string;
emotes: any[];
respond: (message: string) => void;
tags: KeyedObject;
isBroadcaster: boolean;
isMod: boolean;
isSubscriber: boolean;
isVIP: boolean;
isFirstMessage: boolean;
isReturningChatter: boolean;
triggeredEventData?: KeyedObject;
platformEventData?: KeyedObject;
pluginEventData?: KeyedObject;
} as StreamMessage)The three objects at the end of the StreamMessage type depend on what the StreamMessage has been through:
-
triggeredEventDatais the Spooder event object that got triggered by the StreamMessage -
platformEventDatais the object that comes from platform specific events like Twitch's eventsub -
pluginEventDatacomes from events calling a plugin function which will have the data entered from the events-form.json
This is for all streaming platforms (Just Twitch for now). Each message object contains {message, username, displayName, tags, and respond}. The respond function inside the message object is a simpler way of calling sayInChat(message, platform, channel) with the platform and channel already filled in. If your plugin is intended to be shared with other streamers, it's best to call message.respond(yourmessage) for your bot to respond at the appropriate channel.
onOSC(message: object)OSC from both TCP (Spooder's overlays) and UDP (Your software) are piped through here. The message object contains {address:string, args:any[]}.
onEvent(type: string, event: object)Here you can integrate with Spooder's own event system. Write cases for the "type" variable and use the event object as input. Create an events-form.json file to make templates.
onCommunityChat(type, data);This is for all community platforms (Just Discord for now). The type can be "message" or "voice". The data object will be output from Discord.js. This is subject to change to a more universal format for platforms much like onChat.
onLoad();This is a callback function where your plugin and settings have loaded successfully. It's best to start your plugin's operations here instead of a constructor. Settings can be accessed by this.settings anywhere in the plugin.
onDestroy();This function is called just before a plugin is unloaded like when Refresh Plugins is clicked on the Web UI. Use this for clearing timeouts and intervals for example. Those functions will still linger after the plugin has been unloaded.
These functions and variables are able to be called on the entry point of your plugin and are available as soon as onLoad is called. These can all be accessed at the entry file under this.
dirname: string;The internal name of the plugin as it is named by its folder.
modules: {
stream, community, control;
}
getModule(moduleName:string)A collection of modules that are currently active on the running Spooder. For example, to call the Twitch API in your plugin. Call getModule('twitch') to get access to the module's plugin functions.
activePlugins: [key:string]: PluginThe collection of active plugins currently running on the Spooder.
spooderConfig: {
ownerName, botName, host, hostPort, oscTcpPort, oscUdpPort, externalHandle;
}An object with the Spooder's configuration.
spooderTheme: {webui:{hue:float, saturation:float, isDarkTheme:boolean}, spooderPet: {partString:string, partColor:string}[]}The main theme created by the Spooder's owner.
osc: {
sendToTCP(address: string, oscValue: any, log?: boolean),
sendToUDP(client: string, address: string, oscValue: any, log?: boolean),
udpServers: [key:string]: {name: string, ip: string, port: number}
}OSC Info from the Spooder. Use sendToTCP to send messages to overlays and web interfaces. Use sendToUDP for the udpServers. The client parameter is a key inside the udpServers object.
public: {publicHostUrl: string, publicOscUrl: string}The Spooder's publicly hosted URL.
chat: {sayInChat: (message: string, platform?: string, channel?: string)}Chat info from the Spooder. Use sayInChat to send a text message to stream chat. Normally for messages in onChat, you'll want to use respond(text: string) inside the message object to easily reply to incoming messages. The sayInChat function can be called with 1-3 arguments given. 1 will chat on all active platforms, 2 will chat on the home channel of the given platform, and 3 will chat on the given platform on the given channel.
registerPluginApi(
router: 'local' | 'public',
method: 'get' | 'post' | 'put' | 'delete',
address: string,
funct: (req: express.Request, res: express.Response)
)Register a custom endpoint on the Spooder's web server for your plugin. The given address will be accessible by [Spooder Access URL]/plugin/api/[Plugin Name]/[Your Given Address]
subscribeToModuleEvent: (eventName: string, callback: Function)Subscribes to an event specific to a module. This works for Twitch's tmi.js events. You can listen for a user getting banned using the "ban" event.
Here's the supported events:
twitchEvents = [
'botmessage',
'messagedeleted',
'action',
'anongiftpaidupgrade',
'ban',
'cheer',
'clearchat',
'connected',
'connecting',
'disconnected',
'emoteonly',
'emotesets',
'followersonly',
'giftpaidupgrade',
'hosted',
'hosting',
'join',
'logon',
'mod',
'mods',
'part',
'r9kbeta',
'raided',
'raw_message',
'reconnect',
'resub',
'roomstate',
'slowmode',
'subgift',
'submysterygift',
'subscribers',
'subscription',
'timeout',
'unhost',
'unmod',
'vips',
'whisper',
]getActiveViewer(req: express.Request)Use this within your registered API endpoint to validate a viewer using your plugin's public web page. Since we only support Twitch for now, this will output some basic user info such as user id, user name, and display name. Otherwise this will return undefined. You shouldn't worry much about it returning undefined since the public-bundle.js included with the web page should block access and have the user login to Twitch before going forward.
getLocalFilePath(filePath: string)Returns a path in respect to the plugin's backend directory where the package.json is.
getOverlayUrl();
getUtilityUrl();Use this to get a URL to your overlay to share. You must use this function to get the URL because accessing overlays and utilities through the public internet requires a key that's auto generated and appended to the URL. These keys are perpetual, just like Share keys.
registerExtra(key: string, value: any)Used for platform specific features. There are only a couple ways to utilize this.
modmap: {locks: [key:string]: number}Used for defining lockable variables in your plugin. These will be buttons visible on the ModUI.
dSlashCommands: {
name: string,
description: string,
options: {
required: boolean,
type: string | number,
name: string,
description: string
}
}Used for defining slash commands for Discord plugins. Types can be written as follows:
string, integer, number, boolean, user, attachment, channel, role, mentionable, sub_command, sub_command_group
Each plugin comes with their own settings-form.json that makes it easy to create an interface for users to configure your plugins. At its root, we have "form" and "defaults". Whereas form is the UI and defaults are what to fill that UI with if there's nothing already set. Defaults are required since exported plugins exclude the settings.json.
Each form element starts with a key name which is accessed by the plugin as this.settings[keyname]. All of which have a "label" to show a proper name for the variable on the UI and "type" which can be any input type in HTML. To create an array with multiple inputs from the form element, add "multi-select":true.
Some types are Spooder specific like:
-
udp- Makes a select input with the user's UDP clients as options. -
discord- Makes two select inputs to specify a guild and channel. -
obs- Makes two select inputs to specify a scene and sceneItem. -
asset- Makes a select input with files of a specified folder and extension as options. -
code- Makes a special textarea input for JavaScript code. You can make your own way to process this script or make it use Spooder's Response Processor to make it work like a Response script. The resulting text will be passed through onEvent instead of the code.
Add an "options" object to set various parameters for the input. Each type has their own options that can be applied.
- selections - Selections is an object with the input's option elements "value":"label"
- json-friendly - Blocks out special characters and spaces for JSON key names.
- min - Minimum value for input
- max - Maximum value for input
- step - The incremental step between the range
- folder - A directory to upload assets to and select from starting with the root of the plugin's folder in the assets web folder.
- type - Set an extension to filter what kind of assets to upload or show in the select element. You can also set it to "image", "video", or "sound" as they are filter presets.
Subforms are an object within the settings object containing their own form elements to be cloned multiple times. Like any form element, they have a "label" and the "type" is set to "subform". Finally, they have "form" which is an object that works just like the "form" at root. The first form element to add to a subform is literally "keyname" which can be any input like text or select. In command.js, this will be accessed like this.settings[subform][keyname].
Defaults are highly recommended to prefill your inputs as each installed plugin won't have a settings.json included. Use the same key structure of your form with the default values on the right hand side. For subforms, make an object with the same key structure as the subform. Those will be the default values for each new subform cloned.
With OSC, it's easy for hosted web pages to connect with your plugin on Spooder. Using the included osc-bundle.js to automatically send a "/connect" message and receive back a "/connect/success" message. When the onConnect function within your web code fires, your overlay/utility is ready to operate and communicate with your plugin.
Some global functions are set from osc-bundle.js to make communications with your plugin easier:
pluginSettings: object;Your plugin's loaded settings
sendOSC(address: string, …value: any)Send an OSC message to your plugin with however many arguments you want to put. Objects are automatically stringified to be parsed in command.js
getAssetUrl(assetPath: string)Returns a URL to one of your plugin's assets.
getApiUrl(apiPath: string)Returns a URL to one of your plugin's registered API endpoints. This will take the apiPath string and append the base url ([Spooder_Host_Url]/plugin/api/[Plugin_Name]/[apiPath])
Head to the Config tab and click on Backup/Restore. There you can backup your Spooder's settings and plugins. Plugin backups don't include the node_modules folder in each plugin, so dependencies will be reinstalled when restoring. You may also download and import plugin backups to transfer to another Spooder.
As long as your plugin has the same name as your installed plugin, the new files will merge with the old files. This will preserve your settings and assets.
When your plugin is all set to be installed on another Spooder, click its download button in the Plugins tab. All files except settings.json, node_modules folder, and assets folder will be zipped and sent back to you to download. Then that's it! Your plugin is ready to be installed on another Spooder. Note that the name of the zip file is the directory name of the plugin which all folders and icon file will be named. Changing it can break incoming OSC messages. To be safe, don't change it! The assets folder will not be included in the package, but they can be added manually to the package to be installed with the other folders.
Whether you want to demo a plugin to someone before they buy or be the cool nerd in a group of streamer friends. You can share your Spooder plugin with other Twitch users with no installations on their end. You can even stream yourself while sharing plugins with another streamer. However, if you were to lock a plugin or chat command in Spooder, that plugin or command won't be usable by you or any of your shares until the lock is lifted.
To create a share, go to the Sharing tab in the Web UI. Click Create Share and put in a Twitch username. If the streamer is found, a new entry will be created with the streamer's profile picture and display name. Then you can set which plugins or commands to share with that streamer. If one or more of your plugins has an overlay, you can also add their Discord User ID to auto send a URL for them to copy and paste as a browser source. You must have Spooder's integrated Ngrok working to do this. Once it's all set, click save at the bottom. You may also edit shares while a share is running.
Join and Leave messages are great to introduce your Spooder to a channel and leave with a proper goodbye. This is also saved within Spooder's shares.json. However, starting a share from the Sharing tab will use the join and leave message from the input itself rather than what's on file. Using the quick share buttons on the Web UI's navigation menu or Live Auto Share will use the join and leave message on file.
Live Auto Share creates an EventSub for "stream.online" and "stream.offline" to automatically start sharing when the streamer goes live and stops when the streamer ends. Click this on and your Spooder will be part of your share target's stream autonomously.