-
-
Notifications
You must be signed in to change notification settings - Fork 142
Plugins
To get started create a folder like Plugins
and set the path in your config accordingly.
[plugins]
#The path to the plugins folder. Can be relative or absolute
path = "Plugins"
There are two different kinds of classes you can extend the bot with:
-
StaticPlugin
/IBotPlugin
/ICorePlugin
The extension tools for commands and new functionality -
IFactory
A provider to resolve links for the commands!play
and!list
(playlists) (Resource-Factories are used to extract a playable link from, for example, a youtube link or twitch link, etc.)
There are two ways to add your plugin to the bot.
- For quick testing or development you can simply add a
*.cs
file into the plugin folder and the bot will automatically compile it for you when requested. Note that that this method will use a lot more memory, especially when reloading multiple times! - The usual way is to place the compiled .NET
*.dll
with all its dependencies into the plugins folder.
To list all your plugins send !plugin list
to the bot.
#0|OFF|MyFile1.cs
#1|RDY|Plugin: MyPlugin2
#2|+ON|Factory: youtube
#3|UNL|Plugin: <unknown>
#4|ERR|MyFile2.cs
There are different states a plugin can be in
-
OFF
A file has been found but not yet checked -
RDY
The file was loaded without error and can be enabled -
+ON
The plugin (or factory) was successfully enabled and is now integrated into the bot -
UNL
The plugin was manually disabled and will not be loaded -
ERR
There was an error loading the file (See in the console/log for detailed information)
Each id is unique and will be kept for a file even if the file gets changed or reloaded. When a file gets deleted or renamed the id will be freed (and a new one associated).
You can load and unload your plugins with !plugin load <plugin>
and !plugin unload <plugin>
.
Both can take for the <plugin>
parameter either the currently assigned id or the complete file name.
There are 3 types of command-plugins you can declare depending on what you want:
- StaticPlugin: provides only commands. (This can access all modules, when called.)
- CorePlugin: Creates a plugin instance for the core. (This can access all core modules but not single bots.)
- BotPlugin: Creates a plugin instance for each bot. (This can access all core modules and all modules of the bot the plugin is in.)
If you don't know which one to choose, simple pick the BotPlugin
. This is the most versatile and will allow you to do everything.
You can still switch later to the others if you see that you don't want to use all features and optimize the plugin a bit for resource usage.
To start with a simple plugin create an empty file and call it MyPlugin.cs
and place it in the Plugins
folder (this folder can be changed in the config).
Here are examples for the basic layout of the different plugin types.
The StaticPlugin
is annotated by the [StaticPlugin]
attribute and should usually be declared as a static class
. Also all command methods must be declared as static
.
The StaticPlugin
has no state and should not use one except when using structures already provided by the bot. If you need a state consider switching to a Bot- or CorePlugin.
using TS3AudioBot.Audio;
using TS3AudioBot.CommandSystem;
using TS3AudioBot.Plugins;
[StaticPlugin]
public static class MySimplePlugin
{
[Command("hello")]
// you can get modules injected by adding them to your function parameter
public static string CommandHello(PlayManager playManager, string name)
{
if (playManager.CurrentPlayData != null)
return "hello " + name + ". We are currently playing: " + playManager.CurrentPlayData.ResourceData.ResourceTitle;
else
return "hello " + name + ". We are currently not playing.";
}
}
using System;
using TS3AudioBot;
using TS3AudioBot.Audio;
using TS3AudioBot.CommandSystem;
using TS3AudioBot.Plugins;
public class NowPlaying : IBotPlugin /* or ICorePlugin */ {
// Your dependencies will be injected when declared as a public property.
public PlayManager PlayManager { get; set; }
public Ts3Client Ts3Client { get; set; }
const string NowPlayingTag = " [Now Playing]";
string lastName = null;
// The Initialize method will be called when all modules were successfully injected.
public void Initialize() {
PlayManager.AfterResourceStarted += Start;
PlayManager.AfterResourceStopped += Stop;
}
private void Start(object sender, EventArgs e) {
var self = Ts3Client.GetSelf();
if (!self.Ok) return;
lastName = self.Value.Name;
Ts3Client.ChangeName(lastName.EndsWith(NowPlayingTag) ? lastName : lastName + NowPlayingTag);
}
private void Stop(object sender, EventArgs e) {
if(lastName != null) Ts3Client.ChangeName(lastName);
}
// You can still add commands here like in the StaticPlugin which will get added into
// the command system.
// You should prefer static methods which get the modules via parameter to prevent
// strange behavior when called from other places.
public void Dispose() {
// Don't forget to unregister everything you have subscribed to,
// otherwise your plugin will remain in a zombie state
PlayManager.AfterResourceStarted -= Start;
PlayManager.AfterResourceStopped -= Stop;
}
}
The Initialize
method gets called when your plugin gets stated (!plugin load
) and all properties have been initialized for you.
When you allocate external resources, other IDisposable
objects or make other changes to the bot, make sure to deallocate, dispose and undo all those changes in this method.
To create a new command which can be called from the commandsystem with !...
, simply create a method and add the CommandAttribute
to it.
For example:
[Command("greet everyone")]
public static string CommandGreet(string name, int number, string optional = null)
{
if (optional != null)
Console.WriteLine("User opted: " + optional);
return "Hi " + name + ", you choose " + number;
}
The first CommandAttribute
constructor parameter specifies the command path which must be lower case letters and spaces only.
In this example "greet everyone"
will be !greet everyone <name> <number> (optional)
.
This path together with all parameters must be unique.
The CommandSystem will automatically serialize and deserialize all primitve types (bool
, byte
, string
, ...), their nullable variant (bool?
, byte?
, ...), and enums when passing values to your function.
[Command("com")]
/*..*/ Command(int num, string other, bool? optional = null)
The CommandSystem will also inject all Module Types.
Modules are the way the Ts3AudioBot structues its logic and functionality.
For example Ts3Client
, HistoryManager
, PlayManager
,... are modules.
To get one injected declare it somewhere your parameter list (usually for readability at the beginning):
[Command("com")]
/*..*/ Command(HistoryManager hm, PlayManager plm, int num, PlaylistManager optionalPm = null)
You can still add normal parameters just as before.
You can also make a module optional by adding = null
as a default. Your command can then still be called even if the module is not present for any reason.
To get some meta information from a botcommand call you can request special types which are only present during a call.
[Command("com")]
/*..*/ Command(InvokerData invoker, UserSession session)
-
InvokerData
: TS3 data about the caller of this botcommand. Note that many properties might be null when they couldn't get retrieved by the bot. -
UserSession
: A temporal storage for each ts3 user which is interacting with the bot. (Cleared when user leaves the server) -
CallerInfo
Some metainformation about the current call, contains for e.g.:-
TextMessage
the original complete textmessage that initiated this call -
SkipRightsChecks
use this to disable all further rights checks in this call. Note to use this with caution as this may be a dangerous security hole.
-
-
ExecutionInformation
: Raw access to the dependency injection of the current call.
A function can return void
, string
or JsonObject
.
-
void
Nothing will be returned. -
string
A string ornull
for avoid
equivalent can be returned to the user or a calling parent function. -
JsonObject
This is the preferred variant for best compatibility with the chat and api. The commandsystem can decide whether the caller wants the readable string for the chat or a json-serialized object for the api.-
JsonEmpty
is thevoid
equivalent. -
JsonSingleValue
will generate{ "Value" : "yourValue" }
, use preferably for primitive types which have no structure. -
JsonSingleObject
will generate{ ... }
, use this if you want to serialize an entire object. -
JsonArray
can be used for an array of values or objects -
JsonError
should not be used. The commandsystem will generate this type for you when you throw aCommandException
.
-
If you encounter a problem in your command or want to indicate an error result, throw a CommandException
.
This will exit from all botcommands stop the call execution. The user will get the error response in the chat or in the api with a 4XX status code.
Example:
throw new CommandException("Something went wrong", CommandExceptionReason.CommandError);
// or if you want to pass through an exception
try {
// some code that throws
} catch (Exception ex) {
throw new CommandException("Something went wrong", ex, CommandExceptionReason.CommandError);
}
Some functions and events of TS3Client library are wrapped for easy access and use.
You can use them with the Ts3Client
module. Example:
using TS3AudioBot;
using TS3AudioBot.Plugins;
using TS3Client.Messages;
namespace Example
{
public class Example : IBotPlugin {
public Ts3Client Ts3Client { get; set; }
public void Initialize() {
Ts3Client.OnClientConnect += QueryConnection_OnClientConnect;
}
private void QueryConnection_OnClientConnect(object sender, ClientEnterView e) {
Ts3Client.SendMessage("Hello, you just connected to this server", e.InvokerId);
}
public void Dispose() {
Ts3Client.OnClientConnect -= QueryConnection_OnClientConnect;
}
}
}
More autogenerated functions and events, as well as the full Ts3Client library are available by using the Ts3FullClient
module. Example:
using System;
using TS3AudioBot;
using TS3AudioBot.Plugins;
using TS3Client;
using TS3Client.Commands;
using TS3Client.Full;
using TS3Client.Messages;
using System.Collections.Generic;
namespace Example
{
public class Example : IBotPlugin {
public Ts3FullClient FullClient { get; set; }
public void Initialize() {
FullClient.OnEachClientMoved += OnEachClientMoved;
}
private void OnEachClientMoved(object sender, ClientMoved client) {
FullClient.SendPrivateMessage("Hello, you just moved to another channel", client.ClientId);
FullClient.SendCommand<ResponseVoid>(new Ts3Command("clientkick") {
{ "reasonid", 4 },
{ "clid", client.ClientId },
{ "reasonmsg", "Oh, no what are you doing?" },
});
// This 'SendCommand' example call would be the same as with the already defined:
// FullClient.KickClientFromChannel(client.ClientId, "Oh, no what are you doing?");
}
public void Dispose() {
FullClient.OnEachClientMoved -= OnEachClientMoved;
}
}
}
[TODO]
Due to technical limitations each time you load/unload a plugin, a new assembly will be loaded into the application. This means should you notice a high memory usage after a plugin developing session with many reloads. You will need to restart the bot to get to normal memory usage again.