Skip to content

Plugins

Splamy edited this page May 6, 2018 · 15 revisions

Getting started

To get started create a folder like Plugins and set the PluginManager::PluginPath setting accordingly.

Avaiable Hooks

There are two different kinds of classes you can extend the bot with:

  • ITabPlugin The universal extention tool for commands and new functionality
  • IFactory A provider to resolve links for the commands !play and !playlist

Storing Plugins

There are two ways to add your plugin to the bot.

  1. For quick and dirty testing you can simply add a *.cs file into the plugin folder and the bot will automatically compile it for you when requested. Note that the itegrated compiler only supports the C# language specification up to 4.0!
  2. The usual way is to place any .NET *.dll or *.exe (will be treated like a library) into the plugins folder. Note: If you want your new plugin to autoload, just create a new file YourPluginName.Extension.status and add a 1 as it's content.

Listing plugins

To list all your plugins simply 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.

Plugin types

There are 3 types of plugins you can declare depending on what you want. It's fairly simple:

  • 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.

Command Plugins

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.

StaticPlugin

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.";
    }
}

BotPlugin and CorePlugin

using TS3AudioBot.Plugins;
// ...
public class NowPlaying : IBotPlugin {
    // your dependencies will be injected when declared as a property.
    public PlayManager PlayManager { get; set; }
    public TeamspeakControl QueryConnection { get; set; }

    const string np = " [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 = QueryConnection.GetSelf();
        if (!self.Ok) return;
        lastName = self.Value.Name;
        QueryConnection.ChangeName(lastName.EndsWith(np) ? lastName : lastName + np);
    }
    private void Stop(object sender, EventArgs e) {
        if(lastName != null) QueryConnection.ChangeName(lastName);
    }

    // You can still add commands here like in the StaticPlugin which will get added into
    // the command system.
    // You should prefer static commands which get the modules via parameter to prevent
    // strange behavior when called from other places.

    public void Dispose() {
        PlayManager.AfterResourceStarted -= Start;
        PlayManager.AfterResourceStopped -= Stop; 
    }
}

Initialize

The Initialize method gets called when your plugin gets stated (!plugin load) and all properties have been initialized for you.

Dispose

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.

Commands

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")]
public string CommandGreet(string name, int number, string optional = null)
{
    if (optional != null)
        Console.WriteLine("User opted: " + optional);
    return "Hi " + name + ", you choose " + number;
}

Attributes

You can add the following attributes to a command (All attributes described here are in the TS3AudioBot.CommandSystem namespace):

  • CommandAttribute This attribute is required to mark a function as a bot command. It has two parameter:
    1. The first one specifies the command path which must be lower case letters and spaces only. This path must be unique together with the parameter types of the function.
    2. The second parameter is optional and may provide a short help text to the user when called with !help <your> <command>
  • UsageAttribute Add this when a commands get more complicated and you want to give more detailed help to one or more specific use cases of your command.
    This command can be added multiple times for multiple examples. It has two parameter:
    1. The first one lists your parameter names
    2. The second one should explain it.
  • RequiredParametersAttribute By default when calling a bot command the commandsystem will require a value for each parameter in your function (in the above example string name and int number, so the command must be called !greet Tom 42). When less values are given the command will not execute. This attribute allows you to specify the minimal amount of values this command needs. All parameter beyond the specified amount will be considered optional.

Input

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.

To get more information from a botcommand call you can add a ExecutionInformation info parameter to your function. This special command does not count towards the command parameter list and can always be obtained. This objects holds many useful information about the current call:

  • TextMessage the origninal complete textmessage that initiated this call
  • InvokerData all information which are known about the sender of this textmessage. Note that many properties are declared as nullable and many strings also might be null.
  • Session the session is a useful object to annotate certain informatin to a user
  • 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.
  • Bot the MainBot instance that received/initiated this command call. If you want to access related structures use this instance instead of the one given with Initialize.

Output

A function can return void, string or JsonObject.

  • void Nothing will be returned.
  • string A string or null for a void equivalent can be returned to the user or a calling parent funtion.
  • 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 the void equivalent.
    • JsonSingleValue will generate { "Value" : "yourValue" }, use preferedly for primitve 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 a CommandException.

Teamspeak Functions and Events

High Level

Some functions and events of TS3Client library already got wrapped for easy access and use. You can view them with your mainbot's .QueryConnection attribute. Example:

using System;
using TS3AudioBot;
using TS3AudioBot.Plugins;
using TS3Client;
using TS3Client.Commands;
using TS3Client.Full;

namespace Example
{
    public class Example : ITabPlugin {
        private MainBot bot;
        public void Initialize(MainBot mainBot)
        {
            bot = mainBot;
            bot.QueryConnection.OnClientConnect += QueryConnection_OnClientConnect;
        }
        private void QueryConnection_OnClientConnect(object sender, TS3Client.Messages.ClientEnterView e) {
            bot.QueryConnection.SendMessage("Hello, you just connected to this server", e.InvokerId);
        }
        public void Dispose() {
            bot.QueryConnection.OnClientConnect -= QueryConnection_OnClientConnect;
        }
    }
}

Low Level

The more low-level functions and events are only available by using MainBot.QueryConnection.GetLowLibrary<Ts3FullClient>(); Example:

using System;
using TS3AudioBot;
using TS3AudioBot.Plugins;
using TS3Client;
using TS3Client.Commands;
using TS3Client.Full;

namespace Example
{
    public class Example : ITabPlugin {
        private MainBot bot;
        private Ts3FullClient lib;
        public void Initialize(MainBot mainBot)
        {
            bot = mainBot;
            lib = bot.QueryConnection.GetLowLibrary<Ts3FullClient>();
            lib.OnClientMoved += Lib_OnClientMoved;
        }
        private void Lib_OnClientMoved(object sender, System.Collections.Generic.IEnumerable<TS3Client.Messages.ClientMoved> e) {
            foreach (var client in e)
            {
                lib.SendPrivateMessage("Hello, you just moved to another channel", client.ClientId);
		try { lib.Send("clientpoke", new CommandParameter("clid", client.ClientId), new CommandParameter("msg", "Oh,\\sno\\swhat\\sare\\syou\\sdoing?"));
		} catch (Exception ex) { Log.Write(Log.Level.Warning, string.Format("Exception thrown while trying to poke client #{0}: {1}", client.ClientId, ex.Message)); }
	    }
        }
        public void Dispose() {
            lib.OnClientMoved -= Lib_OnClientMoved;
        }
    }
}

NOTE: Some event's of the low-level library return arrays because the teamspeak server queues them and sents multiple at once. Just use a foreach loop to work through each event.

Resource Factories

[TODO]

General

IResourceFactory

IPlaylistFactory

IThumbnailFactory

Technical stuff

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 uasage after a plugin developing session you will need to restart the bot to get to normal memory usage again.