Write a Plugin
In this article, we will write a plugin. Specifically, we will write a plugin that lets users add a friend code1 for their nickname, and look up the friend codes of others. The friend codes will be persisted to disk, and we'll let users remove their friend code should they decide they do not want to be friendly.
For this article, we are going to be implementing both the persistence logic and IRC interface logic together. This is normally bad design, as the plugin should really only be plugging some service provided in IRC-agnostic code with IRC. We"ll look at refactoring this out in another article2.
For persistence, we'll be using the Dirty DB. Don't worry if you've never heard of it, it's a trivial to use database. It"s an extremely basic key-value database that you've probably never heard of, but is perfect for our use case. Just imagine it as a single persistent object.
Project Setup
Depending on whether you want to share your plugin with others or not, there are two ways to get started - locally or externally. You can always move from local to external.
External (Public) Plugin
If you want to contribute to the Tennu platform with a plugin for others to install, setting up an external plugin is the way to do so.
An external plugin is a plugin that is an npm
package. So, to start out, create a new npm
project. Create a directory for the project, create a code repo (probably git) both locally and wherever you store remote repoes (probably GitHub), and run npm init
filling out the questions as appropriate. The package name should be the name of your plugin prefixed with "tennu-"
. For this plugin, it'd be tennu-friend-code
. A good name for the main file is plugin.js
.
The next step is to create plugin.js
, and the rest of the article will discuss that. Live testing and plublishing your plugin are other things you'll want to do, explained in the following paragraphs.
When you want to live-test the plugin, you have a few options.
- If your plugin is a single file, you can install the plugin locally into your bot and test there.
- If the plugin is more than a single file, you can install with npm link.
- If you want to test in a custom bot for your plugin, add
tennu
as a devDependency, and add atest-bot
directory with theconfig.json
file. Just make sure to add the directory to your.npmignore
file so you don"t publish it.
When your plugin is well tested and ready to be published, change the version to 1.0.0
in the package.json
, and npm publish
. Then you can install it like any public plugin.
Local (Private) Plugin
If, for whatever reason, you do not want to publish your plugin for others to use, follow these instructions.
- Create a directory in your bot's project
tennu_plugins
if you have not already done so. - Create a file that is the name of your plugin. In our case,
friend-code.js
. - Write the plugin in the created file.
- Replace TennuPluginName with the name of our plugin. Let us call it FriendCode.
- Determine if we want to export anything. Now, we could export a function to let other plugins look up friend codes, but YAGNI, so we will export nothing, and remove the property from our returned plugin instance.
-
What handlers and commands we need. In this case, we need three command handlers and a handler for the "error" event3 to cleanup.
!addfriendcode
- A command to add a friend code.!delfriendcode
- A command to remove a friend code.!friendcode
- A command to look up a friend code.
We need to add them to the commands array, and add a handler and help entry for each command.
- This example was choosen for the Brave Frontier IRC channel, through other services also use friend codes, such as Nintendo.
- And an article after that may even show how to abstract the friend code logic into any per-user lookup command triple generating module.
- The "error" event in this case is what is sent by the IRC servers to tell you that you have ended your connection to it. It is
not an actual error. Blame the original IRC specification author for this. - If you don"t have a package.json, leave off the
--save
parameter. - A good example of where allowing more than one word for the friend code is in Brave Frontier where some people have multiple accounts and thus multiple friend codes, so they could send
!addfriendcode BF: 000000, JP: 111111
.
Plugin Skeleton
Now that we have a file to edit your plugin in, we should start with a skeleton. The one shown on the Getting Started tutorial is as good as any, so let us start with that.
First Edits
We"ll want to do a few things.
Putting that altogether, and we get a plugin structure that looks like this.
Initialization & Cleanup
Next up, let"s set up the initialization code. There are two points of initialization: One more the module and one for the plugin. Anything that doesn"t change if you were running two bots simultaneously in the same program (which is possible) is placed before FriendCode is defined. Anything that is bot specific goes in the init function before you return the plugin.
The only module level initialization we have to do is importing in the Dirty DB constructor. First make sure you've added it to your dependencies (npm install dirty --save
4) and then add this line to the top of your plugin: var Dirty = require("dirty");
.
But there"s also a utility function format
on the built-in util
module. We are also going to require it to make our string handling code nicer.
In the plugin initialization, we need to create an instance of the Dirty DB, and to do so, we need a location of the database. Since we don"t want to assume a location for the database, we'll ask users to add it to their config file. We can then read the value in the config file with client.config("config-key-name")
. As per a convention in Tennu, we call this config value %plugin-name%-database
or in our case,
If you haven"t read the documentation for the Dirty DB, you create one by calling Dirty(relativePath)
, and if the database exists, it'll use that database. Otherwise, it'll create that file and use it as the database. If for whatever reason the program doesn"t have access to the file it's trying to create, such as permissions errors or the directory doesn"t exist, it'll throw an error. We won"t worry about handling such errors here.
Finally, we are supposed to close the database connection when we are done with it. This is done via the close
method.
Given all of this, let"s write it into code for our plugin.
Handlers
Alright, we have a database open, but our commands do not do anything. We need to implement them. But what information does each command need?
The !friendcode
command needs to know where to send the message and what argument is passed to it. You can either find out which channel (or nickname if private message) a command was sent using the "channel"
property of the command
object passed to the handler, or you could just return your response as one of the Response formats. We"ll be returning the String response type for this and the rest of our commands. For the argument, it'll be on the "args"
property of the command
object. Since a nickname is only ever one word, it'll be the first arg, or command.args[0]
.
Both !addfriendcode
and !delfriendcode
need to know who is sending the command. This is easily found out with the "nickname"
property on the command
object. !addfriendcode
also takes a friend code. While friend codes usually do not contain spaces, we'll be general and allow them5. Tennu doesn"t send the entire arguments as a simple string, so to get a multi-word argument, you slice the number of single-word arguments precede it and then join with a space. In our case, that would be client.args.slice(0).join(" ")
, but since we know that .slice(0)
returns a copy of the array and .join(" ")
doesn"t mutate the array, we can leave it out when there are no single-word arguments.
Then, with this information, we need to set, get, or delete the proper information, and respond with the proper message. We should also make sure to check to make sure the user has a friend code before deleting it, and report that they already don"t have one if they do not.
We also add the proper help for these commands.
Final Notes
At this point, your plugin is complete. Add "friend-code" to your plugins list, and add a new property to yourconfig.json
, "friend-code-database"
. If you aren't sure where to put your database, make a directory databases
in your bot's directory and make it "databases/friend-codes.db"
. Then start your bot, and enjoy the new functionality.