Compare commits

..
2 Commits
Author SHA1 Message Date
Edward Jones 06ec04f670 2.4.2 2017-08-21 15:52:54 +01:00
Edward Jones 5cb5894ea3 Update changelog in preparation for 2.4.2 2017-08-21 15:50:03 +01:00
32 changed files with 4258 additions and 6729 deletions
+1 -17
View File
@@ -1,19 +1,3 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "12"
}
}
]
],
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
"presets": ["node6", "stage-0"]
}
-20
View File
@@ -1,20 +0,0 @@
.git
# Coverage directory used by tools like istanbul
coverage
.nyc_output
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Environment variables and configuration
.env
.environment
config.json
# Build
dist/
+2 -5
View File
@@ -1,19 +1,16 @@
{
"extends": "airbnb-base",
"parser": "@babel/eslint-parser",
"parser": "babel-eslint",
"env": {
"mocha": true,
"node": true
},
"rules": {
"arrow-parens": 0,
"global-require": 0,
"comma-dangle": 0,
"func-names": 0,
"import/no-extraneous-dependencies": [2, { "devDependencies": true }],
"import/prefer-default-export": 0,
"import/no-dynamic-require": 0,
"operator-linebreak": 0,
"implicit-arrow-linebreak": 0
"import/no-dynamic-require": 0
}
}
-26
View File
@@ -1,26 +0,0 @@
# This workflow will build the project's Dockerfile and publish images to Docker Hub
# For more information see: https://docs.github.com/en/actions/language-and-framework-guides/publishing-docker-images
name: Publish Docker image
on:
push:
branches: [ main ]
release:
types: [ published ]
jobs:
push_to_registry:
name: Publish to Docker Hub
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build and push to Docker Hub
uses: docker/build-push-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: discordirc/discord-irc
tag_with_ref: true
tag_with_sha: true
-49
View File
@@ -1,49 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 15.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test
- run: npm run save-coverage
- name: Report to Coveralls (parallel)
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: run-${{ matrix.node-version }}
parallel: true
finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Report to Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "@istanbuljs/nyc-config-babel"
}
+6
View File
@@ -0,0 +1,6 @@
language: node_js
after_success:
- npm run report
node_js:
- '6'
- '7'
+3 -121
View File
@@ -1,136 +1,18 @@
# Changelog
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.9.0] - 2020-10-03
This release comes with a (relatively large) change to use discord.js v12!
There were a lot of breaking changes in the upstream library, including removal of support for Node <12, which prompted a 2.9.0-alpha release before this main version.
Everything seemed to go fine, which is why you're now receiving this latest update!
### Added
* IRC nick colors are now customizable in the config (thanks to [KWeaver87](https://github.com/reactiflux/discord-irc/pull/561))
### Changed
* Upgrade discord.js to 12.3.1 (the pre-release came with 12.2.0) - this changes a lot of how the bot works behind the scenes, and prepares us for upcoming Discord API changes - [#555](https://github.com/reactiflux/discord-irc/pull/555), [#564](https://github.com/reactiflux/discord-irc/pull/564)
* Add support for Node 14 ([#549](https://github.com/reactiflux/discord-irc/pull/549)); drop support for Node 6, 8 and 10 ([#550](https://github.com/reactiflux/discord-irc/pull/550))
### Fixed
* A bunch of vulnerabilities listed in `npm audit` are now gone - [#544](https://github.com/reactiflux/discord-irc/pull/544), [#548](https://github.com/reactiflux/discord-irc/pull/548), [#551](https://github.com/reactiflux/discord-irc/pull/551), [#552](https://github.com/reactiflux/discord-irc/pull/552), [#553](https://github.com/reactiflux/discord-irc/pull/553), [#562](https://github.com/reactiflux/discord-irc/pull/562)
* Log messages now typically take up one line again (fixing a relatively long-standing bug, at this point – oops!) - [#554](https://github.com/reactiflux/discord-irc/pull/554)
* Mentioning the same user more than once in a single message is now fixed (thanks to [Qyriad](https://github.com/reactiflux/discord-irc/pull/541))
* Our Discord client now uses a retry limit of 3 (rather than 1) on internal server errors (hopefully fixing [#461](https://github.com/reactiflux/discord-irc/issues/461)) - [#565](https://github.com/reactiflux/discord-irc/pull/565)
## [2.8.1] - 2020-03-16
### Fixed
* Large avatars failed to display when bridging through webhooks - (thanks to [Miosame](https://github.com/reactiflux/discord-irc/pull/511), follow up in [#530](https://github.com/reactiflux/discord-irc/pull/530))
* Update acorn to 7.1.1 - [#534](https://github.com/reactiflux/discord-irc/pull/534)
* Remove code coverage instrumentation from `dist/` files - [#536](https://github.com/reactiflux/discord-irc/pull/536)
## [2.8.0] - 2019-12-14
### Added
* `format.webhookAvatarURL`, to customize the unrecognized user webhook avatar (thanks to [Geo1088](https://github.com/reactiflux/discord-irc/pull/419))
* `parallelPingFix`, disabled by default, to prevent users of both Discord and IRC getting pings whenever their messages are mirrored to IRC (thanks to [qaisjp](https://github.com/reactiflux/discord-irc/pull/243) originally, follow up in [#502](https://github.com/reactiflux/discord-irc/pull/502) and [#520](https://github.com/reactiflux/discord-irc/pull/520))
* `ignoreUsers.discordIds`, to ignore specific Discord users through the bridge by ID instead of name (thanks to [nsavch](https://github.com/reactiflux/discord-irc/pull/508))
* A basic Docker image, found at [discordirc/discord-irc](https://hub.docker.com/r/discordirc/discord-irc)! This may not be often updated with the release tags we publish on GitHub, but it should contain a `latest` tag in addition to whichever Git hash is available on main (thanks to [gdude2002](https://github.com/reactiflux/discord-irc/pull/421), follow up in [#498](https://github.com/reactiflux/discord-irc/pull/498))
### Changed
* Add support for Node 13, drop testing for Node 6 - [#521](https://github.com/reactiflux/discord-irc/pull/521)
### Fixed
* Upgrade various dependencies: babel, commander, coveralls, discord.js, eslint, mocha, simple-markdown, sinon, sinon-chai - [#488](https://github.com/reactiflux/discord-irc/pull/488), [#503](https://github.com/reactiflux/discord-irc/pull/503), [#522](https://github.com/reactiflux/discord-irc/pull/522), [#523](https://github.com/reactiflux/discord-irc/pull/523), [#524](https://github.com/reactiflux/discord-irc/pull/524/files), [#525](https://github.com/reactiflux/discord-irc/pull/525)
## [2.7.2] - 2019-08-08
### Fixed
* Defer to discord permissions for allowing `@everyone` and `@here` in webhook messages - [#497](https://github.com/reactiflux/discord-irc/pull/497)
* Support Node 10 and 12 - [#499](https://github.com/reactiflux/discord-irc/pull/499)
* Upgrade dependencies
* Tests: Fix lint config deprecation - [#500](https://github.com/reactiflux/discord-irc/pull/500)
* Tests: Ensure all tests are run in dev environment - [#501](https://github.com/reactiflux/discord-irc/pull/501)
## [2.7.1] - 2019-06-15
### Changed
* Upgraded dependencies.
## [2.7.0] - 2019-04-02
### Changed
* Convert channel mentions to codified mentions (thanks to [Throne3d](https://github.com/reactiflux/discord-irc/pull/476)).
* Match IRC style mentions at the beginning of message (thanks to [rdb](https://github.com/reactiflux/discord-irc/pull/470)).
* Upgraded dependencies.
## [2.6.2] - 2018-09-19
### Changed
* Upgraded dependencies.
## [2.6.1] - 2018-05-11
### Changed
* Upgraded dependencies.
## [2.6.0] - 2018-03-22
### Added
* Support for posting messages to Discord using webhooks (thanks to
[Fiaxhs](https://github.com/reactiflux/discord-irc/pull/230)!).
Webhooks lets you override nicknames and avatars, so messages coming from IRC
can appear as regular Discord messages:
![discord-webhook](http://i.imgur.com/lNeJIUI.jpg)
To enable webhooks, follow part 1 of [this
guide](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)
to create and retrieve a webhook URL for a specific channel, then enable it in
discord-irc's config as follows:
```json
"webhooks": {
"#discord-channel": "https://discord.com/api/webhooks/id/token"
}
```
## [2.5.1] - 2018-01-18
### Fixed
* Upgraded dependencies.
## [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)
* Update dependency irc-upd to [0.7.0](https://github.com/Throne3d/node-irc/releases/tag/v0.7.0); adds optional auto-renick functionality - [#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)
-15
View File
@@ -1,15 +0,0 @@
FROM node:12-alpine
ENV LIBRARY_PATH=/lib:/usr/lib
RUN mkdir /bot
COPY . /bot
WORKDIR /bot
RUN apk add --update tini && \
npm install && \
npm run build && \
mkdir /config
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["npm", "start", "--", "--config", "/config/config.json"]
-2
View File
@@ -1,2 +0,0 @@
build:
docker-compose -f docker-compose.yml up -d --build --remove-orphans
+6 -65
View File
@@ -1,13 +1,12 @@
[![Coverage Status](https://coveralls.io/repos/github/reactiflux/discord-irc/badge.svg?branch=main)](https://coveralls.io/github/reactiflux/discord-irc?branch=main)
# 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://discord.com/) and [IRC](https://www.ietf.org/rfc/rfc1459.txt) channels by sending messages back and forth.
> Connects [Discord](https://discordapp.com/) and IRC channels by sending messages back and forth.
## Example
![discord-irc](http://i.imgur.com/oI6iCrf.gif)
## Installation and usage
**Note**: discord-irc requires Node.js version 12 or newer, as it depends on [discord.js](https://github.com/hydrabolt/discord.js).
Future versions may require newer Node.js versions, though we should support active releases.
**Note**: discord-irc requires Node.js version 6 or newer, as it depends on [discord.js](https://github.com/hydrabolt/discord.js).
Before you can run discord-irc you need to create a configuration file by
following the instructions [here](https://github.com/reactiflux/discord-irc#configuration).
@@ -38,24 +37,6 @@ import config from './config.json';
discordIRC(config);
```
## Docker
As an alternative to running discord-irc directly on your machine, we provide a [Docker container image](https://hub.docker.com/r/discordirc/discord-irc).
After creating a configuration file, you can fetch the image from Docker Hub and run it with the following command:
```bash
docker run -v /path/to/config:/config/config.json discordirc/discord-irc
```
If you've checked out the repository already, you can build the Docker image locally and run that instead:
```bash
docker build -t discord-irc .
docker run -v /path/to/config:/config/config.json discord-irc
```
Note that the path to the config file on the host (`/path/to/config`) _must_ be a valid absolute path to a config file.
Otherwise, you may get the error "illegal operation on a directory".
## 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).
@@ -88,40 +69,23 @@ First you need to create a Discord bot user, which you can do by following the i
},
"ircOptions": { // Optional node-irc options
"floodProtection": false, // On by default
"floodProtectionDelay": 1000, // 500 by default
"port": "6697", // 6697 by default
"secure": true, // enable SSL, false by default
"sasl": true, // false by default
"username": "test", // nodeirc by default
"password": "p455w0rd" // empty by default
"floodProtectionDelay": 1000 // 500 by default
},
"format": { // Optional custom formatting options
// Patterns, represented by {$patternName}, are replaced when sending messages
"commandPrelude": "Command sent by {$nickname}", // Message sent before a command
"ircText": "<{$displayUsername}> {$text}", // When sending a message to IRC
"urlAttachment": "<{$displayUsername}> {$attachmentURL}", // When sending a Discord attachment to IRC
"discord": "**<{$author}>** {$withMentions}", // When sending a message to Discord
"discord": "**<{$author}>** {$withMentions}" // When sending a message to Discord
// Other patterns that can be used:
// {$discordChannel} (e.g. #general)
// {$ircChannel} (e.g. #irc)
"webhookAvatarURL": "https://robohash.org/{$nickname}" // Default avatar to use for webhook messages
},
"ircNickColor": false, // Gives usernames a color in IRC for better readability (on by default)
"ircNickColors": ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green', 'dark_green', 'magenta', 'light_magenta', 'orange', 'yellow', 'cyan', 'light_cyan'], // Which irc-upd colors to use
"parallelPingFix": true, // Prevents users of both IRC and Discord from being mentioned in IRC when they speak in Discord (off by default)
// Makes the bot hide the username prefix for messages that start
// 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.
"discordIds": ["198528216523210752"] // Ignore specified Discord ids and do not send their messages to IRC.
},
// List of webhooks per channel
"webhooks": {
"#discord": "https://discord.com/api/webhooks/id/token"
}
"ircStatusNotices": true // Enables notifications in Discord when people join/part in the relevant IRC channel
}
]
```
@@ -130,29 +94,6 @@ The `ircOptions` object is passed directly to irc-upd ([available options](https
To retrieve a discord channel ID, write `\#channel` on the relevant server – it should produce something of the form `<#1234567890>`, which you can then use in the `channelMapping` config.
### Webhooks
Webhooks lets you override nicknames and avatars, so messages coming from IRC
can appear as regular Discord messages:
![discord-webhook](http://i.imgur.com/lNeJIUI.jpg)
To enable webhooks, follow part 1 of [this
guide](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)
to create and retrieve a webhook URL for a specific channel, then enable it in
discord-irc's config as follows:
```json
"webhooks": {
"#discord-channel": "https://discord.com/api/webhooks/id/token"
}
```
### 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
-10
View File
@@ -1,10 +0,0 @@
version: "3.9"
services:
discord-irc:
image: discord-irc
restart: unless-stopped
build:
context: .
dockerfile: ./Dockerfile
volumes:
- ./config:/config/config.json
+42 -255
View File
@@ -1,17 +1,13 @@
import _ from 'lodash';
import irc from 'irc-upd';
import logger from 'winston';
import discord from 'discord.js';
import logger from './logger';
import { ConfigurationError } from './errors';
import { validateChannelMapping } from './validators';
import { formatFromDiscordToIRC, formatFromIRCToDiscord } from './formatting';
// Usernames need to be between 2 and 32 characters for webhooks:
const USERNAME_MIN_LENGTH = 2;
const USERNAME_MAX_LENGTH = 32;
const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordToken'];
const DEFAULT_NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
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;
@@ -29,10 +25,7 @@ class Bot {
validateChannelMapping(options.channelMapping);
this.discord = new discord.Client({
autoReconnect: true,
retryLimit: 3,
});
this.discord = new discord.Client({ autoReconnect: true });
this.server = options.server;
this.nickname = options.nickname;
@@ -40,18 +33,9 @@ class Bot {
this.discordToken = options.discordToken;
this.commandCharacters = options.commandCharacters || [];
this.ircNickColor = options.ircNickColor !== false; // default to true
this.ircNickColors = options.ircNickColors || DEFAULT_NICK_COLORS;
this.parallelPingFix = options.parallelPingFix === true; // default: false
this.channels = _.values(options.channelMapping);
this.ircStatusNotices = options.ircStatusNotices;
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 || [];
this.ignoreUsers.discordIds = this.ignoreUsers.discordIds || [];
// "{$keyName}" => "variableValue"
// author/nickname: nickname of the user who sent the message
@@ -65,7 +49,6 @@ class Bot {
// attachmentURL: the URL of the attachment (only applicable in formatURLAttachment)
this.formatIRCText = this.format.ircText || '<{$displayUsername}> {$text}';
this.formatURLAttachment = this.format.urlAttachment || '<{$displayUsername}> {$attachmentURL}';
// "{$keyName}" => "variableValue"
// side: "Discord" or "IRC"
if ('commandPrelude' in this.format) {
@@ -78,15 +61,10 @@ class Bot {
// withMentions: text with appropriate mentions reformatted
this.formatDiscord = this.format.discord || '**<{$author}>** {$withMentions}';
// "{$keyName} => "variableValue"
// nickname: nickame of IRC message sender
this.formatWebhookAvatarURL = this.format.webhookAvatarURL;
// Keep track of { channel => [list, of, usernames] } for ircStatusNotices
this.channelUsers = {};
this.channelMapping = {};
this.webhooks = {};
// Remove channel passwords from the mapping and lowercase IRC channel names
_.forOwn(options.channelMapping, (ircChan, discordChan) => {
@@ -101,16 +79,6 @@ class Bot {
logger.debug('Connecting to IRC and Discord');
this.discord.login(this.discordToken);
// Extract id and token from Webhook urls and connect.
_.forOwn(this.webhookOptions, (url, channel) => {
const [id, token] = url.split('/').slice(-2);
const client = new discord.WebhookClient(id, token);
this.webhooks[channel] = {
id,
client
};
});
const ircOptions = {
userName: this.nickname,
realName: this.nickname,
@@ -118,31 +86,13 @@ 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();
}
disconnect() {
this.ircClient.disconnect();
this.discord.destroy();
Object.values(this.webhooks).forEach(x => x.client.destroy());
}
attachListeners() {
this.discord.on('ready', () => {
logger.info('Connected to Discord');
@@ -267,11 +217,9 @@ class Bot {
}
static getDiscordNicknameOnServer(user, guild) {
if (guild) {
const userDetails = guild.members.cache.get(user.id);
if (userDetails) {
return userDetails.nickname || user.username;
}
const userDetails = guild.members.get(user.id);
if (userDetails) {
return userDetails.nickname || user.username;
}
return user.username;
}
@@ -279,39 +227,28 @@ class Bot {
parseText(message) {
const text = message.mentions.users.reduce((content, mention) => {
const displayName = Bot.getDiscordNicknameOnServer(mention, message.guild);
const userMentionRegex = RegExp(`<@(&|!)?${mention.id}>`, 'g');
return content.replace(userMentionRegex, `@${displayName}`);
return content.replace(`<@${mention.id}>`, `@${displayName}`)
.replace(`<@!${mention.id}>`, `@${displayName}`)
.replace(`<@&${mention.id}>`, `@${displayName}`);
}, message.content);
return text
.replace(/\n|\r\n|\r/g, ' ')
.replace(/<#(\d+)>/g, (match, channelId) => {
const channel = this.discord.channels.cache.get(channelId);
const channel = this.discord.channels.get(channelId);
if (channel) return `#${channel.name}`;
return '#deleted-channel';
})
.replace(/<@&(\d+)>/g, (match, roleId) => {
const role = message.guild.roles.cache.get(roleId);
const role = message.guild.roles.get(roleId);
if (role) return `@${role.name}`;
return '@deleted-role';
})
.replace(/<a?(:\w+:)\d+>/g, (match, emoteName) => emoteName);
.replace(/<(:\w+:)\d+>/g, (match, emoteName) => emoteName);
}
isCommandMessage(message) {
return this.commandCharacters.some(prefix => message.startsWith(prefix));
}
ignoredIrcUser(user) {
return this.ignoreUsers.irc.some(i => i.toLowerCase() === user.toLowerCase());
}
ignoredDiscordUser(discordUser) {
const ignoredName = this.ignoreUsers.discord.some(
i => i.toLowerCase() === discordUser.username.toLowerCase()
);
const ignoredId = this.ignoreUsers.discordIds.some(i => i === discordUser.id);
return ignoredName || ignoredId;
return this.commandCharacters.indexOf(message[0]) !== -1;
}
static substitutePattern(message, patternMapping) {
@@ -319,16 +256,9 @@ class Bot {
}
sendToIRC(message) {
const { author } = message;
const author = message.author;
// 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)) {
return;
}
if (author.id === this.discord.user.id) return;
const channelName = `#${message.channel.name}`;
const ircChannel = this.channelMapping[message.channel.id] ||
@@ -340,16 +270,9 @@ class Bot {
const nickname = Bot.getDiscordNicknameOnServer(author, fromGuild);
let text = this.parseText(message);
let displayUsername = nickname;
if (this.parallelPingFix) {
// Prevent users of both IRC and Discord from
// being mentioned in IRC when they talk in Discord.
displayUsername = `${displayUsername.slice(0, 1)}\u200B${displayUsername.slice(1)}`;
}
if (this.ircNickColor) {
const colorIndex = (nickname.charCodeAt(0) + nickname.length) % this.ircNickColors.length;
displayUsername = irc.colors.wrap(this.ircNickColors[colorIndex], displayUsername);
const colorIndex = (nickname.charCodeAt(0) + nickname.length) % NICK_COLORS.length;
displayUsername = irc.colors.wrap(NICK_COLORS[colorIndex], nickname);
}
const patternMap = {
@@ -398,21 +321,13 @@ class Bot {
const discordChannelName = this.invertedMapping[ircChannel.toLowerCase()];
if (discordChannelName) {
// #channel -> channel before retrieving and select only text channels:
let discordChannel = null;
if (this.discord.channels.cache.has(discordChannelName)) {
discordChannel = this.discord.channels.cache.get(discordChannelName);
} else if (discordChannelName.startsWith('#')) {
discordChannel = this.discord.channels.cache
.filter(c => c.type === 'text')
.find(c => c.name === discordChannelName.slice(1));
}
const discordChannel = discordChannelName.startsWith('#') ? this.discord.channels
.filter(c => c.type === 'text')
.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;
@@ -420,72 +335,16 @@ class Bot {
return null;
}
findWebhook(ircChannel) {
const discordChannelName = this.invertedMapping[ircChannel.toLowerCase()];
return discordChannelName && this.webhooks[discordChannelName];
}
getDiscordAvatar(nick, channel) {
const guildMembers = this.findDiscordChannel(channel).guild.members.cache;
const findByNicknameOrUsername = caseSensitive =>
(member) => {
if (caseSensitive) {
return member.user.username === nick || member.nickname === nick;
}
const nickLowerCase = nick.toLowerCase();
return member.user.username.toLowerCase() === nickLowerCase
|| (member.nickname && member.nickname.toLowerCase() === nickLowerCase);
};
// Try to find exact matching case
let users = guildMembers.filter(findByNicknameOrUsername(true));
// Now let's search case insensitive.
if (users.size === 0) {
users = guildMembers.filter(findByNicknameOrUsername(false));
}
// No matching user or more than one => default avatar
if (users && users.size === 1) {
const url = users.first().user.avatarURL({ size: 128, format: 'png' });
if (url) return url;
}
// If there isn't a URL format, don't send an avatar at all
if (this.formatWebhookAvatarURL) {
return Bot.substitutePattern(this.formatWebhookAvatarURL, { nickname: nick });
}
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
@@ -502,107 +361,35 @@ class Bot {
return;
}
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.cache.find(x =>
Bot.caseComp(x.user.username, username)
&& x.user.discriminator === discriminator);
if (user) return user;
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;
}
return match;
}).replace(/^([^@\s:,]+)[:,]|@([^\s]+)/g, (match, startRef, atRef) => {
const reference = startRef || atRef;
const user = this.discord.users.find('username', search);
if (user) {
return user;
}
// this preliminary stuff is ultimately unnecessary
// but might save time over later more complicated calculations
// @nickname => mention, case insensitively
const nickUser = guild.members.cache.find(x =>
x.nickname && Bot.caseComp(x.nickname, reference));
if (nickUser) return nickUser;
// @username => mention, case insensitively
const user = guild.members.cache.find(x => Bot.caseComp(x.user.username, reference));
if (user) return user;
// @role => mention, case insensitively
const role = guild.roles.cache.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);
}
};
// check users by username and nickname
guild.members.cache.forEach((member) => {
checkMatch(member.user.username, member);
if (bestMatch === member || !member.nickname) return;
checkMatch(member.nickname, member);
});
// check mentionable roles by visible name
guild.roles.cache.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);
const role = guild.roles.find('name', search);
if (role && role.mentionable) {
return role;
}
return match;
}).replace(/:(\w+):/g, (match, ident) => {
// :emoji: => mention, case sensitively
const emoji = guild.emojis.cache.find(x => x.name === ident && x.requiresColons);
if (emoji) return emoji;
const guild = discordChannel.guild;
const emoji = guild.emojis.find(x => x.name === ident && x.requiresColons);
if (emoji) {
return `<:${emoji.identifier}>`; // identifier = name + ":" + id
}
return match;
}).replace(/#([^\s#@'!?,.]+)/g, (match, channelName) => {
// channel names can't contain spaces, #, @, ', !, ?, , or .
// (based on brief testing. they also can't contain some other symbols,
// but these seem likely to be common around channel references)
// discord matches channel names case insensitively
const chan = guild.channels.cache.find(x => Bot.caseComp(x.name, channelName));
return chan || match;
});
// Webhooks first
const webhook = this.findWebhook(channel);
if (webhook) {
logger.debug('Sending message to Discord via webhook', withMentions, channel, '->', `#${discordChannel.name}`);
const permissions = discordChannel.permissionsFor(this.discord.user);
let canPingEveryone = false;
if (permissions) {
canPingEveryone = permissions.has(discord.Permissions.FLAGS.MENTION_EVERYONE);
}
const avatarURL = this.getDiscordAvatar(author, channel);
const username = _.padEnd(author.substring(0, USERNAME_MAX_LENGTH), USERNAME_MIN_LENGTH, '_');
webhook.client.send(withMentions, {
username,
avatarURL,
disableMentions: canPingEveryone ? 'none' : 'everyone',
}).catch(logger.error);
return;
}
patternMap.withMentions = withMentions;
// Add bold formatting:
+4 -6
View File
@@ -3,6 +3,7 @@
import fs from 'fs';
import program from 'commander';
import path from 'path';
import checkEnv from 'check-env';
import stripJsonComments from 'strip-json-comments';
import { endsWith } from 'lodash';
import * as helpers from './helpers';
@@ -25,17 +26,14 @@ 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);
const opts = program.opts();
// If no config option is given, try to use the env variable:
if (opts.config) process.env.CONFIG_FILE = opts.config;
if (!process.env.CONFIG_FILE) throw new Error('Missing environment variable CONFIG_FILE');
if (!program.config) checkEnv(['CONFIG_FILE']);
else process.env.CONFIG_FILE = program.config;
const completePath = path.resolve(process.cwd(), process.env.CONFIG_FILE);
const config = endsWith(process.env.CONFIG_FILE, '.js') ?
+5 -11
View File
@@ -3,18 +3,12 @@ import SimpleMarkdown from 'simple-markdown';
import colors from 'irc-colors';
function mdNodeToIRC(node) {
let { content } = node;
let content = node.content;
if (Array.isArray(content)) content = content.map(mdNodeToIRC).join('');
switch (node.type) {
case 'em':
return colors.italic(content);
case 'strong':
return colors.bold(content);
case 'u':
return colors.underline(content);
default:
return content;
}
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;
}
export function formatFromDiscordToIRC(text) {
+6
View File
@@ -1,7 +1,13 @@
#!/usr/bin/env node
import logger from 'winston';
import { createBots } from './helpers';
/* istanbul ignore next */
if (process.env.NODE_ENV === 'development') {
logger.level = 'debug';
}
/* istanbul ignore next */
if (!module.parent) {
require('./cli').default();
-27
View File
@@ -1,27 +0,0 @@
import winston, { format } from 'winston';
import { inspect } from 'util';
function simpleInspect(value) {
if (typeof value === 'string') return value;
return inspect(value, { depth: null });
}
function formatter(info) {
const splat = info[Symbol.for('splat')] || [];
const stringifiedRest = splat.length > 0 ? ` ${splat.map(simpleInspect).join(' ')}` : '';
const padding = (info.padding && info.padding[info.level]) || '';
return `${info.timestamp} ${info.level}:${padding} ${info.message}${stringifiedRest}`;
}
const logger = winston.createLogger({
transports: [new winston.transports.Console()],
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
format: format.combine(
format.colorize(),
format.timestamp(),
format.printf(formatter),
)
});
export default logger;
+4057 -5456
View File
File diff suppressed because it is too large Load Diff
+29 -31
View File
@@ -1,6 +1,6 @@
{
"name": "discord-irc",
"version": "2.9.0",
"version": "2.4.2",
"description": "Connects IRC and Discord channels by sending messages back and forth.",
"keywords": [
"discord",
@@ -11,7 +11,7 @@
"reactiflux"
],
"engines": {
"node": ">=12.0.0"
"node": ">=6.0.0"
},
"main": "dist/index.js",
"bin": "dist/index.js",
@@ -25,10 +25,10 @@
"scripts": {
"start": "node dist/index.js",
"build": "babel lib --out-dir dist",
"prepare": "npm run build",
"prepublish": "npm run build",
"lint": "eslint . --ignore-path .gitignore",
"coverage": "cross-env NODE_ENV=test nyc --require @babel/register mocha -- 'test/*.test.js'",
"save-coverage": "nyc report --reporter=lcov",
"coverage": "nyc --require babel-core/register _mocha -- test/*.test.js",
"report": "nyc report --reporter=text-lcov | coveralls",
"test": "npm run lint && npm run coverage"
},
"author": {
@@ -36,33 +36,31 @@
},
"license": "MIT",
"dependencies": {
"commander": "^7.2.0",
"discord.js": "^12.5.3",
"irc-colors": "1.5.0",
"irc-formatting": "1.0.0-rc3",
"irc-upd": "0.11.0",
"lodash": "^4.17.21",
"simple-markdown": "^0.7.3",
"strip-json-comments": "^3.1.1",
"winston": "^3.3.3"
"check-env": "1.3.0",
"commander": "2.11.0",
"discord.js": "11.1.0",
"irc-upd": "0.7.0",
"irc-colors": "^1.3.2",
"irc-formatting": "^1.0.0-rc3",
"lodash": "^4.17.4",
"simple-markdown": "^0.2.1",
"strip-json-comments": "2.0.1",
"winston": "2.3.1"
},
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.13.16",
"@babel/eslint-parser": "^7.13.14",
"@babel/preset-env": "^7.13.15",
"@babel/register": "^7.13.16",
"@istanbuljs/nyc-config-babel": "^3.0.0",
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.3.4",
"coveralls": "^3.1.0",
"cross-env": "^7.0.3",
"eslint": "^7.25.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1",
"mocha": "^8.3.2",
"nyc": "^15.1.0",
"sinon": "^10.0.0",
"sinon-chai": "^3.6.0"
"babel-cli": "^6.6.5",
"babel-core": "^6.7.4",
"babel-eslint": "^7.0.0",
"babel-preset-node6": "^11.0.0",
"babel-preset-stage-0": "^6.5.0",
"chai": "^4.0.2",
"coveralls": "^2.11.14",
"eslint": "^4.1.1",
"eslint-config-airbnb-base": "^11.0.1",
"eslint-plugin-import": "^2.2.0",
"mocha": "^3.1.2",
"nyc": "^11.0.3",
"sinon": "^2.0.0",
"sinon-chai": "^2.8.0"
}
}
+5 -9
View File
@@ -4,10 +4,9 @@ import sinonChai from 'sinon-chai';
import sinon from 'sinon';
import irc from 'irc-upd';
import discord from 'discord.js';
import logger from 'winston';
import Bot from '../lib/bot';
import logger from '../lib/logger';
import createDiscordStub from './stubs/discord-stub';
import createWebhookStub from './stubs/webhook-stub';
import ClientStub from './stubs/irc-client-stub';
import config from './fixtures/single-test-config.json';
@@ -15,7 +14,7 @@ chai.should();
chai.use(sinonChai);
describe('Bot Events', function () {
const sandbox = sinon.createSandbox({
const sandbox = sinon.sandbox.create({
useFakeTimers: false,
useFakeServer: false
});
@@ -35,9 +34,9 @@ describe('Bot Events', function () {
this.warnSpy = sandbox.stub(logger, 'warn');
this.errorSpy = sandbox.stub(logger, 'error');
this.sendStub = sandbox.stub();
this.getUserStub = sandbox.stub();
irc.Client = ClientStub;
discord.Client = createDiscordStub(this.sendStub);
discord.WebhookClient = createWebhookStub(this.sendStub);
discord.Client = createDiscordStub(this.sendStub, this.getUserStub);
ClientStub.prototype.send = sandbox.stub();
ClientStub.prototype.join = sandbox.stub();
this.bot = createBot();
@@ -45,7 +44,6 @@ describe('Bot Events', function () {
});
afterEach(function () {
this.bot.disconnect();
sandbox.restore();
});
@@ -160,9 +158,7 @@ describe('Bot Events', function () {
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: '+'
};
const nicks = { [bot.nickname]: '', user: '', user2: '@', user3: '+' };
bot.ircClient.emit('names', channel, nicks);
const channelNicks = new Set([bot.nickname, 'user', 'user2', 'user3']);
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
+71 -481
View File
@@ -3,70 +3,65 @@ import chai from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import irc from 'irc-upd';
import logger from 'winston';
import discord from 'discord.js';
import logger from '../lib/logger';
import Bot from '../lib/bot';
import createDiscordStub from './stubs/discord-stub';
import ClientStub from './stubs/irc-client-stub';
import createWebhookStub from './stubs/webhook-stub';
import config from './fixtures/single-test-config.json';
import configMsgFormatDefault from './fixtures/msg-formats-default.json';
const { expect } = chai;
chai.should();
chai.use(sinonChai);
describe('Bot', function () {
const sandbox = sinon.createSandbox({
const sandbox = sinon.sandbox.create({
useFakeTimers: false,
useFakeServer: false
});
const createGuildStub = () => ({
roles: new discord.Collection(),
members: new discord.Collection(),
emojis: new discord.Collection()
});
beforeEach(function () {
this.infoSpy = sandbox.stub(logger, 'info');
this.debugSpy = sandbox.stub(logger, 'debug');
this.errorSpy = sandbox.stub(logger, 'error');
this.sendStub = sandbox.stub();
this.discordUsers = new discord.Collection();
irc.Client = ClientStub;
discord.Client = createDiscordStub(this.sendStub);
this.guild = createGuildStub();
discord.Client = createDiscordStub(this.sendStub, this.guild, this.discordUsers);
ClientStub.prototype.say = sandbox.stub();
ClientStub.prototype.send = sandbox.stub();
ClientStub.prototype.join = sandbox.stub();
this.sendWebhookMessageStub = sandbox.stub();
discord.WebhookClient = createWebhookStub(this.sendWebhookMessageStub);
this.bot = new Bot(config);
this.bot.connect();
this.setCustomBot = conf => {
this.bot = new Bot(conf);
this.guild = this.bot.discord.guilds.cache.first();
this.bot.connect();
};
this.setCustomBot(config);
// modified variants of https://github.com/discordjs/discord.js/blob/stable/src/client/ClientDataManager.js
// (for easier stubbing)
this.addUser = function (user, member = null) {
const userObj = new discord.User(this.bot.discord, user);
// also set guild members
const guildMember = { ...(member || user), user: userObj };
const guildMember = Object.assign({}, member || user, { user: userObj });
guildMember.nick = guildMember.nickname; // nick => nickname in Discord API
const memberObj = new discord.GuildMember(this.bot.discord, guildMember, this.guild);
this.guild.members.cache.set(userObj.id, memberObj);
this.bot.discord.users.cache.set(userObj.id, userObj);
const memberObj = new discord.GuildMember(this.guild, guildMember);
this.guild.members.set(userObj.id, memberObj);
this.discordUsers.set(userObj.id, userObj);
return memberObj;
};
this.addRole = function (role) {
const roleObj = new discord.Role(this.bot.discord, role, this.guild);
this.guild.roles.cache.set(roleObj.id, roleObj);
const roleObj = new discord.Role(this.bot.discord, role);
this.guild.roles.set(roleObj.id, roleObj);
return roleObj;
};
this.addEmoji = function (emoji) {
const emojiObj = new discord.GuildEmoji(this.bot.discord, emoji, this.guild);
this.guild.emojis.cache.set(emojiObj.id, emojiObj);
const emojiObj = new discord.Emoji(this.bot.discord, emoji);
this.guild.emojis.set(emojiObj.id, emojiObj);
return emojiObj;
};
});
@@ -101,21 +96,17 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(formatted);
});
it(
'should not send messages to discord if the channel isn\'t in the channel mapping',
it('should not send messages to discord if the channel isn\'t in the channel mapping',
function () {
this.bot.sendToDiscord('user', '#no-irc', 'message');
this.sendStub.should.not.have.been.called;
}
);
});
it(
'should not send messages to discord if it isn\'t in the channel',
it('should not send messages to discord if it isn\'t in the channel',
function () {
this.bot.sendToDiscord('user', '#otherirc', 'message');
this.sendStub.should.not.have.been.called;
}
);
});
it('should send to a discord channel ID appropriately', function () {
const username = 'testuser';
@@ -125,35 +116,30 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(formatted);
});
it(
'should not send special messages to discord if the channel isn\'t in the channel mapping',
it('should not send special messages to discord if the channel isn\'t in the channel mapping',
function () {
this.bot.sendExactToDiscord('#no-irc', 'message');
this.sendStub.should.not.have.been.called;
}
);
});
it(
'should not send special messages to discord if it isn\'t in the channel',
it('should not send special messages to discord if it isn\'t in the channel',
function () {
this.bot.sendExactToDiscord('#otherirc', 'message');
this.sendStub.should.not.have.been.called;
}
);
});
it(
'should send special messages to discord',
it('should send special messages to discord',
function () {
this.bot.sendExactToDiscord('#irc', 'message');
this.sendStub.should.have.been.calledWith('message');
this.debugSpy.should.have.been.calledWith('Sending special message to Discord', 'message', '#irc', '->', '#discord');
}
);
});
it('should not color irc messages if the option is disabled', function () {
const text = 'testmessage';
const newConfig = { ...config, ircNickColor: false };
this.setCustomBot(newConfig);
const bot = new Bot(newConfig);
bot.connect();
const message = {
content: text,
mentions: { users: [] },
@@ -167,33 +153,11 @@ describe('Bot', function () {
guild: this.guild
};
this.bot.sendToIRC(message);
bot.sendToIRC(message);
const expected = `<${message.author.username}> ${text}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});
it('should only use message color defined in config', function () {
const text = 'testmessage';
const newConfig = { ...config, ircNickColors: ['orange'] };
this.setCustomBot(newConfig);
const message = {
content: text,
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'otherauthor',
id: 'not bot id'
},
guild: this.guild
};
this.bot.sendToIRC(message);
const expected = `<\u000307${message.author.username}\u000f> ${text}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});
it('should send correct messages to irc', function () {
const text = 'testmessage';
const message = {
@@ -301,10 +265,8 @@ describe('Bot', function () {
this.bot.sendToIRC(message);
ClientStub.prototype.say.should.have.been.calledWith(
'#irc',
`<\u000304${message.author.username}\u000f> ${text}`
);
ClientStub.prototype.say.should.have.been.calledWith('#irc',
`<\u000304${message.author.username}\u000f> ${text}`);
const expected = `<\u000304${message.author.username}\u000f> ${attachmentUrl}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
@@ -343,8 +305,7 @@ describe('Bot', function () {
ClientStub.prototype.say.should.not.have.been.called;
});
it(
'should not send messages to irc if the channel isn\'t in the channel mapping',
it('should not send messages to irc if the channel isn\'t in the channel mapping',
function () {
const message = {
channel: {
@@ -359,34 +320,7 @@ describe('Bot', function () {
this.bot.sendToIRC(message);
ClientStub.prototype.say.should.not.have.been.called;
}
);
it('should break mentions when parallelPingFix is enabled', function () {
const newConfig = { ...config, parallelPingFix: true };
this.setCustomBot(newConfig);
const text = 'testmessage';
const username = 'otherauthor';
const brokenNickname = 'o\u200Btherauthor';
const message = {
content: text,
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username,
id: 'not bot id'
},
guild: this.guild
};
this.bot.sendToIRC(message);
// Wrap in colors:
const expected = `<\u000304${brokenNickname}\u000f> ${text}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});
});
it('should parse text from discord when sending messages', function () {
const text = '<#1234>';
@@ -472,16 +406,7 @@ describe('Bot', function () {
this.bot.parseText(message).should.equal(':SCGWat:');
});
it('should convert animated emoji from discord', function () {
const message = {
mentions: { users: [] },
content: '<a:in_love:432887860270465028>'
};
this.bot.parseText(message).should.equal(':in_love:');
});
it('should convert user at-mentions from IRC', function () {
it('should convert user mentions from IRC', function () {
const testUser = this.addUser({ username: 'testuser', id: '123' });
const username = 'ircuser';
@@ -492,40 +417,7 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert user colon-initial mentions from IRC', function () {
const testUser = this.addUser({ username: 'testuser', id: '123' });
const username = 'ircuser';
const text = 'testuser: hello!';
const expected = `**<${username}>** <@${testUser.id}> hello!`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert user comma-initial mentions from IRC', function () {
const testUser = this.addUser({ username: 'testuser', id: '123' });
const username = 'ircuser';
const text = 'testuser, hello!';
const expected = `**<${username}>** <@${testUser.id}> hello!`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should not convert user initial mentions from IRC mid-message', function () {
this.addUser({ username: 'testuser', id: '123' });
const username = 'ircuser';
const text = 'Hi there testuser, how goes?';
const expected = `**<${username}>** Hi there testuser, how goes?`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should not convert user at-mentions from IRC if such user does not exist', function () {
it('should not convert user mentions from IRC if such user does not exist', function () {
const username = 'ircuser';
const text = 'See you there @5pm';
const expected = `**<${username}>** See you there @5pm`;
@@ -534,15 +426,6 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(expected);
});
it('should not convert user initial mentions from IRC if such user does not exist', function () {
const username = 'ircuser';
const text = 'Agreed, see you then.';
const expected = `**<${username}>** Agreed, see you then.`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert multiple user mentions from IRC', function () {
const testUser = this.addUser({ username: 'testuser', id: '123' });
const anotherUser = this.addUser({ username: 'anotheruser', id: '124' });
@@ -566,19 +449,6 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert channel mentions from IRC', function () {
this.guild.addTextChannel({ id: '1235', name: 'testchannel' });
this.guild.addTextChannel({ id: '1236', name: 'channel-compliqué' });
const otherGuild = this.bot.discord.createGuildStub({ id: '2' });
otherGuild.addTextChannel({ id: '1237', name: 'foreignchannel' });
const username = 'ircuser';
const text = "Here is a broken #channelname, a working #testchannel, #channel-compliqué, an irregular case #TestChannel and another guild's #foreignchannel";
const expected = `**<${username}>** Here is a broken #channelname, a working <#1235>, <#1236>, an irregular case <#1235> and another guild's #foreignchannel`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert newlines from discord', function () {
const message = {
mentions: { users: [] },
@@ -610,29 +480,6 @@ describe('Bot', function () {
ClientStub.prototype.say.getCall(1).args.should.deep.equal(['#irc', text]);
});
it('should support multi-character command prefixes', function () {
this.setCustomBot({ ...config, commandCharacters: ['@@'] });
const text = '@@test command';
const message = {
content: text,
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'test',
id: 'not bot id'
},
guild: this.guild
};
this.bot.sendToIRC(message);
ClientStub.prototype.say.getCall(0).args.should.deep.equal([
'#irc', 'Command sent from Discord by test:'
]);
ClientStub.prototype.say.getCall(1).args.should.deep.equal(['#irc', text]);
});
it('should hide usernames for commands to Discord', function () {
const username = 'ircuser';
const text = '!command';
@@ -645,10 +492,11 @@ describe('Bot', function () {
it('should use nickname instead of username when available', function () {
const text = 'testmessage';
const newConfig = { ...config, ircNickColor: false };
this.setCustomBot(newConfig);
const bot = new Bot(newConfig);
const id = 'not bot id';
const nickname = 'discord-nickname';
this.guild.members.cache.set(id, { nickname });
this.guild.members.set(id, { nickname });
bot.connect();
const message = {
content: text,
mentions: { users: [] },
@@ -662,7 +510,7 @@ describe('Bot', function () {
guild: this.guild
};
this.bot.sendToIRC(message);
bot.sendToIRC(message);
const expected = `<${nickname}> ${text}`;
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
});
@@ -683,24 +531,7 @@ describe('Bot', function () {
const username = 'ircuser';
const text = 'Hello, @testuser!';
const expected = `**<${username}>** Hello, ${testUser}!`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert username-discriminator mentions from IRC properly', function () {
const user1 = this.addUser({ username: 'user', id: '123', discriminator: '9876' });
const user2 = this.addUser({
username: 'user',
id: '124',
discriminator: '5555',
nickname: 'secondUser'
});
const username = 'ircuser';
const text = 'hello @user#9876 and @user#5555 and @fakeuser#1234';
const expected = `**<${username}>** hello ${user1} and ${user2} and @fakeuser#1234`;
const expected = `**<${username}>** Hello, <@${testUser.id}>!`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
@@ -768,39 +599,11 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert overlapping mentions from IRC properly and case-insensitively', function () {
const user = this.addUser({ username: 'user', id: '111' });
const nickUser = this.addUser({ username: 'user2', id: '112', nickname: 'userTest' });
const nickUserCase = this.addUser({ username: 'user3', id: '113', nickname: 'userTEST' });
const role = this.addRole({ name: 'userTestRole', id: '12345', mentionable: true });
const username = 'ircuser';
const text = 'hello @User, @user, @userTest, @userTEST, @userTestRole and @usertestrole';
const expected = `**<${username}>** hello ${user}, ${user}, ${nickUser}, ${nickUserCase}, ${role} and ${role}`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should convert partial matches from IRC properly', function () {
const user = this.addUser({ username: 'user', id: '111' });
const longUser = this.addUser({ username: 'user-punc', id: '112' });
const nickUser = this.addUser({ username: 'user2', id: '113', nickname: 'nick' });
const nickUserCase = this.addUser({ username: 'user3', id: '114', nickname: 'NiCK' });
const role = this.addRole({ name: 'role', id: '12345', mentionable: true });
const username = 'ircuser';
const text = '@user-ific @usermore, @user\'s friend @user-punc, @nicks and @NiCKs @roles';
const expected = `**<${username}>** ${user}-ific ${user}more, ${user}'s friend ${longUser}, ${nickUser}s and ${nickUserCase}s ${role}s`;
this.bot.sendToDiscord(username, '#irc', text);
this.sendStub.should.have.been.calledWith(expected);
});
it('should successfully send messages with default config', function () {
this.setCustomBot(configMsgFormatDefault);
const bot = new Bot(configMsgFormatDefault);
bot.connect();
this.bot.sendToDiscord('testuser', '#irc', 'test message');
bot.sendToDiscord('testuser', '#irc', 'test message');
this.sendStub.should.have.been.calledOnce;
const message = {
content: 'test message',
@@ -815,34 +618,37 @@ describe('Bot', function () {
guild: this.guild
};
this.bot.sendToIRC(message);
bot.sendToIRC(message);
this.sendStub.should.have.been.calledOnce;
});
it('should not replace unmatched patterns', function () {
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
const bot = new Bot({ ...configMsgFormatDefault, format });
bot.connect();
const username = 'testuser';
const msg = 'test message';
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
this.bot.sendToDiscord(username, '#irc', msg);
bot.sendToDiscord(username, '#irc', msg);
this.sendStub.should.have.been.calledWith(expected);
});
it('should respect custom formatting for Discord', function () {
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
const bot = new Bot({ ...configMsgFormatDefault, format });
bot.connect();
const username = 'test';
const msg = 'test @user <#1234>';
const expected = `<test> #irc => #discord: ${msg}`;
this.bot.sendToDiscord(username, '#irc', msg);
bot.sendToDiscord(username, '#irc', msg);
this.sendStub.should.have.been.calledWith(expected);
});
it('should successfully send messages with default config', function () {
this.setCustomBot(configMsgFormatDefault);
this.bot = new Bot(configMsgFormatDefault);
this.bot.connect();
this.bot.sendToDiscord('testuser', '#irc', 'test message');
this.sendStub.should.have.been.calledOnce;
@@ -865,7 +671,8 @@ describe('Bot', function () {
it('should not replace unmatched patterns', function () {
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const username = 'testuser';
const msg = 'test message';
@@ -876,7 +683,8 @@ describe('Bot', function () {
it('should respect custom formatting for regular Discord output', function () {
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const username = 'test';
const msg = 'test @user <#1234>';
@@ -887,7 +695,8 @@ describe('Bot', function () {
it('should respect custom formatting for commands in Discord output', function () {
const format = { commandPrelude: '{$nickname} from {$ircChannel} sent command to {$discordChannel}:' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const username = 'test';
const msg = '!testcmd';
@@ -899,7 +708,8 @@ describe('Bot', function () {
it('should respect custom formatting for regular IRC output', function () {
const format = { ircText: '<{$nickname}> {$discordChannel} => {$ircChannel}: {$text}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const message = {
content: 'test message',
mentions: { users: [] },
@@ -920,7 +730,8 @@ describe('Bot', function () {
it('should respect custom formatting for commands in IRC output', function () {
const format = { commandPrelude: '{$nickname} from {$discordChannel} sent command to {$ircChannel}:' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const text = '!testcmd';
const message = {
@@ -944,7 +755,8 @@ describe('Bot', function () {
it('should respect custom formatting for attachment URLs in IRC output', function () {
const format = { urlAttachment: '<{$nickname}> {$discordChannel} => {$ircChannel}, attachment: {$attachmentURL}' };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const attachmentUrl = 'https://image/url.jpg';
const message = {
@@ -968,7 +780,8 @@ describe('Bot', function () {
it('should not bother with command prelude if falsy', function () {
const format = { commandPrelude: null };
this.setCustomBot({ ...configMsgFormatDefault, format });
this.bot = new Bot({ ...configMsgFormatDefault, format });
this.bot.connect();
const text = '!testcmd';
const message = {
@@ -994,227 +807,4 @@ describe('Bot', function () {
this.sendStub.should.have.been.calledOnce;
this.sendStub.getCall(0).args.should.deep.equal([msg]);
});
it('should create webhooks clients for each webhook url in the config', function () {
this.bot.webhooks.should.have.property('#withwebhook');
});
it('should extract id and token from webhook urls', function () {
this.bot.webhooks['#withwebhook'].id.should.equal('id');
});
it('should find the matching webhook when it exists', function () {
this.bot.findWebhook('#ircwebhook').should.not.equal(null);
});
context('with enabled Discord webhook', function () {
this.beforeEach(function () {
const newConfig = { ...config, webhooks: { '#discord': 'https://discord.com/api/webhooks/id/token' } };
this.setCustomBot(newConfig);
});
it('should prefer webhooks to send a message', function () {
this.bot.sendToDiscord('nick', '#irc', 'text');
this.sendWebhookMessageStub.should.have.been.called;
});
it('pads too short usernames', function () {
const text = 'message';
this.bot.sendToDiscord('n', '#irc', text);
this.sendWebhookMessageStub.should.have.been.calledWith(text, {
username: 'n_',
avatarURL: null,
disableMentions: 'everyone',
});
});
it('slices too long usernames', function () {
const text = 'message';
this.bot.sendToDiscord('1234567890123456789012345678901234567890', '#irc', text);
this.sendWebhookMessageStub.should.have.been.calledWith(text, {
username: '12345678901234567890123456789012',
avatarURL: null,
disableMentions: 'everyone',
});
});
it('does not ping everyone if user lacks permission', function () {
const text = 'message';
const permission = discord.Permissions.FLAGS.VIEW_CHANNEL
+ discord.Permissions.FLAGS.SEND_MESSAGES;
this.bot.discord.channels.cache.get('1234').setPermissionStub(
this.bot.discord.user,
new discord.Permissions(permission),
);
this.bot.sendToDiscord('nick', '#irc', text);
this.sendWebhookMessageStub.should.have.been.calledWith(text, {
username: 'nick',
avatarURL: null,
disableMentions: 'everyone',
});
});
it('sends @everyone messages if the bot has permission to do so', function () {
const text = 'message';
const permission = discord.Permissions.FLAGS.VIEW_CHANNEL
+ discord.Permissions.FLAGS.SEND_MESSAGES
+ discord.Permissions.FLAGS.MENTION_EVERYONE;
this.bot.discord.channels.cache.get('1234').setPermissionStub(
this.bot.discord.user,
new discord.Permissions(permission),
);
this.bot.sendToDiscord('nick', '#irc', text);
this.sendWebhookMessageStub.should.have.been.calledWith(text, {
username: 'nick',
avatarURL: null,
disableMentions: 'none',
});
});
const setupUser = base => {
const userObj = { id: 123, username: 'Nick', avatar: 'avatarURL' };
const memberObj = { nickname: 'Different' };
base.addUser(userObj, memberObj);
};
const setupCommonPair = base => {
const userObj1 = { id: 124, username: 'common', avatar: 'avatarURL' };
const userObj2 = { id: 125, username: 'diffUser', avatar: 'avatarURL' };
const memberObj1 = { nickname: 'diffNick' };
const memberObj2 = { nickname: 'common' };
base.addUser(userObj1, memberObj1);
base.addUser(userObj2, memberObj2);
};
context('when matching avatars', function () {
this.beforeEach(function () {
setupUser(this);
});
it('should match a user\'s username', function () {
this.bot.getDiscordAvatar('Nick', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
});
it('should match a user\'s username case insensitively', function () {
this.bot.getDiscordAvatar('nick', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
});
it('should match a user\'s nickname', function () {
this.bot.getDiscordAvatar('Different', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
});
it('should match a user\'s nickname case insensitively', function () {
this.bot.getDiscordAvatar('different', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
});
it('should only return matching users\' avatars', function () {
expect(this.bot.getDiscordAvatar('other', '#irc')).to.equal(null);
});
it('should return no avatar when there are multiple matches', function () {
setupCommonPair(this);
this.bot.getDiscordAvatar('diffUser', '#irc').should.not.equal(null);
this.bot.getDiscordAvatar('diffNick', '#irc').should.not.equal(null);
expect(this.bot.getDiscordAvatar('common', '#irc')).to.equal(null);
});
it('should handle users without nicknames', function () {
const userObj = { id: 124, username: 'nickless', avatar: 'nickless-avatar' };
const memberObj = {};
this.addUser(userObj, memberObj);
this.bot.getDiscordAvatar('nickless', '#irc').should.equal('/avatars/124/nickless-avatar.png?size=128');
});
it('should handle users without avatars', function () {
const userObj = { id: 124, username: 'avatarless' };
const memberObj = {};
this.addUser(userObj, memberObj);
expect(this.bot.getDiscordAvatar('avatarless', '#irc')).to.equal(null);
});
});
context('when matching avatars with fallback URL', function () {
this.beforeEach(function () {
const newConfig = { ...config, webhooks: { '#discord': 'https://discord.com/api/webhooks/id/token' }, format: { webhookAvatarURL: 'avatarFrom/{$nickname}' } };
this.setCustomBot(newConfig);
setupUser(this);
});
it('should use a matching user\'s avatar', function () {
this.bot.getDiscordAvatar('Nick', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
this.bot.getDiscordAvatar('nick', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
this.bot.getDiscordAvatar('Different', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
this.bot.getDiscordAvatar('different', '#irc').should.equal('/avatars/123/avatarURL.png?size=128');
});
it('should use fallback without matching user', function () {
this.bot.getDiscordAvatar('other', '#irc').should.equal('avatarFrom/other');
});
it('should use fallback when there are multiple matches', function () {
setupCommonPair(this);
this.bot.getDiscordAvatar('diffUser', '#irc').should.equal('/avatars/125/avatarURL.png?size=128');
this.bot.getDiscordAvatar('diffNick', '#irc').should.equal('/avatars/124/avatarURL.png?size=128');
this.bot.getDiscordAvatar('common', '#irc').should.equal('avatarFrom/common');
});
it('should use fallback for users without avatars', function () {
const userObj = { id: 124, username: 'avatarless' };
const memberObj = {};
this.addUser(userObj, memberObj);
this.bot.getDiscordAvatar('avatarless', '#irc').should.equal('avatarFrom/avatarless');
});
});
});
it(
'should not send messages to Discord if IRC user is ignored',
function () {
this.bot.sendToDiscord('irc_ignored_user', '#irc', 'message');
this.sendStub.should.not.have.been.called;
}
);
it(
'should not send messages to IRC if Discord user is ignored',
function () {
const message = {
content: 'text',
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'discord_ignored_user',
id: 'some id'
},
guild: this.guild
};
this.bot.sendToIRC(message);
ClientStub.prototype.say.should.not.have.been.called;
}
);
it(
'should not send messages to IRC if Discord user is ignored by id',
function () {
const message = {
content: 'text',
mentions: { users: [] },
channel: {
name: 'discord'
},
author: {
username: 'vasya_pupkin',
id: '4499'
},
guild: this.guild
};
this.bot.sendToIRC(message);
ClientStub.prototype.say.should.not.have.been.called;
}
);
});
+1 -1
View File
@@ -11,7 +11,7 @@ chai.should();
chai.use(sinonChai);
describe('CLI', function () {
const sandbox = sinon.createSandbox({
const sandbox = sinon.sandbox.create({
useFakeTimers: false,
useFakeServer: false
});
+1 -1
View File
@@ -14,7 +14,7 @@ chai.should();
chai.use(sinonChai);
describe('Create Bots', function () {
const sandbox = sinon.createSandbox({
const sandbox = sinon.sandbox.create({
useFakeTimers: false,
useFakeServer: false
});
+1 -4
View File
@@ -3,10 +3,7 @@
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"commandCharacters": ["!", "."],
"ircOptions": {
"encoding": "utf-8"
},
"channelMapping": {
"#discord": "#irc"
}
}
}
+1 -13
View File
@@ -3,9 +3,6 @@
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"commandCharacters": ["!", "."],
"ircOptions": {
"encoding": "utf-8"
},
"autoSendCommands": [
["MODE", "test", "+x"],
["AUTH", "test", "password"]
@@ -13,15 +10,6 @@
"channelMapping": {
"#discord": "#irc channelKey",
"#notinchannel": "#otherIRC",
"1234": "#channelforid",
"#withwebhook": "#ircwebhook"
},
"webhooks": {
"#withwebhook": "https://discord.com/api/webhooks/id/token"
},
"ignoreUsers": {
"irc": ["irc_ignored_user"],
"discord": ["discord_ignored_user"],
"discordIds": ["4499"]
"1234": "#channelforid"
}
}
-6
View File
@@ -3,9 +3,6 @@
"nickname": "test",
"server": "irc.freenode.net",
"discordToken": /* comment */ "whatapassword",
"ircOptions": {
"encoding": "utf-8"
},
"channelMapping": {
"#discord": "#irc" // Comment
}
@@ -15,9 +12,6 @@
"nickname": "test2",
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"ircOptions": {
"encoding": "utf-8"
},
"channelMapping": {
"#discord": "#irc"
}
-6
View File
@@ -3,9 +3,6 @@
"nickname": "test",
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"ircOptions": {
"encoding": "utf-8"
},
"channelMapping": {
"#discord": "#irc"
}
@@ -14,9 +11,6 @@
"nickname": "test2",
"server": "irc.freenode.net",
"discordToken": "whatapassword",
"ircOptions": {
"encoding": "utf-8"
},
"channelMapping": {
"#discord": "#irc"
}
-6
View File
@@ -3,9 +3,6 @@ module.exports = [
nickname: 'test',
server: 'irc.freenode.net',
discordToken: 'whatapassword',
ircOptions: {
encoding: 'utf-8'
},
channelMapping: {
'#discord': '#irc'
}
@@ -14,9 +11,6 @@ module.exports = [
nickname: 'test2',
server: 'irc.freenode.net',
discordToken: 'whatapassword',
ircOptions: {
encoding: 'utf-8'
},
channelMapping: {
'#discord': '#irc'
}
+3 -3
View File
@@ -12,8 +12,8 @@ describe('Formatting', () => {
});
it('should convert italic markdown', () => {
formatFromDiscordToIRC('*text*').should.equal('\x1dtext\x1d');
formatFromDiscordToIRC('_text_').should.equal('\x1dtext\x1d');
formatFromDiscordToIRC('*text*').should.equal('\x16text\x16');
formatFromDiscordToIRC('_text_').should.equal('\x16text\x16');
});
it('should convert underline markdown', () => {
@@ -26,7 +26,7 @@ describe('Formatting', () => {
it('should convert nested markdown', () => {
formatFromDiscordToIRC('**bold *italics***')
.should.equal('\x02bold \x1ditalics\x1d\x02');
.should.equal('\x02bold \x16italics\x16\x02');
});
});
+13 -60
View File
@@ -3,75 +3,28 @@ import events from 'events';
import sinon from 'sinon';
import discord from 'discord.js';
export default function createDiscordStub(sendStub) {
export default function createDiscordStub(sendStub, guild, discordUsers) {
return class DiscordStub extends events.EventEmitter {
constructor() {
super();
this.user = {
id: 'testid'
};
this.channels = new discord.ChannelManager(this, []);
this.options = {
http: {
cdn: ''
}
};
this.channels = this.guildChannels();
this.users = new discord.UserManager(this, []);
this.guilds = new discord.GuildManager(this, []);
const guild = this.createGuildStub();
this.guilds.cache.set(guild.id, guild);
this.rest = this.createRestStub();
this.users = discordUsers;
}
destroy() {}
addTextChannel(guild, textChannel) {
const textChannelData = {
type: discord.Constants.ChannelTypes.TEXT,
...textChannel
};
const textChannelObj = new discord.TextChannel(guild, textChannelData);
textChannelObj.send = sendStub;
const permissions = new discord.Collection();
textChannelObj.setPermissionStub = (user, perms) => permissions.set(user, perms);
textChannelObj.permissionsFor = user => permissions.get(user);
this.channels.cache.set(textChannelObj.id, textChannelObj);
return textChannelObj;
}
createGuildStub(guildData = {}) {
const guild = {
id: '1',
client: this,
addTextChannel: (textChannel) => {
const textChannelObj = this.addTextChannel(guild, textChannel);
textChannelObj.guild.channels.cache.set(textChannelObj.id, textChannelObj);
return textChannelObj;
}
};
guild.roles = new discord.RoleManager(guild, []);
guild.members = new discord.GuildMemberManager(guild, []);
guild.emojis = new discord.GuildEmojiManager(guild, []);
guild.channels = new discord.GuildChannelManager(guild, []);
Object.assign(guild, guildData);
this.guilds.cache.set(guild.id, guild);
if (guild.id === '1') {
guild.addTextChannel({
name: 'discord',
id: '1234',
});
}
return guild;
}
createRestStub() {
return {
cdn: discord.Constants.Endpoints.CDN(''),
};
guildChannels() {
const channels = new discord.Collection();
channels.set('1234', {
name: 'discord',
id: '1234',
type: 'text',
send: sendStub,
guild
});
return channels;
}
login() {
+1 -4
View File
@@ -1,13 +1,10 @@
import events from 'events';
/* eslint-disable class-methods-use-this */
class ClientStub extends events.EventEmitter {
constructor(...args) {
super();
this.nick = args[1]; // eslint-disable-line prefer-destructuring
this.nick = args[1];
}
disconnect() {}
}
export default ClientStub;
-16
View File
@@ -1,16 +0,0 @@
/* eslint-disable class-methods-use-this */
export default function createWebhookStub(sendWebhookMessage) {
return class WebhookStub {
constructor(id, token) {
this.id = id;
this.token = token;
}
send(...args) {
sendWebhookMessage(...args);
return new Promise(() => {});
}
destroy() {}
};
}