Lightweight express-like routing for discord bots
This acts as a wrapper to discord.js, but provides support for middleware-style routing for discord.js events.
See /example/mainWithHelpers.js
for more. This example uses ES6 imports to import all the helpers, but see below for examples without this.
import * as DiscoExpress from 'discoexpress';
import { ignoreSelf, contentMatches, reply, requireRole, withArgs } from 'discoexpress'; // Optionally import helpers
var app = new DiscoExpress()
.on("ready", () => console.log("Bot ready"))
.on("disconnected", () => console.log("Disconnected"))
// Log all messages that come through, then continue to other handlers.
.on("message", () => {
console.log("Got message:", msg.content);
next();
})
// Ignore messages from myself - don't call any handlers after this one
// if the message author is this bot
.on("message", ignoreSelf)
// Reply to the message "!ping" with "pong!"
.on("message",
contentMatches("!ping"),
reply("pong!")
)
// Reply can take a function as an argument, and contentMatches can take a regexp
.on("message",
contentMatches(/^!flip/),
reply(() => Math.random() < 0.5 ? "Heads" : "Tails")
);
// This message will never be reached, because the !flip handler above will always
// catch the message "!fliptable"
.on("message",
contentMatches("!fliptable"),
reply("No don't flip tables")
)
// See /src/helpers for all the helpers available, and see the examples below for
// writing your own handlers
.on("message",
requireRole(["admin", "mod"]),
contentMatches("!modcmd"),
reply("you just used a mod command!", true)
)
// A more complicated example
.on("message",
requirePermission("manageRoles"),
contentMatches(/!setcolor ([^\s]+) ([a-zA-Z0-9]{6})$/), // Only accept role names without spaces, for simplicity
withArgs((args, bot, msg, next) => {
var roleName = args[1];
var color = args[2];
var role = msg.channel.server.roles.get('name', roleName);
bot.updateRole(role, {
color: parseInt(color, 16)
}, function(err, role){
if(err) return bot.reply(msg, "Failed to update role color: " + err.message);
bot.reply(msg, "Role " + role.name + " updated!");
});
})
)
.login("[token]");
See /example/main.js
for more.
var DiscoExpress = require('discoexpress');
var app = new DiscoExpress();
app.on("ready", () => console.log("Bot ready"));
app.on("disconnected", () => console.log("Disconnected :("));
app.on("message", (bot, msg, next) => {
// Ignore messages from myself
// If next is not called, none of the handlers past this point get called!
if(msg.sender.id != bot.user.id) return next();
});
app.on("message", (bot, msg, next) => {
// This handler gets called, and then always continues to the next one
console.log("Got message:", msg.content);
return next();
});
app.on("message", (bot, msg, next) => {
// The message "!fliptable" will match both this handler AND the "!flip" handler!
// Put this one first, to match the more specific of the two, and then do
// not call next. This way the !flip handler never runs redundantly.
if(msg.content == "!fliptable"){
bot.reply(msg, "No don't flip tables");
}else{
next();
}
});
app.on("message", (bot, msg, next) => {
// If the !fliptable handler matched, it never gets to this point, so the
// bot won't reply twice.
if(msg.content.startsWith("!flip")){
bot.reply(msg, (Math.random() < 0.5) ? "Heads" : "Tails");
}else{
next();
}
});
// Other events can be routed the same way.
app.on("serverNewMember", (bot, server, user, next) => {
console.log("Logging new member join:", user.name);
return next();
});
app.on("serverNewMember", (bot, server, user, next) => {
// Greet the user in the general channel.
bot.sendMessage(server.id, "Welcome " + user + "!");
return next();
});
app.login("[token]");
Passing multiple handlers to app.on
will create a subroute. A subroute can
have its own middleware. This can be useful, for example, to make a subroute
where only people with the role "Moderator" can run the commands.
A subroute can be broken out of with next(false)
.
// Multiple handlers can be given to one call of app.on(). This will create a
// sub-route, which can be broken out of by calling next(false);
var requireMod = require('discoexpress-require-role')(["Moderator", "Admin"]);
app.on("message", requireMod, (bot, msg, next) => {
// Only users with a role named "Moderator" or "Admin" can run this command!
if(msg.content == "!modcmd"){
bot.reply(msg, "A moderator ran this command!");
}else{
next();
}
});
// Alternatively, you can create subroutes manually!
var subroute = new DiscoExpress.Route();
subroute.route((bot, msg, next) => {
if(msg.content.startsWith("!times2")){
return next();
}else{
// call next(false) to break from a subroute, and continue processing the parent route.
return next(false);
}
});
subroute.route((bot, msg, next) => {
// Should only get to this route if command starts with !times2, as per the handler above this.
// Make sure ignoreself middleware is enabled, or this message will trigger itself repeatedly!
bot.sendMessage(msg.channel, msg.content + msg.content);
});
app.on("message", subroute);