Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Fiaxhs committed Jan 11, 2018
2 parents 549b8fb + 68d0fd8 commit 90c901a
Show file tree
Hide file tree
Showing 16 changed files with 4,273 additions and 2,212 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ after_success:
- npm run report
node_js:
- '6'
- '7'
- '8'
42 changes: 41 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
# Changelog
This project adheres to [Semantic Versioning](http://semver.org/).

## [2.4.1] - 2017-07-16
## [2.5.0] - 2017-10-27
### Added
* Support multi-character command prefixes - [#301](https://github.com/reactiflux/discord-irc/pull/301)

* Enable auto-renicking by default, so the bot tries to get the target nickname after it fails - [#302](https://github.com/reactiflux/discord-irc/pull/302)

* Add the ability to ignore IRC/Discord users by nickname - [#322](https://github.com/reactiflux/discord-irc/pull/322)

### Fixed
* Improve IRC → Discord mentions around non-word characters and nickname prefix matches - [#273](https://github.com/reactiflux/discord-irc/pull/273)

* Default to UTF-8 encoding when bridging messages to prevent character corruption - [#315](https://github.com/reactiflux/discord-irc/pull/315)

* Fix a crash when using the bot in a group DM - [#316](https://github.com/reactiflux/discord-irc/pull/316)

* Use a `prepare` script for transpiling instead of `prepublish`, fixing `npm` installation direct from the GitHub repository - [#323](https://github.com/reactiflux/discord-irc/pull/323)

* Update dependencies:

- discord.js to 11.2.1
- sinon to ^4.0.1
- irc-upd to 0.8.0 - [#313](https://github.com/reactiflux/discord-irc/pull/313)
- simple-markdown to ^0.3.1
- coveralls to ^3.0.0
- mocha to ^4.0.0
- winston to 2.4.0

### Changed
* Add a link to the IRC spec in the README - [#307](https://github.com/reactiflux/discord-irc/pull/307)

* Drop testing for Node 7, add testing for Node 8 - [#329](https://github.com/reactiflux/discord-irc/pull/329)

## [2.4.2] - 2017-08-21
### Fixed
* Tests: Use globbing instead of `find` so tests work on Windows - [#279](https://github.com/reactiflux/discord-irc/pull/279)

### Changed
* Update dependency irc-upd to [0.7.0](https://github.com/Throne3d/node-irc/releases/tag/v0.7.0) - [#284](https://github.com/reactiflux/discord-irc/pull/284)

* Tests: Use Discord objects to simplify code - [#272](https://github.com/reactiflux/discord-irc/pull/272)

## [2.4.1] - 2017-07-16
### Added
* Falsy command preludes are no longer sent (previously would choose default prelude) - [#260](https://github.com/reactiflux/discord-irc/pull/260)

Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# discord-irc [![Build Status](https://travis-ci.org/reactiflux/discord-irc.svg?branch=master)](https://travis-ci.org/reactiflux/discord-irc) [![Coverage Status](https://coveralls.io/repos/github/reactiflux/discord-irc/badge.svg?branch=master)](https://coveralls.io/github/reactiflux/discord-irc?branch=master)

> Connects [Discord](https://discordapp.com/) and IRC channels by sending messages back and forth.
> Connects [Discord](https://discordapp.com/) and [IRC](https://www.ietf.org/rfc/rfc1459.txt) channels by sending messages back and forth.
## Example
![discord-irc](http://i.imgur.com/oI6iCrf.gif)
Expand Down Expand Up @@ -37,6 +37,10 @@ import config from './config.json';
discordIRC(config);
```

When installing the library, you may encounter an error relating to the installation of `iconv` or `node-icu-charset-detector`.
These are optional dependencies which allow you to set the target encoding of messages sent to Discord, as detailed below in the README.
Without these dependencies and the relevant setting, messages that aren't sent in UTF-8 may be corrupted when copied to Discord.

## Configuration
First you need to create a Discord bot user, which you can do by following the instructions [here](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token).

Expand Down Expand Up @@ -86,6 +90,10 @@ First you need to create a Discord bot user, which you can do by following the i
// with one of these characters (commands):
"commandCharacters": ["!", "."],
"ircStatusNotices": true, // Enables notifications in Discord when people join/part in the relevant IRC channel
"ignoreUsers": {
"irc": ["irc_nick1", "irc_nick2"], // Ignore specified IRC nicks and do not send their messages to Discord.
"discord": ["discord_nick1", "discord_nick2"] // Ignore specified Discord nicks and do not send their messages to IRC.
},
// List of webhooks per channel
"webhooks": {
"#discord": "https://discordapp.com/api/webhooks/id/token"
Expand All @@ -107,6 +115,13 @@ Example result:

![discord-webhook](http://i.imgur.com/lNeJIUI.jpg)

### Encodings

If you encounter trouble with some characters being corrupted from some clients (particularly umlauted characters, such as `ä` or `ö`), try installing the optional dependencies `iconv` and `node-icu-charset-detector`.
The bot will produce a warning when started if the IRC library is unable to convert between encodings.

Further information can be found in [the installation section of irc-upd](https://github.com/Throne3d/node-irc#character-set-detection).

## Tests
Run the tests with:
```bash
Expand Down
147 changes: 121 additions & 26 deletions lib/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class Bot {
this.announceSelfJoin = options.announceSelfJoin;
this.webhookOptions = options.webhooks;

// Nicks to ignore
this.ignoreUsers = options.ignoreUsers || {};
this.ignoreUsers.irc = this.ignoreUsers.irc || [];
this.ignoreUsers.discord = this.ignoreUsers.discord || [];

// "{$keyName}" => "variableValue"
// author/nickname: nickname of the user who sent the message
// discordChannel: Discord channel (e.g. #general)
Expand Down Expand Up @@ -98,9 +103,21 @@ class Bot {
floodProtection: true,
floodProtectionDelay: 500,
retryCount: 10,
autoRenick: true,
// options specified in the configuration file override the above defaults
...this.ircOptions
};

// default encoding to UTF-8 so messages to Discord aren't corrupted
if (!Object.prototype.hasOwnProperty.call(ircOptions, 'encoding')) {
if (irc.canConvertEncoding()) {
ircOptions.encoding = 'utf-8';
} else {
logger.warn('Cannot convert message encoding; you may encounter corrupted characters with non-English text.\n' +
'For information on how to fix this, please see: https://github.com/Throne3d/node-irc#character-set-detection');
}
}

this.ircClient = new irc.Client(this.server, this.nickname, ircOptions);
this.attachListeners();
}
Expand Down Expand Up @@ -229,9 +246,11 @@ class Bot {
}

static getDiscordNicknameOnServer(user, guild) {
const userDetails = guild.members.get(user.id);
if (userDetails) {
return userDetails.nickname || user.username;
if (guild) {
const userDetails = guild.members.get(user.id);
if (userDetails) {
return userDetails.nickname || user.username;
}
}
return user.username;
}
Expand Down Expand Up @@ -260,20 +279,33 @@ class Bot {
}

isCommandMessage(message) {
return this.commandCharacters.indexOf(message[0]) !== -1;
return this.commandCharacters.some(prefix => message.startsWith(prefix));
}

ignoredIrcUser(user) {
return this.ignoreUsers.irc.some(i => i.toLowerCase() === user.toLowerCase());
}

ignoredDiscordUser(user) {
return this.ignoreUsers.discord.some(i => i.toLowerCase() === user.toLowerCase());
}

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

sendToIRC(message) {
const author = message.author;
const { author } = message;
// Ignore messages sent by the bot itself:
if (author.id === this.discord.user.id ||
Object.keys(this.webhooks).some(channel => this.webhooks[channel].id === author.id)
) return;

// Do not send to IRC if this user is on the ignore list.
if (this.ignoredDiscordUser(author.username)) {
return;
}

const channelName = `#${message.channel.name}`;
const ircChannel = this.channelMapping[message.channel.id] ||
this.channelMapping[channelName];
Expand Down Expand Up @@ -340,8 +372,10 @@ class Bot {
.find('name', discordChannelName.slice(1)) : this.discord.channels.get(discordChannelName);

if (!discordChannel) {
logger.info('Tried to send a message to a channel the bot isn\'t in: ',
discordChannelName);
logger.info(
'Tried to send a message to a channel the bot isn\'t in: ',
discordChannelName
);
return null;
}
return discordChannel;
Expand Down Expand Up @@ -381,16 +415,34 @@ class Bot {
return null;
}

// compare two strings case-insensitively
// for discord mention matching
static caseComp(str1, str2) {
return str1.toUpperCase() === str2.toUpperCase();
}

// check if the first string starts with the second case-insensitively
// for discord mention matching
static caseStartsWith(str1, str2) {
return str1.toUpperCase().startsWith(str2.toUpperCase());
}

sendToDiscord(author, channel, text) {
const discordChannel = this.findDiscordChannel(channel);
if (!discordChannel) return;

// Do not send to Discord if this user is on the ignore list.
if (this.ignoredIrcUser(author)) {
return;
}

// Convert text formatting (bold, italics, underscore)
const withFormat = formatFromIRCToDiscord(text);

const patternMap = {
author,
nickname: author,
displayUsername: author,
text: withFormat,
discordChannel: `#${discordChannel.name}`,
ircChannel: channel
Expand All @@ -407,31 +459,74 @@ class Bot {
return;
}

const withMentions = withFormat.replace(/@[^\s]+\b/g, (match) => {
const search = match.substring(1);
const guild = discordChannel.guild;
const nickUser = guild.members.find('nickname', search);
if (nickUser) {
return nickUser;
}
const { guild } = discordChannel;
const withMentions = withFormat.replace(/@([^\s#]+)#(\d+)/g, (match, username, discriminator) => {
// @username#1234 => mention
// skips usernames including spaces for ease (they cannot include hashes)
// checks case insensitively as Discord does
const user = guild.members.find(x =>
Bot.caseComp(x.user.username, username.toUpperCase())
&& x.user.discriminator === discriminator);
if (user) return user;

const user = this.discord.users.find('username', search);
if (user) {
return user;
}
return match;
}).replace(/@([^\s]+)/g, (match, reference) => {
// this preliminary stuff is ultimately unnecessary
// but might save time over later more complicated calculations
// @nickname => mention, case insensitively
const nickUser = guild.members.find(x =>
x.nickname !== null && Bot.caseComp(x.nickname, reference));
if (nickUser) return nickUser;

// @username => mention, case insensitively
const user = guild.members.find(x => Bot.caseComp(x.user.username, reference));
if (user) return user;

// @role => mention, case insensitively
const role = guild.roles.find(x => x.mentionable && Bot.caseComp(x.name, reference));
if (role) return role;

// No match found checking the whole word. Check for partial matches now instead.
// @nameextra => [mention]extra, case insensitively, as Discord does
// uses the longest match, and if there are two, whichever is a match by case
let matchLength = 0;
let bestMatch = null;
let caseMatched = false;

// check if a partial match is found in reference and if so update the match values
const checkMatch = function (matchString, matchValue) {
// if the matchString is longer than the current best and is a match
// or if it's the same length but it matches by case unlike the current match
// set the best match to this matchString and matchValue
if ((matchString.length > matchLength && Bot.caseStartsWith(reference, matchString))
|| (matchString.length === matchLength && !caseMatched
&& reference.startsWith(matchString))) {
matchLength = matchString.length;
bestMatch = matchValue;
caseMatched = reference.startsWith(matchString);
}
};

const role = guild.roles.find('name', search);
if (role && role.mentionable) {
return role;
}
// check users by username and nickname
guild.members.forEach((member) => {
checkMatch(member.user.username, member);
if (bestMatch === member || member.nickname === null) return;
checkMatch(member.nickname, member);
});
// check mentionable roles by visible name
guild.roles.forEach((member) => {
if (!member.mentionable) return;
checkMatch(member.name, member);
});

// if a partial match was found, return the match and the unmatched trailing characters
if (bestMatch) return bestMatch.toString() + reference.substring(matchLength);

return match;
}).replace(/:(\w+):/g, (match, ident) => {
const guild = discordChannel.guild;
// :emoji: => mention, case sensitively
const emoji = guild.emojis.find(x => x.name === ident && x.requiresColons);
if (emoji) {
return `<:${emoji.identifier}>`; // identifier = name + ":" + id
}
if (emoji) return emoji;

return match;
});
Expand Down
3 changes: 2 additions & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ function readJSONConfig(filePath) {
function run() {
program
.version(version)
.option('-c, --config <path>',
.option(
'-c, --config <path>',
'Sets the path to the config file, otherwise read from the env variable CONFIG_FILE.'
)
.parse(process.argv);
Expand Down
16 changes: 11 additions & 5 deletions lib/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import SimpleMarkdown from 'simple-markdown';
import colors from 'irc-colors';

function mdNodeToIRC(node) {
let content = node.content;
let { content } = node;
if (Array.isArray(content)) content = content.map(mdNodeToIRC).join('');
if (node.type === 'em') return colors.italic(content);
if (node.type === 'strong') return colors.bold(content);
if (node.type === 'u') return colors.underline(content);
return content;
switch (node.type) {
case 'em':
return colors.italic(content);
case 'strong':
return colors.bold(content);
case 'u':
return colors.underline(content);
default:
return content;
}
}

export function formatFromDiscordToIRC(text) {
Expand Down
Loading

0 comments on commit 90c901a

Please sign in to comment.