Skip to content

Commit 1a7ea2d

Browse files
authored
Merge pull request #204 from Throne3d/add/custom-formatting
Add custom formatting to IRC & Discord output
2 parents 9915db1 + 9848a89 commit 1a7ea2d

File tree

3 files changed

+233
-4
lines changed

3 files changed

+233
-4
lines changed

lib/bot.js

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { formatFromDiscordToIRC, formatFromIRCToDiscord } from './formatting';
99
const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordToken'];
1010
const NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
1111
'dark_green', 'magenta', 'light_magenta', 'orange', 'yellow', 'cyan', 'light_cyan'];
12+
const patternMatch = /{\$(.+?)}/g;
1213

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

38+
this.format = options.format || {};
39+
// "{$keyName}" => "variableValue"
40+
// nickname: discord nickname
41+
// displayUsername: nickname with wrapped colors
42+
// text: the (IRC-formatted) message content
43+
// discordChannel: Discord channel (e.g. #general)
44+
// ircChannel: IRC channel (e.g. #irc)
45+
// attachmentURL: the URL of the attachment (only applicable in formatURLAttachment)
46+
this.formatCommandPrelude = this.format.commandPrelude || 'Command sent from Discord by {$nickname}:';
47+
this.formatIRCText = this.format.ircText || '<{$displayUsername}> {$text}';
48+
this.formatURLAttachment = this.format.urlAttachment || '<{$displayUsername}> {$attachmentURL}';
49+
50+
// "{$keyName}" => "variableValue"
51+
// author: IRC nickname
52+
// text: the (Discord-formatted) message content
53+
// withMentions: text with appropriate mentions reformatted
54+
// discordChannel: Discord channel (e.g. #general)
55+
// ircChannel: IRC channel (e.g. #irc)
56+
this.formatDiscord = this.format.discord || '**<{$author}>** {$withMentions}';
57+
3758
this.channelMapping = {};
3859

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

