Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7330aa7b1 | ||
|
|
ce71a7c7e5 | ||
|
|
a6a9531107 | ||
|
|
7ce65dc804 | ||
|
|
b2e7598554 | ||
|
|
b467e7d915 | ||
|
|
e21b174fe1 | ||
|
|
f2ea354717 | ||
|
|
fd27afb8f1 | ||
|
|
feac4c792a | ||
|
|
4d31ecd4eb | ||
|
|
fa83071b5c | ||
|
|
4f449954e0 | ||
|
|
d646ba149f | ||
|
|
6f89730ea8 | ||
|
|
e0f2edf2d4 | ||
|
|
3908e00d16 | ||
|
|
328fe0b44e | ||
|
|
5b01e7639f | ||
|
|
56d1c84147 | ||
|
|
5af02c16c6 | ||
|
|
f047ce0224 | ||
|
|
ee2d70fac1 | ||
|
|
91ad0b12d2 | ||
|
|
8a1378f864 | ||
|
|
8084ed42a7 | ||
|
|
6df5da619c | ||
|
|
7789bd6a24 | ||
|
|
1b7d3378aa | ||
|
|
8c97037930 | ||
|
|
8a02361758 | ||
|
|
41f8444d17 | ||
|
|
eaf8b3d470 | ||
|
|
b3ebca5dcb | ||
|
|
9585a70eb8 | ||
|
|
4fe14837db | ||
|
|
88fecf3aa1 | ||
|
|
03d6ac2170 | ||
|
|
c5ad1a6a6a | ||
|
|
33d12d7b38 | ||
|
|
97b0bf8669 | ||
|
|
21985c6d97 | ||
|
|
11a5347006 | ||
|
|
5d31e2d16b | ||
|
|
681cd40056 | ||
|
|
8a6dec41c6 | ||
|
|
4e807ca34d | ||
|
|
221d42a44b | ||
|
|
c28151a30c | ||
|
|
d528aedb6d | ||
|
|
70e88498de | ||
|
|
4f10b60c89 | ||
|
|
a719185cc9 | ||
|
|
c9ccd7b473 | ||
|
|
4196726799 | ||
|
|
fc9f62a7e7 | ||
|
|
3b362d8698 | ||
|
|
2b4bcb1136 | ||
|
|
28fc86826b | ||
|
|
8cf95fff4f | ||
|
|
45a898ae67 | ||
|
|
492629816e | ||
|
|
98c6286416 | ||
|
|
977b337a53 | ||
|
|
aac3fa677f | ||
|
|
5c18507ffd | ||
|
|
9387bca2ce | ||
|
|
ef553d37e2 | ||
|
|
3e6aa324f2 | ||
|
|
df3bf91465 | ||
|
|
68678fb134 | ||
|
|
87608f3e99 | ||
|
|
5ea6abbe86 | ||
|
|
15f2382639 | ||
|
|
32b12b3096 | ||
|
|
0e21e679fd | ||
|
|
38e7f1fe3a | ||
|
|
d07ac66e39 | ||
|
|
5e3a8e201b | ||
|
|
ba505edd32 | ||
|
|
d7d3bd6f67 | ||
|
|
05e7ccd1eb | ||
|
|
cb4979b0f8 | ||
|
|
d10606106f | ||
|
|
308766163a | ||
|
|
ebee361e2d | ||
|
|
4690e69bd3 | ||
|
|
4f3c4365c6 | ||
|
|
1a8685d26d | ||
|
|
f8ad2d2fa7 | ||
|
|
d7c99e49af | ||
|
|
9a98f6da95 | ||
|
|
877212dd0c | ||
|
|
1fd15b9f94 | ||
|
|
21eda4ef53 | ||
|
|
b8c069ed12 | ||
|
|
fe1aea55b1 | ||
|
|
dec2ea9379 | ||
|
|
9383b1bcf4 | ||
|
|
c5e7b13866 | ||
|
|
02264093f8 | ||
|
|
c977943ff8 | ||
|
|
c75031c67f | ||
|
|
2dcd6c97fc | ||
|
|
3d57250883 | ||
|
|
1f9f6bda6f | ||
|
|
b252f56658 | ||
|
|
6a6a1ed547 | ||
|
|
1c6146d603 | ||
|
|
20e76872ad | ||
|
|
cb7dc2b75d | ||
|
|
f624134814 | ||
|
|
22e3bece3c | ||
|
|
883c3ecd83 | ||
|
|
a1e645626f | ||
|
|
f0afa90170 | ||
|
|
d2a6dad79a | ||
|
|
91a191ec37 | ||
|
|
0d1dfd22f6 | ||
|
|
c7263b72dd | ||
|
|
b4f6e50728 | ||
|
|
85bce0caf6 | ||
|
|
08e1b3dd68 | ||
|
|
f660afda3b | ||
|
|
b80d9f650d | ||
|
|
2da61b3e53 | ||
|
|
90c901a034 | ||
|
|
d2fe4200ed | ||
|
|
0a13110c68 | ||
|
|
aa00cfbce0 | ||
|
|
68d0fd8f75 | ||
|
|
8b62058541 | ||
|
|
9ab541413f | ||
|
|
7de32caa7a | ||
|
|
93f1fc15d8 | ||
|
|
6e45493055 | ||
|
|
2e1fed8311 | ||
|
|
88c50636b9 | ||
|
|
903eca635f | ||
|
|
4b2ffb2c2c | ||
|
|
a45e07c760 | ||
|
|
cff6eee324 | ||
|
|
0992dc2da1 | ||
|
|
188553b670 | ||
|
|
569893adb6 | ||
|
|
751b750776 | ||
|
|
05573eedf4 | ||
|
|
3d2eeb2798 | ||
|
|
db39ba237e | ||
|
|
6eca392e00 | ||
|
|
b86fd1eaa4 | ||
|
|
c3cac2b7ac | ||
|
|
ab59a1ec66 | ||
|
|
dc27e8e137 | ||
|
|
0b266834f0 | ||
|
|
87a3458bdd | ||
|
|
1f49f74c85 | ||
|
|
aa3d6fc665 | ||
|
|
3bd32206b4 | ||
|
|
94d90960c6 | ||
|
|
be281c5add | ||
|
|
0adcf50b02 | ||
|
|
a12a186f8c | ||
|
|
bc382d8d5c | ||
|
|
78c4935614 | ||
|
|
7fbf1d0ba0 | ||
|
|
1c0cc3fba4 | ||
|
|
1cf6d644c8 | ||
|
|
2fba3ee008 | ||
|
|
59b9e071d5 | ||
|
|
ddaa2e6325 | ||
|
|
69a8b8d9ae | ||
|
|
fdd7b1d200 | ||
|
|
8bf36dd58d | ||
|
|
c0a4133d03 | ||
|
|
f8f7fea3b6 | ||
|
|
55b9d48c52 | ||
|
|
eecb782dc5 | ||
|
|
345da09fcf | ||
|
|
65bb6fa85e | ||
|
|
631b0485aa | ||
|
|
549b8fb767 | ||
|
|
ff788083d1 | ||
|
|
e8c96e2a44 | ||
|
|
8a23a91931 | ||
|
|
4facb56ebb | ||
|
|
07662761b4 | ||
|
|
34cdd373af | ||
|
|
d62dcfb9e6 | ||
|
|
8c0c11a2b1 | ||
|
|
2bf761b163 | ||
|
|
fa9ef71372 | ||
|
|
b4452dd487 | ||
|
|
2c05038c37 | ||
|
|
9ea90a9d96 | ||
|
|
3ca43ae0f4 | ||
|
|
2f59cbe2ef |
@@ -1,3 +1,19 @@
|
||||
{
|
||||
"presets": ["node6", "stage-0"]
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "12"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": [
|
||||
"istanbul"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
.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/
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"extends": "airbnb-base",
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"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
|
||||
"import/no-dynamic-require": 0,
|
||||
"operator-linebreak": 0,
|
||||
"implicit-arrow-linebreak": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
language: node_js
|
||||
after_success:
|
||||
- npm run report
|
||||
node_js:
|
||||
- '6'
|
||||
- '7'
|
||||
+129
-1
@@ -1,8 +1,136 @@
|
||||
# Changelog
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.4.1] - 2017-07-16
|
||||
## [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:
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
||||
* 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
@@ -0,0 +1,15 @@
|
||||
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"]
|
||||
@@ -0,0 +1,2 @@
|
||||
build:
|
||||
docker-compose -f docker-compose.yml up -d --build --remove-orphans
|
||||
@@ -1,12 +1,13 @@
|
||||
# discord-irc [](https://travis-ci.org/reactiflux/discord-irc) [](https://coveralls.io/github/reactiflux/discord-irc?branch=master)
|
||||
[](https://coveralls.io/github/reactiflux/discord-irc?branch=main)
|
||||
|
||||
> Connects [Discord](https://discordapp.com/) and IRC channels by sending messages back and forth.
|
||||
> Connects [Discord](https://discord.com/) and [IRC](https://www.ietf.org/rfc/rfc1459.txt) channels by sending messages back and forth.
|
||||
|
||||
## Example
|
||||

|
||||
|
||||
## Installation and usage
|
||||
**Note**: discord-irc requires Node.js version 6 or newer, as it depends on [discord.js](https://github.com/hydrabolt/discord.js).
|
||||
**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.
|
||||
|
||||
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).
|
||||
@@ -37,6 +38,24 @@ 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).
|
||||
|
||||
@@ -69,23 +88,40 @@ 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
|
||||
"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
|
||||
},
|
||||
"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
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -94,6 +130,29 @@ 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:
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
discord-irc:
|
||||
image: discord-irc
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
volumes:
|
||||
- ./config:/config/config.json
|
||||
+255
-42
@@ -1,13 +1,17 @@
|
||||
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 NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
|
||||
const DEFAULT_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;
|
||||
|
||||
@@ -25,7 +29,10 @@ class Bot {
|
||||
|
||||
validateChannelMapping(options.channelMapping);
|
||||
|
||||
this.discord = new discord.Client({ autoReconnect: true });
|
||||
this.discord = new discord.Client({
|
||||
autoReconnect: true,
|
||||
retryLimit: 3,
|
||||
});
|
||||
|
||||
this.server = options.server;
|
||||
this.nickname = options.nickname;
|
||||
@@ -33,9 +40,18 @@ 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
|
||||
@@ -49,6 +65,7 @@ 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) {
|
||||
@@ -61,10 +78,15 @@ 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) => {
|
||||
@@ -79,6 +101,16 @@ 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,
|
||||
@@ -86,13 +118,31 @@ 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');
|
||||
@@ -217,9 +267,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.cache.get(user.id);
|
||||
if (userDetails) {
|
||||
return userDetails.nickname || user.username;
|
||||
}
|
||||
}
|
||||
return user.username;
|
||||
}
|
||||
@@ -227,28 +279,39 @@ class Bot {
|
||||
parseText(message) {
|
||||
const text = message.mentions.users.reduce((content, mention) => {
|
||||
const displayName = Bot.getDiscordNicknameOnServer(mention, message.guild);
|
||||
return content.replace(`<@${mention.id}>`, `@${displayName}`)
|
||||
.replace(`<@!${mention.id}>`, `@${displayName}`)
|
||||
.replace(`<@&${mention.id}>`, `@${displayName}`);
|
||||
const userMentionRegex = RegExp(`<@(&|!)?${mention.id}>`, 'g');
|
||||
return content.replace(userMentionRegex, `@${displayName}`);
|
||||
}, message.content);
|
||||
|
||||
return text
|
||||
.replace(/\n|\r\n|\r/g, ' ')
|
||||
.replace(/<#(\d+)>/g, (match, channelId) => {
|
||||
const channel = this.discord.channels.get(channelId);
|
||||
const channel = this.discord.channels.cache.get(channelId);
|
||||
if (channel) return `#${channel.name}`;
|
||||
return '#deleted-channel';
|
||||
})
|
||||
.replace(/<@&(\d+)>/g, (match, roleId) => {
|
||||
const role = message.guild.roles.get(roleId);
|
||||
const role = message.guild.roles.cache.get(roleId);
|
||||
if (role) return `@${role.name}`;
|
||||
return '@deleted-role';
|
||||
})
|
||||
.replace(/<(:\w+:)\d+>/g, (match, emoteName) => emoteName);
|
||||
.replace(/<a?(:\w+:)\d+>/g, (match, emoteName) => emoteName);
|
||||
}
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
static substitutePattern(message, patternMapping) {
|
||||
@@ -256,9 +319,16 @@ class Bot {
|
||||
}
|
||||
|
||||
sendToIRC(message) {
|
||||
const author = message.author;
|
||||
const { author } = message;
|
||||
// Ignore messages sent by the bot itself:
|
||||
if (author.id === this.discord.user.id) return;
|
||||
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;
|
||||
}
|
||||
|
||||
const channelName = `#${message.channel.name}`;
|
||||
const ircChannel = this.channelMapping[message.channel.id] ||
|
||||
@@ -270,9 +340,16 @@ 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) % NICK_COLORS.length;
|
||||
displayUsername = irc.colors.wrap(NICK_COLORS[colorIndex], nickname);
|
||||
const colorIndex = (nickname.charCodeAt(0) + nickname.length) % this.ircNickColors.length;
|
||||
displayUsername = irc.colors.wrap(this.ircNickColors[colorIndex], displayUsername);
|
||||
}
|
||||
|
||||
const patternMap = {
|
||||
@@ -321,13 +398,21 @@ class Bot {
|
||||
const discordChannelName = this.invertedMapping[ircChannel.toLowerCase()];
|
||||
if (discordChannelName) {
|
||||
// #channel -> channel before retrieving and select only text channels:
|
||||
const discordChannel = discordChannelName.startsWith('#') ? this.discord.channels
|
||||
.filter(c => c.type === 'text')
|
||||
.find('name', discordChannelName.slice(1)) : this.discord.channels.get(discordChannelName);
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -335,16 +420,72 @@ 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
|
||||
@@ -361,35 +502,107 @@ 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.cache.find(x =>
|
||||
Bot.caseComp(x.user.username, username)
|
||||
&& x.user.discriminator === discriminator);
|
||||
if (user) return user;
|
||||
|
||||
const user = this.discord.users.find('username', search);
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
return match;
|
||||
}).replace(/^([^@\s:,]+)[:,]|@([^\s]+)/g, (match, startRef, atRef) => {
|
||||
const reference = startRef || atRef;
|
||||
|
||||
const role = guild.roles.find('name', search);
|
||||
if (role && role.mentionable) {
|
||||
return role;
|
||||
}
|
||||
// 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);
|
||||
|
||||
return match;
|
||||
}).replace(/:(\w+):/g, (match, ident) => {
|
||||
const guild = discordChannel.guild;
|
||||
const emoji = guild.emojis.find(x => x.name === ident && x.requiresColons);
|
||||
if (emoji) {
|
||||
return `<:${emoji.identifier}>`; // identifier = name + ":" + id
|
||||
}
|
||||
// :emoji: => mention, case sensitively
|
||||
const emoji = guild.emojis.cache.find(x => x.name === ident && x.requiresColons);
|
||||
if (emoji) return emoji;
|
||||
|
||||
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:
|
||||
|
||||
+6
-4
@@ -3,7 +3,6 @@
|
||||
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';
|
||||
@@ -26,14 +25,17 @@ 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 (!program.config) checkEnv(['CONFIG_FILE']);
|
||||
else process.env.CONFIG_FILE = program.config;
|
||||
if (opts.config) process.env.CONFIG_FILE = opts.config;
|
||||
if (!process.env.CONFIG_FILE) throw new Error('Missing environment variable CONFIG_FILE');
|
||||
|
||||
const completePath = path.resolve(process.cwd(), process.env.CONFIG_FILE);
|
||||
const config = endsWith(process.env.CONFIG_FILE, '.js') ?
|
||||
|
||||
+11
-5
@@ -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) {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#!/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();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
Generated
+5992
-3016
File diff suppressed because it is too large
Load Diff
+31
-29
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord-irc",
|
||||
"version": "2.4.1",
|
||||
"version": "2.9.0",
|
||||
"description": "Connects IRC and Discord channels by sending messages back and forth.",
|
||||
"keywords": [
|
||||
"discord",
|
||||
@@ -11,7 +11,7 @@
|
||||
"reactiflux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"node": ">=12.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",
|
||||
"prepublish": "npm run build",
|
||||
"prepare": "npm run build",
|
||||
"lint": "eslint . --ignore-path .gitignore",
|
||||
"coverage": "nyc --require babel-core/register _mocha -- test/*.test.js",
|
||||
"report": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage": "cross-env NODE_ENV=test nyc --require @babel/register mocha -- 'test/*.test.js'",
|
||||
"save-coverage": "nyc report --reporter=lcov",
|
||||
"test": "npm run lint && npm run coverage"
|
||||
},
|
||||
"author": {
|
||||
@@ -36,31 +36,33 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ 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';
|
||||
|
||||
@@ -14,7 +15,7 @@ chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('Bot Events', function () {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
@@ -34,9 +35,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, this.getUserStub);
|
||||
discord.Client = createDiscordStub(this.sendStub);
|
||||
discord.WebhookClient = createWebhookStub(this.sendStub);
|
||||
ClientStub.prototype.send = sandbox.stub();
|
||||
ClientStub.prototype.join = sandbox.stub();
|
||||
this.bot = createBot();
|
||||
@@ -44,6 +45,7 @@ describe('Bot Events', function () {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.bot.disconnect();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
@@ -158,7 +160,9 @@ 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 });
|
||||
|
||||
+509
-99
@@ -3,65 +3,70 @@ 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.sandbox.create({
|
||||
const sandbox = sinon.createSandbox({
|
||||
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;
|
||||
this.guild = createGuildStub();
|
||||
discord.Client = createDiscordStub(this.sendStub, this.guild, this.discordUsers);
|
||||
discord.Client = createDiscordStub(this.sendStub);
|
||||
|
||||
ClientStub.prototype.say = sandbox.stub();
|
||||
ClientStub.prototype.send = sandbox.stub();
|
||||
ClientStub.prototype.join = sandbox.stub();
|
||||
this.bot = new Bot(config);
|
||||
this.bot.connect();
|
||||
this.sendWebhookMessageStub = sandbox.stub();
|
||||
discord.WebhookClient = createWebhookStub(this.sendWebhookMessageStub);
|
||||
|
||||
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);
|
||||
const guildMember = Object.assign({}, member || user, { user: userObj });
|
||||
// also set guild members
|
||||
const guildMember = { ...(member || user), user: userObj };
|
||||
guildMember.nick = guildMember.nickname; // nick => nickname in Discord API
|
||||
const memberObj = new discord.GuildMember(this.guild, guildMember);
|
||||
this.guild.members.set(userObj.id, memberObj);
|
||||
this.discordUsers.set(userObj.id, userObj);
|
||||
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);
|
||||
return memberObj;
|
||||
};
|
||||
|
||||
this.addRole = function (role) {
|
||||
const roleObj = new discord.Role(this.bot.discord, role);
|
||||
this.guild.roles.set(roleObj.id, roleObj);
|
||||
const roleObj = new discord.Role(this.bot.discord, role, this.guild);
|
||||
this.guild.roles.cache.set(roleObj.id, roleObj);
|
||||
return roleObj;
|
||||
};
|
||||
|
||||
this.addEmoji = function (emoji) {
|
||||
const emojiObj = new discord.Emoji(this.bot.discord, emoji);
|
||||
this.guild.emojis.set(emojiObj.id, emojiObj);
|
||||
const emojiObj = new discord.GuildEmoji(this.bot.discord, emoji, this.guild);
|
||||
this.guild.emojis.cache.set(emojiObj.id, emojiObj);
|
||||
return emojiObj;
|
||||
};
|
||||
});
|
||||
@@ -96,17 +101,21 @@ 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';
|
||||
@@ -116,30 +125,35 @@ 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 };
|
||||
const bot = new Bot(newConfig);
|
||||
bot.connect();
|
||||
this.setCustomBot(newConfig);
|
||||
const message = {
|
||||
content: text,
|
||||
mentions: { users: [] },
|
||||
@@ -153,11 +167,33 @@ describe('Bot', function () {
|
||||
guild: this.guild
|
||||
};
|
||||
|
||||
bot.sendToIRC(message);
|
||||
this.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 = {
|
||||
@@ -265,8 +301,10 @@ 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);
|
||||
@@ -305,7 +343,8 @@ 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: {
|
||||
@@ -320,7 +359,34 @@ 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>';
|
||||
@@ -406,7 +472,16 @@ describe('Bot', function () {
|
||||
this.bot.parseText(message).should.equal(':SCGWat:');
|
||||
});
|
||||
|
||||
it('should convert user mentions from IRC', function () {
|
||||
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 () {
|
||||
const testUser = this.addUser({ username: 'testuser', id: '123' });
|
||||
|
||||
const username = 'ircuser';
|
||||
@@ -417,7 +492,40 @@ describe('Bot', function () {
|
||||
this.sendStub.should.have.been.calledWith(expected);
|
||||
});
|
||||
|
||||
it('should not convert user mentions from IRC if such user does not exist', function () {
|
||||
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 () {
|
||||
const username = 'ircuser';
|
||||
const text = 'See you there @5pm';
|
||||
const expected = `**<${username}>** See you there @5pm`;
|
||||
@@ -426,6 +534,15 @@ 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' });
|
||||
@@ -449,6 +566,19 @@ 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: [] },
|
||||
@@ -480,6 +610,29 @@ 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';
|
||||
@@ -492,11 +645,10 @@ describe('Bot', function () {
|
||||
it('should use nickname instead of username when available', function () {
|
||||
const text = 'testmessage';
|
||||
const newConfig = { ...config, ircNickColor: false };
|
||||
const bot = new Bot(newConfig);
|
||||
this.setCustomBot(newConfig);
|
||||
const id = 'not bot id';
|
||||
const nickname = 'discord-nickname';
|
||||
this.guild.members.set(id, { nickname });
|
||||
bot.connect();
|
||||
this.guild.members.cache.set(id, { nickname });
|
||||
const message = {
|
||||
content: text,
|
||||
mentions: { users: [] },
|
||||
@@ -510,7 +662,7 @@ describe('Bot', function () {
|
||||
guild: this.guild
|
||||
};
|
||||
|
||||
bot.sendToIRC(message);
|
||||
this.bot.sendToIRC(message);
|
||||
const expected = `<${nickname}> ${text}`;
|
||||
ClientStub.prototype.say.should.have.been.calledWith('#irc', expected);
|
||||
});
|
||||
@@ -531,7 +683,24 @@ describe('Bot', function () {
|
||||
|
||||
const username = 'ircuser';
|
||||
const text = 'Hello, @testuser!';
|
||||
const expected = `**<${username}>** Hello, <@${testUser.id}>!`;
|
||||
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`;
|
||||
|
||||
this.bot.sendToDiscord(username, '#irc', text);
|
||||
this.sendStub.should.have.been.calledWith(expected);
|
||||
@@ -599,56 +768,37 @@ describe('Bot', function () {
|
||||
this.sendStub.should.have.been.calledWith(expected);
|
||||
});
|
||||
|
||||
it('should successfully send messages with default config', function () {
|
||||
const bot = new Bot(configMsgFormatDefault);
|
||||
bot.connect();
|
||||
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 });
|
||||
|
||||
bot.sendToDiscord('testuser', '#irc', 'test message');
|
||||
this.sendStub.should.have.been.calledOnce;
|
||||
const message = {
|
||||
content: 'test message',
|
||||
mentions: { users: [] },
|
||||
channel: {
|
||||
name: 'discord'
|
||||
},
|
||||
author: {
|
||||
username: 'otherauthor',
|
||||
id: 'not bot id'
|
||||
},
|
||||
guild: this.guild
|
||||
};
|
||||
const username = 'ircuser';
|
||||
const text = 'hello @User, @user, @userTest, @userTEST, @userTestRole and @usertestrole';
|
||||
const expected = `**<${username}>** hello ${user}, ${user}, ${nickUser}, ${nickUserCase}, ${role} and ${role}`;
|
||||
|
||||
bot.sendToIRC(message);
|
||||
this.sendStub.should.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not replace unmatched patterns', function () {
|
||||
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
|
||||
const bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
bot.connect();
|
||||
|
||||
const username = 'testuser';
|
||||
const msg = 'test message';
|
||||
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
|
||||
bot.sendToDiscord(username, '#irc', msg);
|
||||
this.bot.sendToDiscord(username, '#irc', text);
|
||||
this.sendStub.should.have.been.calledWith(expected);
|
||||
});
|
||||
|
||||
it('should respect custom formatting for Discord', function () {
|
||||
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
|
||||
const bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
bot.connect();
|
||||
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 = 'test';
|
||||
const msg = 'test @user <#1234>';
|
||||
const expected = `<test> #irc => #discord: ${msg}`;
|
||||
bot.sendToDiscord(username, '#irc', msg);
|
||||
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.bot = new Bot(configMsgFormatDefault);
|
||||
this.bot.connect();
|
||||
this.setCustomBot(configMsgFormatDefault);
|
||||
|
||||
this.bot.sendToDiscord('testuser', '#irc', 'test message');
|
||||
this.sendStub.should.have.been.calledOnce;
|
||||
@@ -671,8 +821,51 @@ describe('Bot', function () {
|
||||
|
||||
it('should not replace unmatched patterns', function () {
|
||||
const format = { discord: '{$unmatchedPattern} stays intact: {$author} {$text}' };
|
||||
this.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const username = 'testuser';
|
||||
const msg = 'test message';
|
||||
const expected = `{$unmatchedPattern} stays intact: ${username} ${msg}`;
|
||||
this.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 username = 'test';
|
||||
const msg = 'test @user <#1234>';
|
||||
const expected = `<test> #irc => #discord: ${msg}`;
|
||||
this.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.sendToDiscord('testuser', '#irc', 'test message');
|
||||
this.sendStub.should.have.been.calledOnce;
|
||||
const message = {
|
||||
content: 'test message',
|
||||
mentions: { users: [] },
|
||||
channel: {
|
||||
name: 'discord'
|
||||
},
|
||||
author: {
|
||||
username: 'otherauthor',
|
||||
id: 'not bot id'
|
||||
},
|
||||
guild: this.guild
|
||||
};
|
||||
|
||||
this.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 username = 'testuser';
|
||||
const msg = 'test message';
|
||||
@@ -683,8 +876,7 @@ describe('Bot', function () {
|
||||
|
||||
it('should respect custom formatting for regular Discord output', function () {
|
||||
const format = { discord: '<{$author}> {$ircChannel} => {$discordChannel}: {$text}' };
|
||||
this.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const username = 'test';
|
||||
const msg = 'test @user <#1234>';
|
||||
@@ -695,8 +887,7 @@ 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.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const username = 'test';
|
||||
const msg = '!testcmd';
|
||||
@@ -708,8 +899,7 @@ describe('Bot', function () {
|
||||
|
||||
it('should respect custom formatting for regular IRC output', function () {
|
||||
const format = { ircText: '<{$nickname}> {$discordChannel} => {$ircChannel}: {$text}' };
|
||||
this.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
const message = {
|
||||
content: 'test message',
|
||||
mentions: { users: [] },
|
||||
@@ -730,8 +920,7 @@ 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.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const text = '!testcmd';
|
||||
const message = {
|
||||
@@ -755,8 +944,7 @@ describe('Bot', function () {
|
||||
|
||||
it('should respect custom formatting for attachment URLs in IRC output', function () {
|
||||
const format = { urlAttachment: '<{$nickname}> {$discordChannel} => {$ircChannel}, attachment: {$attachmentURL}' };
|
||||
this.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const attachmentUrl = 'https://image/url.jpg';
|
||||
const message = {
|
||||
@@ -780,8 +968,7 @@ describe('Bot', function () {
|
||||
|
||||
it('should not bother with command prelude if falsy', function () {
|
||||
const format = { commandPrelude: null };
|
||||
this.bot = new Bot({ ...configMsgFormatDefault, format });
|
||||
this.bot.connect();
|
||||
this.setCustomBot({ ...configMsgFormatDefault, format });
|
||||
|
||||
const text = '!testcmd';
|
||||
const message = {
|
||||
@@ -807,4 +994,227 @@ 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
@@ -11,7 +11,7 @@ chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('CLI', function () {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('Create Bots', function () {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"commandCharacters": ["!", "."],
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -3,6 +3,9 @@
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"commandCharacters": ["!", "."],
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"autoSendCommands": [
|
||||
["MODE", "test", "+x"],
|
||||
["AUTH", "test", "password"]
|
||||
@@ -10,6 +13,15 @@
|
||||
"channelMapping": {
|
||||
"#discord": "#irc channelKey",
|
||||
"#notinchannel": "#otherIRC",
|
||||
"1234": "#channelforid"
|
||||
"1234": "#channelforid",
|
||||
"#withwebhook": "#ircwebhook"
|
||||
},
|
||||
"webhooks": {
|
||||
"#withwebhook": "https://discord.com/api/webhooks/id/token"
|
||||
},
|
||||
"ignoreUsers": {
|
||||
"irc": ["irc_ignored_user"],
|
||||
"discord": ["discord_ignored_user"],
|
||||
"discordIds": ["4499"]
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -3,6 +3,9 @@
|
||||
"nickname": "test",
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": /* comment */ "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc" // Comment
|
||||
}
|
||||
@@ -12,6 +15,9 @@
|
||||
"nickname": "test2",
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -3,6 +3,9 @@
|
||||
"nickname": "test",
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
@@ -11,6 +14,9 @@
|
||||
"nickname": "test2",
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
|
||||
+6
@@ -3,6 +3,9 @@ module.exports = [
|
||||
nickname: 'test',
|
||||
server: 'irc.freenode.net',
|
||||
discordToken: 'whatapassword',
|
||||
ircOptions: {
|
||||
encoding: 'utf-8'
|
||||
},
|
||||
channelMapping: {
|
||||
'#discord': '#irc'
|
||||
}
|
||||
@@ -11,6 +14,9 @@ module.exports = [
|
||||
nickname: 'test2',
|
||||
server: 'irc.freenode.net',
|
||||
discordToken: 'whatapassword',
|
||||
ircOptions: {
|
||||
encoding: 'utf-8'
|
||||
},
|
||||
channelMapping: {
|
||||
'#discord': '#irc'
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ describe('Formatting', () => {
|
||||
});
|
||||
|
||||
it('should convert italic markdown', () => {
|
||||
formatFromDiscordToIRC('*text*').should.equal('\x16text\x16');
|
||||
formatFromDiscordToIRC('_text_').should.equal('\x16text\x16');
|
||||
formatFromDiscordToIRC('*text*').should.equal('\x1dtext\x1d');
|
||||
formatFromDiscordToIRC('_text_').should.equal('\x1dtext\x1d');
|
||||
});
|
||||
|
||||
it('should convert underline markdown', () => {
|
||||
@@ -26,7 +26,7 @@ describe('Formatting', () => {
|
||||
|
||||
it('should convert nested markdown', () => {
|
||||
formatFromDiscordToIRC('**bold *italics***')
|
||||
.should.equal('\x02bold \x16italics\x16\x02');
|
||||
.should.equal('\x02bold \x1ditalics\x1d\x02');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+60
-13
@@ -3,28 +3,75 @@ import events from 'events';
|
||||
import sinon from 'sinon';
|
||||
import discord from 'discord.js';
|
||||
|
||||
export default function createDiscordStub(sendStub, guild, discordUsers) {
|
||||
export default function createDiscordStub(sendStub) {
|
||||
return class DiscordStub extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.user = {
|
||||
id: 'testid'
|
||||
};
|
||||
this.channels = this.guildChannels();
|
||||
this.channels = new discord.ChannelManager(this, []);
|
||||
this.options = {
|
||||
http: {
|
||||
cdn: ''
|
||||
}
|
||||
};
|
||||
|
||||
this.users = discordUsers;
|
||||
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();
|
||||
}
|
||||
|
||||
guildChannels() {
|
||||
const channels = new discord.Collection();
|
||||
channels.set('1234', {
|
||||
name: 'discord',
|
||||
id: '1234',
|
||||
type: 'text',
|
||||
send: sendStub,
|
||||
guild
|
||||
});
|
||||
return channels;
|
||||
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(''),
|
||||
};
|
||||
}
|
||||
|
||||
login() {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import events from 'events';
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
class ClientStub extends events.EventEmitter {
|
||||
constructor(...args) {
|
||||
super();
|
||||
this.nick = args[1];
|
||||
this.nick = args[1]; // eslint-disable-line prefer-destructuring
|
||||
}
|
||||
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
export default ClientStub;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* 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() {}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user