This website is super plain. The text formatting looks awful due to the not existant CSS. Please keep in mind.
Welcome to the rail getting started guide! This little helper will lead you through the main tools which you will need while developing a plugin for rail.
For now, this tutorial is written in C# only, since my time is really limited and its nearly the same progress in other .NET languages as VB for instance.
In rail, we separate two sorts of plugins: The Backend-plugins and the Frontend-plugins:
The Backend is simply the console of rail. Backend-plugins are not supposed to take any commands from the IRC directly. So these plugins don't really need any rights management, since the console is only visible to the administrator of the server where the rail instance is running on.
The IRC, or the data coming from the IRC server to be more precise, is our so called Frontend. Frontend-plugins are able to take commands from the IRC and react on various ways.
Ok, I admit that these explanations won't help you at all. They are quite confusing whitout any examples, but you will get used to it once you made your first plugins. Believe me, its actually really simple :)
First, create a new project in the Visual Studio (or any other IDE, your choice) and name it to whatever you like to. The type should be a class library for obvious reasons.
You should find yourself with an empty project. Now create a reference to the rail assembly (important!) and include the rail namespace. In my case, it looks like this:
using System;
using System.Collections.Generic;
using System.Text;
using rail;
namespace GettingStartedSamplePlugin {
public class Class1 {
}
}
See the Class1? This class will be our plugin. Lets implement the IFrontendPlugin interface:
using System;
using System.Collections.Generic;
using System.Text;
using rail;
namespace GettingStartedSamplePlugin {
public class Class1 : IFrontendPlugin {
#region IFrontendPlugin Member
public void Initialize() {
throw new NotImplementedException();
}
public string[] PluginCommands {
get { throw new NotImplementedException(); }
}
public void ProcessCommand(string pluginCommand, string parameter, string data) {
throw new NotImplementedException();
}
#endregion
}
}
Wow, thats some new stuff! Lets check it out step by step:
Initialize:
NEVER EVER register redirects in the default constructor! Even if you don't know what I mean by redirects you should keep this in mind. Its bad. If you do it, Chuck Norris will come for you (you don't want that to happen, believe me). But if you register them in the Initialize method, christmas will be great next year, or so.
Seriously, just don't register them in the constructor.
PluginCommands:
You know these channels where somebody types any character followed by a keyword and some bot thingy replies with something? No? Shame. You'll learn it later on whatsoever.
ProcessCommand:
This is the response stuff from the point above.
You should be familiar with the basic structure of any plugin (the backend stuff looks nearly the same).
At this point, we should know what we want our plugin to do. This is my guide so I'm gonna make the plan:
- If somebody writes an url in a channel, he'll get kicked and a message will be displayed
- !date will print the date, !time the time
- Everybody with rail rights and any operator will be able to kick via !kick [name]
Sounds like fun! Lets do it.
What a clever transitions to redirects. Everything we need for our first point on the list are redirects.
The basic idea behind these so called redirects is simple. If rail wants to handle every response from the IRC server, it would be way too much of typing. And you know developers are lazy people.
To let plugins interact, we would need events, lots of them. Lucky me, I came up with this idea.
If you read the IRC RFC you know which response is important for PRIVATE MESSAGES (hint! hint!). If not, developing for rail will be real pain. I assume you all read it (not even half you did, right?) and continue with a bit of code:
public void Initialize() {
Globals g = Globals.GetInstance;
g.IRC.Redirects.Add("SamplePluginRedirect1", new Redirect(this, Command.PRIVMSG, delegate (string data) {
Response resp = new Response(data);
}));
}
"Wait a second, what the hell is Globals?!"
Oh, I forgot. Simpleton class with everything you need. Its like the toolbox of an engineer.
The first parameter of the Add-method is simply the key in the dictionary used for removing and stuff. the second parameter is a redirect (surprise!). Parameter one of the constructor is the plugin instance (this, everytime), number two will define the command to be redirected and finally number 3 which will handle the response.
It doesn't have to be a anonymous method, your choice. "data" is the complete string coming from the server.
The response class is simply a wrapper for this string. It provides you with everything you need.
To check whether a user postet an url, we have to use regular expressions, yey! I can hear some of you moan, so I already made a simple pattern (its not perfect, I know):
public void Initialize() {
Globals g = Globals.GetInstance;
g.IRC.Redirects.Add("SamplePluginRedirect1", new Redirect(this, Command.PRIVMSG, delegate (string data) {
Response resp = new Response(data);
if (Regex.IsMatch(resp.Parameter[1], @"(https?://)?(www\.)?([a-zA-Z0-9]+?\.)?[a-zA-Z0-9]\.[a-zA-Z]{2,3}(\.[a-zA-Z]{2,3})?(.+)?$")) {
Request req = new Request(Command.KICK);
req.AddParameter(resp.Parameter[0]);
req.AddParameter(resp.Parameter[1]);
g.IRC.Send(req);
}
}));
}
Ok, simple.
If we would call the ToString method of the Request, it would look something like this:
KICK #channel nickname
But only kicking the user is boring. Lets write it to the logfile and display a message in the channel (in color!):
public void Initialize() {
Globals g = Globals.GetInstance;
g.IRC.Redirects.Add("SamplePluginRedirect1", new Redirect(this, Command.PRIVMSG, delegate (string data) {
Response resp = new Response(data);
if (Regex.IsMatch(resp.Parameter[1], @"(https?://)?(www\.)?([a-zA-Z0-9]+?\.)?[a-zA-Z0-9]\.[a-zA-Z]{2,3}(\.[a-zA-Z]{2,3})?(.+)?$")) {
Request req = new Request(Command.KICK);
req.AddParameter(resp.Parameter[0]);
req.AddParameter(resp.Prefix.NickName);
g.IRC.Send(req);
g.Logger.WriteMessage("Kicked " + resp.Prefix.NickName + " from channel " + resp.Parameter[0] + "! Reason: Advertising");
IRCTextFormatter formatter = new IRCTextFormatter();
formatter.AppendFormattedText("Advertising is not allowed!", TextColor.Red);
Request req2 = new Request(Command.PRIVMSG);
req2.AddParameter(resp.Parameter[0]);
req2.AddParameter(formatter.ToString(), true);
g.IRC.Send(req2);
}
}));
}
Should be pretty self explanatory.