Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom formatting to IRC & Discord output #204

Merged
merged 7 commits into from
Mar 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions lib/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { formatFromDiscordToIRC, formatFromIRCToDiscord } from './formatting';
const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordToken'];
const NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
'dark_green', 'magenta', 'light_magenta', 'orange', 'yellow', 'cyan', 'light_cyan'];
const patternMatch = /{\$(.+?)}/g;

/**
* An IRC bot, works as a middleman for all communication
Expand All @@ -34,6 +35,26 @@ class Bot {
this.ircNickColor = options.ircNickColor !== false; // default to true
this.channels = _.values(options.channelMapping);

this.format = options.format || {};
// "{$keyName}" => "variableValue"
// nickname: discord nickname
// displayUsername: nickname with wrapped colors
// text: the (IRC-formatted) message content
// discordChannel: Discord channel (e.g. #general)
// ircChannel: IRC channel (e.g. #irc)
// attachmentURL: the URL of the attachment (only applicable in formatURLAttachment)
this.formatCommandPrelude = this.format.commandPrelude || 'Command sent from Discord by {$nickname}:';
this.formatIRCText = this.format.ircText || '<{$displayUsername}> {$text}';
this.formatURLAttachment = this.format.urlAttachment || '<{$displayUsername}> {$attachmentURL}';

// "{$keyName}" => "variableValue"
// author: IRC nickname
// text: the (Discord-formatted) message content
// withMentions: text with appropriate mentions reformatted
// discordChannel: Discord channel (e.g. #general)
// ircChannel: IRC channel (e.g. #irc)
this.formatDiscord = this.format.discord || '**<{$author}>** {$withMentions}';

this.channelMapping = {};

// Remove channel passwords from the mapping and lowercase IRC channel names
Expand Down Expand Up @@ -155,6 +176,10 @@ class Bot {
return this.commandCharacters.indexOf(message[0]) !== -1;
}

static substitutePattern(message, patternMapping) {
return message.replace(patternMatch, (match, varName) => patternMapping[varName] || match);
}

sendToIRC(message) {
const author = message.author;
// Ignore messages sent by the bot itself:
Expand All @@ -175,23 +200,34 @@ class Bot {
displayUsername = irc.colors.wrap(NICK_COLORS[colorIndex], nickname);
}

const patternMap = {
nickname,
displayUsername,
text,
discordChannel: channelName,
ircChannel
};

if (this.isCommandMessage(text)) {
const prelude = `Command sent from Discord by ${nickname}:`;
const prelude = Bot.substitutePattern(this.formatCommandPrelude, patternMap);
this.ircClient.say(ircChannel, prelude);
this.ircClient.say(ircChannel, text);
} else {
if (text !== '') {
// Convert formatting
text = formatFromDiscordToIRC(text);
patternMap.text = text;

text = `<${displayUsername}> ${text}`;
text = Bot.substitutePattern(this.formatIRCText, patternMap);
logger.debug('Sending message to IRC', ircChannel, text);
this.ircClient.say(ircChannel, text);
}

if (message.attachments && message.attachments.size) {
message.attachments.forEach((a) => {
const urlMessage = `<${displayUsername}> ${a.url}`;
patternMap.attachmentURL = a.url;
const urlMessage = Bot.substitutePattern(this.formatURLAttachment, patternMap);

logger.debug('Sending attachment URL to IRC', ircChannel, urlMessage);
this.ircClient.say(ircChannel, urlMessage);
});
Expand Down Expand Up @@ -238,8 +274,17 @@ class Bot {
return match;
});

const patternMap = {
author,
text: withFormat,
withMentions,
discordChannel: `#${discordChannel.name}`,
ircChannel: channel
};

// Add bold formatting:
const withAuthor = `**<${author}>** ${withMentions}`;
// Use custom formatting from config / default formatting with bold author
const withAuthor = Bot.substitutePattern(this.formatDiscord, patternMap);
logger.debug('Sending message to Discord', withAuthor, channel, '->', discordChannelName);
discordChannel.sendMessage(withAuthor);
}
Expand Down
175 changes: 175 additions & 0 deletions test/bot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Bot from '../lib/bot';
import createDiscordStub from './stubs/discord-stub';
import ClientStub from './stubs/irc-client-stub';
import config from './fixtures/single-test-config.json';
import configMsgFormatDefault from './fixtures/msg-formats-default.json';

chai.should();
chai.use(sinonChai);
Expand Down Expand Up @@ -569,4 +570,178 @@ describe('Bot', function () {
this.bot.sendToDiscord(username, '#irc', text);
this.sendMessageStub.should.have.been.calledWith(expected);
});

it('should successfully send messages with default config', function () {
const bot = new Bot(configMsgFormatDefault);
bot.connect();

bot.sendToDiscord('testuser', '#irc', 'test message');
this.sendMessageStub.should.have.been.calledOnce;

const guild = createGuildStub();
const message = {
content: 'test message',
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'otherauthor',
id: 'not bot id'
},
guild
};

bot.sendToIRC(message);
this.sendMessageStub.should.have.been.calledOnce;
});

it('should not replace unmatched patterns', function () {
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
const bot = new Bot({ ...configMsgFormatDefault, format });
bot.connect();

const username = 'testuser';
const msg = 'test message';
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
bot.sendToDiscord(username, '#irc', msg);
this.sendMessageStub.should.have.been.calledWith(expected);
});

it('should respect custom formatting for Discord', function () {
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
const bot = new Bot({ ...configMsgFormatDefault, format });
bot.connect();

const username = 'test';
const msg = 'test @user <#1234>';
const expected = `<test> #irc => #discord: ${msg}`;
bot.sendToDiscord(username, '#irc', msg);
this.sendMessageStub.should.have.been.calledWith(expected);
});

it('should successfully send messages with default config', function () {
this.bot = new Bot(configMsgFormatDefault);
this.bot.connect();

this.bot.sendToDiscord('testuser', '#irc', 'test message');
this.sendMessageStub.should.have.been.calledOnce;

const guild = createGuildStub();
const message = {
content: 'test message',
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'otherauthor',
id: 'not bot id'
},
guild
};

this.bot.sendToIRC(message);
this.sendMessageStub.should.have.been.calledOnce;
});

it('should not replace unmatched patterns', function () {
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();

const username = 'testuser';
const msg = 'test message';
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
this.bot.sendToDiscord(username, '#irc', msg);
this.sendMessageStub.should.have.been.calledWith(expected);
});

it('should respect custom formatting for Discord', function () {
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();

const username = 'test';
const msg = 'test @user <#1234>';
const expected = `<test> #irc => #discord: ${msg}`;
this.bot.sendToDiscord(username, '#irc', msg);
this.sendMessageStub.should.have.been.calledWith(expected);
});

it('should respect custom formatting for regular IRC output', function () {
const format = { ircText: '<{$nickname}> {$discordChannel} => {$ircChannel}: {$text}' };
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();

const guild = createGuildStub();
const message = {
content: 'test message',
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'testauthor',
id: 'not bot id'
},
guild
};
const expected = '<testauthor> #discord => #irc: test message';

this.bot.sendToIRC(message);
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});

it('should respect custom formatting for commands in IRC output', function () {
const format = { commandPrelude: '{$nickname} from {$discordChannel} sent command to {$ircChannel}:' };
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();

const text = '!testcmd';
const guild = createGuildStub();
const message = {
content: text,
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'testauthor',
id: 'not bot id'
},
guild
};
const expected = 'testauthor from #discord sent command to #irc:';

this.bot.sendToIRC(message);
ClientStub.prototype.say.getCall(0).args.should.deep.equal(['#irc', expected]);
ClientStub.prototype.say.getCall(1).args.should.deep.equal(['#irc', text]);
});

it('should respect custom formatting for attachment URLs in IRC output', function () {
const format = { urlAttachment: '<{$nickname}> {$discordChannel} => {$ircChannel}, attachment: {$attachmentURL}' };
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();

const attachmentUrl = 'https://image/url.jpg';
const guild = createGuildStub();
const message = {
content: '',
mentions: { users: [] },
attachments: createAttachments(attachmentUrl),
channel: {
name: 'discord'
},
author: {
username: 'otherauthor',
id: 'not bot id'
},
guild
};

this.bot.sendToIRC(message);
const expected = `<otherauthor> #discord => #irc, attachment: ${attachmentUrl}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});
});
9 changes: 9 additions & 0 deletions test/fixtures/msg-formats-default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"nickname": "Reactiflux",
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"commandCharacters": ["!", "."],
"channelMapping": {
"#discord": "#irc"
}
}