179+
static substitutePattern(message, patternMapping) {
180+
return message.replace(patternMatch, (match, varName) => patternMapping[varName] || match);
181+
}
182+
158183
sendToIRC(message) {
159184
const author = message.author;
160185
// Ignore messages sent by the bot itself:
@@ -175,23 +200,34 @@ class Bot {
175200
displayUsername = irc.colors.wrap(NICK_COLORS[colorIndex], nickname);
176201
}
177202

203+
const patternMap = {
204+
nickname,
205+
displayUsername,
206+
text,
207+
discordChannel: channelName,
208+
ircChannel
209+
};
210+
178211
if (this.isCommandMessage(text)) {
179-
const prelude = `Command sent from Discord by ${nickname}:`;
212+
const prelude = Bot.substitutePattern(this.formatCommandPrelude, patternMap);
180213
this.ircClient.say(ircChannel, prelude);
181214
this.ircClient.say(ircChannel, text);
182215
} else {
183216
if (text !== '') {
184217
// Convert formatting
185218
text = formatFromDiscordToIRC(text);
219+
patternMap.text = text;
186220

187-
text = `<${displayUsername}> ${text}`;
221+
text = Bot.substitutePattern(this.formatIRCText, patternMap);
188222
logger.debug('Sending message to IRC', ircChannel, text);
189223
this.ircClient.say(ircChannel, text);
190224
}
191225

192226
if (message.attachments && message.attachments.size) {
193227
message.attachments.forEach((a) => {
194-
const urlMessage = `<${displayUsername}> ${a.url}`;
228+
patternMap.attachmentURL = a.url;
229+
const urlMessage = Bot.substitutePattern(this.formatURLAttachment, patternMap);
230+
195231
logger.debug('Sending attachment URL to IRC', ircChannel, urlMessage);
196232
this.ircClient.say(ircChannel, urlMessage);
197233
});
@@ -238,8 +274,17 @@ class Bot {
238274
return match;
239275
});
240276

277+
const patternMap = {
278+
author,
279+
text: withFormat,
280+
withMentions,
281+
discordChannel: `#${discordChannel.name}`,
282+
ircChannel: channel
283+
};
284+
241285
// Add bold formatting:
242-
const withAuthor = `**<${author}>** ${withMentions}`;
286+
// Use custom formatting from config / default formatting with bold author
287+
const withAuthor = Bot.substitutePattern(this.formatDiscord, patternMap);
243288
logger.debug('Sending message to Discord', withAuthor, channel, '->', discordChannelName);
244289
discordChannel.sendMessage(withAuthor);
245290
}

test/bot.test.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Bot from '../lib/bot';
99
import createDiscordStub from './stubs/discord-stub';
1010
import ClientStub from './stubs/irc-client-stub';
1111
import config from './fixtures/single-test-config.json';
12+
import configMsgFormatDefault from './fixtures/msg-formats-default.json';
1213

1314
chai.should();
1415
chai.use(sinonChai);
@@ -569,4 +570,178 @@ describe('Bot', function () {
569570
this.bot.sendToDiscord(username, '#irc', text);
570571
this.sendMessageStub.should.have.been.calledWith(expected);
571572
});
573+
574+
it('should successfully send messages with default config', function () {
575+
const bot = new Bot(configMsgFormatDefault);
576+
bot.connect();
577+
578+
bot.sendToDiscord('testuser', '#irc', 'test message');
579+
this.sendMessageStub.should.have.been.calledOnce;
580+
581+
const guild = createGuildStub();
582+
const message = {
583+
content: 'test message',
584+
mentions: { users: [] },
585+
channel: {
586+
name: 'discord'
587+
},
588+
author: {
589+
username: 'otherauthor',
590+
id: 'not bot id'
591+
},
592+
guild
593+
};
594+
595+
bot.sendToIRC(message);
596+
this.sendMessageStub.should.have.been.calledOnce;
597+
});
598+
599+
it('should not replace unmatched patterns', function () {
600+
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
601+
const bot = new Bot({ ...configMsgFormatDefault, format });
602+
bot.connect();
603+
604+
const username = 'testuser';
605+
const msg = 'test message';
606+
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
607+
bot.sendToDiscord(username, '#irc', msg);
608+
this.sendMessageStub.should.have.been.calledWith(expected);
609+
});
610+
611+
it('should respect custom formatting for Discord', function () {
612+
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
613+
const bot = new Bot({ ...configMsgFormatDefault, format });
614+
bot.connect();
615+
616+
const username = 'test';
617+
const msg = 'test @user <#1234>';
618+
const expected = `<test> #irc => #discord: ${msg}`;
619+
bot.sendToDiscord(username, '#irc', msg);
620+
this.sendMessageStub.should.have.been.calledWith(expected);
621+
});
622+
623+
it('should successfully send messages with default config', function () {
624+
this.bot = new Bot(configMsgFormatDefault);
625+
this.bot.connect();
626+
627+
this.bot.sendToDiscord('testuser', '#irc', 'test message');
628+
this.sendMessageStub.should.have.been.calledOnce;
629+
630+
const guild = createGuildStub();
631+
const message = {
632+
content: 'test message',
633+
mentions: { users: [] },
634+
channel: {
635+
name: 'discord'
636+
},
637+
author: {
638+
username: 'otherauthor',
639+
id: 'not bot id'
640+
},
641+
guild
642+
};
643+
644+
this.bot.sendToIRC(message);
645+
this.sendMessageStub.should.have.been.calledOnce;
646+
});
647+
648+
it('should not replace unmatched patterns', function () {
649+
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
650+
this.bot = new Bot({ ...configMsgFormatDefault, format });
651+
this.bot.connect();
652+
653+
const username = 'testuser';
654+
const msg = 'test message';
655+
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
656+
this.bot.sendToDiscord(username, '#irc', msg);
657+
this.sendMessageStub.should.have.been.calledWith(expected);
658+
});
659+
660+
it('should respect custom formatting for Discord', function () {
661+
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
662+
this.bot = new Bot({ ...configMsgFormatDefault, format });
663+
this.bot.connect();
664+
665+
const username = 'test';
666+
const msg = 'test @user <#1234>';
667+
const expected = `<test> #irc => #discord: ${msg}`;
668+
this.bot.sendToDiscord(username, '#irc', msg);
669+
this.sendMessageStub.should.have.been.calledWith(expected);
670+
});
671+
672+
it('should respect custom formatting for regular IRC output', function () {
673+
const format = { ircText: '<{$nickname}> {$discordChannel} => {$ircChannel}: {$text}' };
674+
this.bot = new Bot({ ...configMsgFormatDefault, format });
675+
this.bot.connect();
676+
677+
const guild = createGuildStub();
678+
const message = {
679+
content: 'test message',
680+
mentions: { users: [] },
681+
channel: {
682+
name: 'discord'
683+
},
684+
author: {
685+
username: 'testauthor',
686+
id: 'not bot id'
687+
},
688+
guild
689+
};
690+
const expected = '<testauthor> #discord => #irc: test message';
691+
692+
this.bot.sendToIRC(message);
693+
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
694+
});
695+
696+
it('should respect custom formatting for commands in IRC output', function () {
697+
const format = { commandPrelude: '{$nickname} from {$discordChannel} sent command to {$ircChannel}:' };
698+
this.bot = new Bot({ ...configMsgFormatDefault, format });
699+
this.bot.connect();
700+
701+
const text = '!testcmd';
702+
const guild = createGuildStub();
703+
const message = {
704+
content: text,
705+
mentions: { users: [] },
706+
channel: {
707+
name: 'discord'
708+
},
709+
author: {
710+
username: 'testauthor',
711+
id: 'not bot id'
712+
},
713+
guild
714+
};
715+
const expected = 'testauthor from #discord sent command to #irc:';
716+
717+
this.bot.sendToIRC(message);
718+
ClientStub.prototype.say.getCall(0).args.should.deep.equal(['#irc', expected]);
719+
ClientStub.prototype.say.getCall(1).args.should.deep.equal(['#irc', text]);
720+
});
721+
722+
it('should respect custom formatting for attachment URLs in IRC output', function () {
723+
const format = { urlAttachment: '<{$nickname}> {$discordChannel} => {$ircChannel}, attachment: {$attachmentURL}' };
724+
this.bot = new Bot({ ...configMsgFormatDefault, format });
725+
this.bot.connect();
726+
727+
const attachmentUrl = 'https://image/url.jpg';
728+
const guild = createGuildStub();
729+
const message = {
730+
content: '',
731+
mentions: { users: [] },
732+
attachments: createAttachments(attachmentUrl),
733+
channel: {
734+
name: 'discord'
735+
},
736+
author: {
737+
username: 'otherauthor',
738+
id: 'not bot id'
739+
},
740+
guild
741+
};
742+
743+
this.bot.sendToIRC(message);
744+
const expected = `<otherauthor> #discord => #irc, attachment: ${attachmentUrl}`;
745+
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
746+
});
572747
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"nickname": "Reactiflux",
3+
"server": "irc.freenode.net",
4+
"discordToken": "whatapassword",
5+
"commandCharacters": ["!", "."],
6+
"channelMapping": {
7+
"#discord": "#irc"
8+
}
9+
}

0 commit comments

Comments
 (0)