Skip to content

Commit

Permalink
Fix IRC quit messages sending to all channels by tracking users
Browse files Browse the repository at this point in the history
Keep a list of users in each channel, using the names event
in addition to the join/part events, so as to prevent spamming
unnecessary channels when the quit event is raised with all
channels from a server.

Fixes #213.
  • Loading branch information
Throne3d committed Apr 4, 2017
1 parent 27f9abe commit 1c8fa12
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 5 deletions.
22 changes: 21 additions & 1 deletion lib/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class Bot {
// ircChannel: IRC channel (e.g. #irc)
this.formatDiscord = this.format.discord || '**<{$author}>** {$withMentions}';

// Keep track of { channel => [list, of, usernames] } for ircStatusNotices
this.channelUsers = {};

this.channelMapping = {};

// Remove channel passwords from the mapping and lowercase IRC channel names
Expand Down Expand Up @@ -125,21 +128,38 @@ class Bot {
this.ircClient.on('join', (channel, nick) => {
if (!this.ircStatusNotices) return;
if (nick === this.nickname && !this.announceSelfJoin) return;
// self-join is announced before names (which includes own nick)
// so don't add nick to channelUsers
if (nick !== this.nickname) this.channelUsers[channel].push(nick);
this.sendExactToDiscord(channel, `*${nick}* has joined the channel`);
});

this.ircClient.on('part', (channel, nick, reason) => {
if (!this.ircStatusNotices || nick === this.nickname) return;
if (!this.ircStatusNotices) return;
// remove list of users when no longer in channel (as it will become out of date)
if (nick === this.nickname) {
delete this.channelUsers[channel];
return;
}
this.channelUsers[channel].splice(this.channelUsers[channel].indexOf(nick), 1);
this.sendExactToDiscord(channel, `*${nick}* has left the channel (${reason})`);
});

this.ircClient.on('quit', (nick, reason, channels) => {
if (!this.ircStatusNotices || nick === this.nickname) return;
channels.forEach((channel) => {
const index = this.channelUsers[channel].indexOf(nick);
if (index === -1) return;
this.channelUsers[channel].splice(index, 1);
this.sendExactToDiscord(channel, `*${nick}* has quit (${reason})`);
});
});

this.ircClient.on('names', (channel, nicks) => {
if (!this.ircStatusNotices) return;
this.channelUsers[channel] = Object.keys(nicks);
});

this.ircClient.on('action', (author, to, text) => {
this.sendToDiscord(author, to, `_${text}_`);
});
Expand Down
49 changes: 45 additions & 4 deletions test/bot-events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,26 +123,43 @@ describe('Bot Events', function () {
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, formattedText);
});

it('should keep track of users through names event when irc status notices enabled', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
bot.channelUsers.should.be.an('object');
const channel = '#channel';
// nick => '' means the user is not a special user
const nicks = { [bot.nickname]: '', user: '', user2: '@', user3: '+' };
bot.ircClient.emit('names', channel, nicks);
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname, 'user', 'user2', 'user3'] });
});

it('should send join messages to discord when config enabled', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
const nick = 'user';
const text = `*${nick}* has joined the channel`;
bot.ircClient.emit('join', channel, nick);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname, nick] });
});

it('should not announce itself joining by default', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
const nick = bot.nickname;
bot.ircClient.emit('join', channel, nick);
bot.sendExactToDiscord.should.not.have.been.called;
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname] });
});

it('should be possible to get the bot to announce itself joining', function () {
it('should announce the bot itself when config enabled', function () {
// self-join is announced before names (which includes own nick)
// hence don't trigger a names and don't expect anything of bot.channelUsers
const bot = createBot({ ...config, ircStatusNotices: true, announceSelfJoin: true });
bot.connect();
const channel = '#channel';
Expand All @@ -157,23 +174,46 @@ describe('Bot Events', function () {
bot.connect();
const channel = '#channel';
const nick = 'user';
bot.ircClient.emit('names', channel, { [bot.nickname]: '', [nick]: '' });
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname, nick] });
const reason = 'Leaving';
const text = `*${nick}* has left the channel (${reason})`;
bot.ircClient.emit('part', channel, nick, reason);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
// it should remove the nickname from the channelUsers list
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname] });
});

it('should not announce itself leaving a channel', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '', user: '' });
bot.channelUsers.should.deep.equal({ '#channel': [bot.nickname, 'user'] });
const reason = 'Leaving';
bot.ircClient.emit('part', channel, bot.nickname, reason);
bot.sendExactToDiscord.should.not.have.been.called;
// it should remove the nickname from the channelUsers list
bot.channelUsers.should.deep.equal({});
});

it('should send quit messages to discord when config enabled', function () {
it('should only send quit messages to discord for channels the user is tracked in', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel1 = '#channel1';
const channel2 = '#channel2';
const channel3 = '#channel3';
const nick = 'user';
bot.ircClient.emit('names', channel1, { [bot.nickname]: '', [nick]: '' });
bot.ircClient.emit('names', channel2, { [bot.nickname]: '' });
bot.ircClient.emit('names', channel3, { [bot.nickname]: '', [nick]: '' });
const reason = 'Quit: Leaving';
const text = `*${nick}* has quit (${reason})`;
bot.ircClient.emit('quit', nick, reason, [channel1, channel2]);
// send quit message for all channels on server, as the node-irc library does
bot.ircClient.emit('quit', nick, reason, [channel1, channel2, channel3]);
bot.sendExactToDiscord.should.have.been.calledTwice;
bot.sendExactToDiscord.getCall(0).args.should.deep.equal([channel1, text]);
bot.sendExactToDiscord.getCall(1).args.should.deep.equal([channel2, text]);
bot.sendExactToDiscord.getCall(1).args.should.deep.equal([channel3, text]);
});

it('should be possible to disable join/part/quit messages', function () {
Expand All @@ -183,6 +223,7 @@ describe('Bot Events', function () {
const nick = 'user';
const reason = 'Leaving';

bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
bot.ircClient.emit('join', channel, nick);
bot.ircClient.emit('part', channel, nick, reason);
bot.ircClient.emit('join', channel, nick);
Expand Down

0 comments on commit 1c8fa12

Please sign in to comment.