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 | ||
|
|
552bba2c75 | ||
|
|
1ff61413cc | ||
|
|
7a7cd24695 | ||
|
|
549b8fb767 | ||
|
|
ff788083d1 | ||
|
|
e8c96e2a44 | ||
|
|
92d5b437ba | ||
|
|
8a23a91931 | ||
|
|
4facb56ebb | ||
|
|
07662761b4 | ||
|
|
34cdd373af | ||
|
|
ddd9c54645 | ||
|
|
f0ffc260cc | ||
|
|
2f6a3d5eed | ||
|
|
e47d8e3aab | ||
|
|
f56b51a569 | ||
|
|
74dea2b67d | ||
|
|
3a1841c8ca | ||
|
|
531ea6bd95 | ||
|
|
726100eb98 | ||
|
|
7f7505cae7 | ||
|
|
c9299a6269 | ||
|
|
ae5fc08222 | ||
|
|
031f22f1ec | ||
|
|
d62dcfb9e6 | ||
|
|
b299d80169 | ||
|
|
85a55e6ef2 | ||
|
|
1aff7a99ef | ||
|
|
47a5a6c085 | ||
|
|
483fc44e9b | ||
|
|
67ac5f1605 | ||
|
|
d7b4ab5889 | ||
|
|
474d298713 | ||
|
|
afc5df4a86 | ||
|
|
6c30f7b933 | ||
|
|
596a9fdbae | ||
|
|
c0d443d9c5 | ||
|
|
76fda25a19 | ||
|
|
80c3752074 | ||
|
|
efa3d513fe | ||
|
|
8c0c11a2b1 | ||
|
|
2bf761b163 | ||
|
|
fa9ef71372 | ||
|
|
8c4d83a37d | ||
|
|
fd95d7d9b0 | ||
|
|
5453e4589f | ||
|
|
fd88a992b1 | ||
|
|
131b48fb01 | ||
|
|
a03433d357 | ||
|
|
ec7ca8b88d | ||
|
|
2e96dd7b79 | ||
|
|
4edb307556 | ||
|
|
e2eb354c0b | ||
|
|
284d60b0ad | ||
|
|
b4452dd487 | ||
|
|
2c05038c37 | ||
|
|
9ea90a9d96 | ||
|
|
3ca43ae0f4 | ||
|
|
9c7bc2f8c7 | ||
|
|
fe37ec48a4 | ||
|
|
ef73243b21 | ||
|
|
03596570bc | ||
|
|
aefd88cd97 | ||
|
|
4d238d9bd4 | ||
|
|
8c3ad07386 | ||
|
|
d877ae2fdd | ||
|
|
ce7db43ef1 | ||
|
|
4120c5961b | ||
|
|
de448eac49 | ||
|
|
884f0cafa0 | ||
|
|
beb1b336d6 | ||
|
|
cc990d67e1 | ||
|
|
1c8fa12def | ||
|
|
27f9abe6c7 | ||
|
|
a41cd0c6a1 | ||
|
|
cb184cc023 | ||
|
|
c9fa8ba596 | ||
|
|
f996405964 | ||
|
|
215c33baff | ||
|
|
99e3a0f83f | ||
|
|
2b85b8f458 | ||
|
|
de68c48202 | ||
|
|
607cb687fd | ||
|
|
c39d751f4f | ||
|
|
0633c32b0a | ||
|
|
611aa6b6db | ||
|
|
4322ed595e | ||
|
|
352065b019 | ||
|
|
b60c86c858 | ||
|
|
89ec8128f2 | ||
|
|
3356e99781 | ||
|
|
9439b9388c | ||
|
|
eb1e56ffd9 | ||
|
|
3384c32c32 | ||
|
|
c4944a4f7c | ||
|
|
b163759c7d | ||
|
|
79ecf68ec9 | ||
|
|
1a7ea2d4a2 | ||
|
|
9848a89dd6 | ||
|
|
e4a994b996 | ||
|
|
b88695eb56 | ||
|
|
8e7900c9c5 | ||
|
|
60e7d78c6f | ||
|
|
4599e1731b | ||
|
|
b345485366 | ||
|
|
9915db1585 | ||
|
|
200a0b996e | ||
|
|
bc3f51750a | ||
|
|
1daf62e5a6 | ||
|
|
8c37393ca6 | ||
|
|
25eae441d1 | ||
|
|
8ef4ba9d44 | ||
|
|
86fadd5979 | ||
|
|
bc712281b8 | ||
|
|
336e87e861 | ||
|
|
cb35b49e4c | ||
|
|
0684668c82 | ||
|
|
43d5e27ca1 | ||
|
|
31042c1eda | ||
|
|
b30c4c34f3 | ||
|
|
860a4137a7 | ||
|
|
a1dfdf65ff | ||
|
|
91bbbad872 | ||
|
|
2ae9e370b6 | ||
|
|
f882e15b8b | ||
|
|
26626e55a5 | ||
|
|
a8fe5cb841 | ||
|
|
7e896addbc | ||
|
|
59f4d7594e | ||
|
|
a67e94c1a2 | ||
|
|
1139f3db47 | ||
|
|
ebe340b7cb | ||
|
|
6bfd774d1c | ||
|
|
8482a5e493 | ||
|
|
2f59cbe2ef | ||
|
|
b6f53cb241 | ||
|
|
17d27293ef | ||
|
|
0f48dfebe8 | ||
|
|
b4aef61c79 | ||
|
|
de8aa80b86 | ||
|
|
8de8f4958a | ||
|
|
f865e46ed7 | ||
|
|
d69884e703 | ||
|
|
c922d3bec5 | ||
|
|
79b56caae4 | ||
|
|
c3e9fc026a | ||
|
|
fe841eac4d | ||
|
|
cb6e41889a | ||
|
|
bac7069421 | ||
|
|
4031f9e601 | ||
|
|
38e4d3b818 | ||
|
|
8a6935c2e5 | ||
|
|
ad2c9747c3 | ||
|
|
ae71708e9c | ||
|
|
18f201b16f | ||
|
|
a5bda5fcfc | ||
|
|
20c7b99db2 | ||
|
|
3667bf1627 | ||
|
|
cee885b7fe | ||
|
|
31609e9245 | ||
|
|
0b8ef54831 | ||
|
|
615991a69a | ||
|
|
9727ec6c7b | ||
|
|
7d312ffb26 | ||
|
|
abc34f4049 | ||
|
|
00527b5e09 | ||
|
|
fc80b4af0c | ||
|
|
b4d6228ad9 | ||
|
|
2b82f72476 | ||
|
|
e6ee35b2be | ||
|
|
29956a8ef1 | ||
|
|
55dbfa622f | ||
|
|
bb5b9a8719 | ||
|
|
1866a4160a | ||
|
|
5168aeaf3a | ||
|
|
0779c64129 | ||
|
|
406ca0de59 | ||
|
|
0d75a01ab1 | ||
|
|
395213b67c | ||
|
|
c3881be0bb | ||
|
|
007e15cffe | ||
|
|
6da2fed645 | ||
|
|
ae6ebbe749 | ||
|
|
e9f53ed68e | ||
|
|
9ceb1418c0 | ||
|
|
2fd870fc1b | ||
|
|
7b934e5319 | ||
|
|
22d74daf9e | ||
|
|
6f0244949a | ||
|
|
479c0b2ced | ||
|
|
be42399ab8 | ||
|
|
9ddd858722 | ||
|
|
a17584739b | ||
|
|
35f060e1b0 | ||
|
|
51f0d705e4 | ||
|
|
44849df01b | ||
|
|
11fd54d76d | ||
|
|
a3b76d04d2 | ||
|
|
55d195890d | ||
|
|
8ea0cbd7bb | ||
|
|
80e2958329 | ||
|
|
ed2da6f9f6 | ||
|
|
29432c35e1 | ||
|
|
e2a3423b11 | ||
|
|
150e57ee6f | ||
|
|
ce56d7dd4d | ||
|
|
0ada98223a | ||
|
|
633b490b23 | ||
|
|
add4f05877 | ||
|
|
f197f9d2d5 | ||
|
|
977d8a4a7c | ||
|
|
bc2e66a654 | ||
|
|
68bfe77635 | ||
|
|
1aa90830d9 | ||
|
|
6eed323c5d | ||
|
|
65e40b3b14 | ||
|
|
9e05344374 | ||
|
|
483798cd54 | ||
|
|
2039577f92 | ||
|
|
4201ee76c0 | ||
|
|
6ffd5e13da | ||
|
|
6e450c77b9 | ||
|
|
29cc5a1c55 | ||
|
|
d2b8aee321 | ||
|
|
0d858790b8 | ||
|
|
56bd19d6e4 | ||
|
|
6995a8fa23 | ||
|
|
64e515e866 | ||
|
|
a5a962c308 | ||
|
|
48a4781cba | ||
|
|
e585270512 | ||
|
|
8f876e3970 | ||
|
|
e94e506d91 | ||
|
|
a6271c02a6 | ||
|
|
e0b2e4c9b1 | ||
|
|
cb0a8b128a | ||
|
|
d0ded9a4e2 | ||
|
|
df04ee1b2e | ||
|
|
50e8a434f7 | ||
|
|
a5ba794865 | ||
|
|
a3be583ccd | ||
|
|
7d8415fb3a | ||
|
|
bdfe3affcc | ||
|
|
6d0dd1e404 | ||
|
|
a185d9aecc | ||
|
|
6f9ec0744f | ||
|
|
007d6ceb99 | ||
|
|
c5e47bb4aa | ||
|
|
064a333214 | ||
|
|
36908e6865 | ||
|
|
486003ee9f | ||
|
|
770a0052a1 | ||
|
|
14cc47f160 | ||
|
|
7e39e80001 | ||
|
|
d3c3ccd27e | ||
|
|
5ffb103df2 | ||
|
|
59d42e18af | ||
|
|
14d8015f8a | ||
|
|
1538089f33 | ||
|
|
7d67f6406c | ||
|
|
4634696ed9 | ||
|
|
56b280933a | ||
|
|
8a44fde1d3 | ||
|
|
b295e494b3 | ||
|
|
6205e77f2c | ||
|
|
17e973ec40 | ||
|
|
e657317daa | ||
|
|
fec2eacd1f | ||
|
|
9d75cbcdaf | ||
|
|
d53efaa8fe | ||
|
|
aa61b84d7d | ||
|
|
a5221cc7d1 | ||
|
|
9362083421 | ||
|
|
53d1ef0b6a | ||
|
|
409835c12a | ||
|
|
f6432cb958 | ||
|
|
b760d47ac4 | ||
|
|
5b54a9461a | ||
|
|
f75dd5c213 | ||
|
|
36fd4c6ddf | ||
|
|
6b65b87c52 | ||
|
|
6777585bf1 | ||
|
|
5bf86bc7df | ||
|
|
581830c22d | ||
|
|
9b81a7e930 |
@@ -1,3 +1,19 @@
|
||||
{
|
||||
"presets": ["es2015", "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,11 +1,19 @@
|
||||
{
|
||||
"extends": "webkom",
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"spaced-comment": 0 // disable because of she-bang bug in index.js
|
||||
},
|
||||
"extends": "airbnb-base",
|
||||
"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,
|
||||
"operator-linebreak": 0,
|
||||
"implicit-arrow-linebreak": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
tasks:
|
||||
- npm install
|
||||
- npm run lint
|
||||
- npm run coverage
|
||||
|
||||
coverage:
|
||||
path: coverage/cobertura-coverage.xml
|
||||
parser: cobertura
|
||||
@@ -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,5 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.12'
|
||||
- '4'
|
||||
- '5'
|
||||
+315
-1
@@ -1,6 +1,320 @@
|
||||
# Changelog
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.9.0] - 2020-10-03
|
||||
This release comes with a (relatively large) change to use discord.js v12!
|
||||
There were a lot of breaking changes in the upstream library, including removal of support for Node <12, which prompted a 2.9.0-alpha release before this main version.
|
||||
Everything seemed to go fine, which is why you're now receiving this latest update!
|
||||
|
||||
### Added
|
||||
* IRC nick colors are now customizable in the config (thanks to [KWeaver87](https://github.com/reactiflux/discord-irc/pull/561))
|
||||
|
||||
### Changed
|
||||
* Upgrade discord.js to 12.3.1 (the pre-release came with 12.2.0) - this changes a lot of how the bot works behind the scenes, and prepares us for upcoming Discord API changes - [#555](https://github.com/reactiflux/discord-irc/pull/555), [#564](https://github.com/reactiflux/discord-irc/pull/564)
|
||||
* Add support for Node 14 ([#549](https://github.com/reactiflux/discord-irc/pull/549)); drop support for Node 6, 8 and 10 ([#550](https://github.com/reactiflux/discord-irc/pull/550))
|
||||
|
||||
### Fixed
|
||||
* A bunch of vulnerabilities listed in `npm audit` are now gone - [#544](https://github.com/reactiflux/discord-irc/pull/544), [#548](https://github.com/reactiflux/discord-irc/pull/548), [#551](https://github.com/reactiflux/discord-irc/pull/551), [#552](https://github.com/reactiflux/discord-irc/pull/552), [#553](https://github.com/reactiflux/discord-irc/pull/553), [#562](https://github.com/reactiflux/discord-irc/pull/562)
|
||||
* Log messages now typically take up one line again (fixing a relatively long-standing bug, at this point – oops!) - [#554](https://github.com/reactiflux/discord-irc/pull/554)
|
||||
* Mentioning the same user more than once in a single message is now fixed (thanks to [Qyriad](https://github.com/reactiflux/discord-irc/pull/541))
|
||||
* Our Discord client now uses a retry limit of 3 (rather than 1) on internal server errors (hopefully fixing [#461](https://github.com/reactiflux/discord-irc/issues/461)) - [#565](https://github.com/reactiflux/discord-irc/pull/565)
|
||||
|
||||
## [2.8.1] - 2020-03-16
|
||||
### Fixed
|
||||
* Large avatars failed to display when bridging through webhooks - (thanks to [Miosame](https://github.com/reactiflux/discord-irc/pull/511), follow up in [#530](https://github.com/reactiflux/discord-irc/pull/530))
|
||||
* Update acorn to 7.1.1 - [#534](https://github.com/reactiflux/discord-irc/pull/534)
|
||||
* Remove code coverage instrumentation from `dist/` files - [#536](https://github.com/reactiflux/discord-irc/pull/536)
|
||||
|
||||
## [2.8.0] - 2019-12-14
|
||||
### Added
|
||||
* `format.webhookAvatarURL`, to customize the unrecognized user webhook avatar (thanks to [Geo1088](https://github.com/reactiflux/discord-irc/pull/419))
|
||||
* `parallelPingFix`, disabled by default, to prevent users of both Discord and IRC getting pings whenever their messages are mirrored to IRC (thanks to [qaisjp](https://github.com/reactiflux/discord-irc/pull/243) originally, follow up in [#502](https://github.com/reactiflux/discord-irc/pull/502) and [#520](https://github.com/reactiflux/discord-irc/pull/520))
|
||||
* `ignoreUsers.discordIds`, to ignore specific Discord users through the bridge by ID instead of name (thanks to [nsavch](https://github.com/reactiflux/discord-irc/pull/508))
|
||||
* A basic Docker image, found at [discordirc/discord-irc](https://hub.docker.com/r/discordirc/discord-irc)! This may not be often updated with the release tags we publish on GitHub, but it should contain a `latest` tag in addition to whichever Git hash is available on main (thanks to [gdude2002](https://github.com/reactiflux/discord-irc/pull/421), follow up in [#498](https://github.com/reactiflux/discord-irc/pull/498))
|
||||
|
||||
### Changed
|
||||
* Add support for Node 13, drop testing for Node 6 - [#521](https://github.com/reactiflux/discord-irc/pull/521)
|
||||
|
||||
### Fixed
|
||||
* Upgrade various dependencies: babel, commander, coveralls, discord.js, eslint, mocha, simple-markdown, sinon, sinon-chai - [#488](https://github.com/reactiflux/discord-irc/pull/488), [#503](https://github.com/reactiflux/discord-irc/pull/503), [#522](https://github.com/reactiflux/discord-irc/pull/522), [#523](https://github.com/reactiflux/discord-irc/pull/523), [#524](https://github.com/reactiflux/discord-irc/pull/524/files), [#525](https://github.com/reactiflux/discord-irc/pull/525)
|
||||
|
||||
## [2.7.2] - 2019-08-08
|
||||
### Fixed
|
||||
* Defer to discord permissions for allowing `@everyone` and `@here` in webhook messages - [#497](https://github.com/reactiflux/discord-irc/pull/497)
|
||||
* Support Node 10 and 12 - [#499](https://github.com/reactiflux/discord-irc/pull/499)
|
||||
* Upgrade dependencies
|
||||
* Tests: Fix lint config deprecation - [#500](https://github.com/reactiflux/discord-irc/pull/500)
|
||||
* Tests: Ensure all tests are run in dev environment - [#501](https://github.com/reactiflux/discord-irc/pull/501)
|
||||
|
||||
## [2.7.1] - 2019-06-15
|
||||
### Changed
|
||||
* Upgraded dependencies.
|
||||
|
||||
## [2.7.0] - 2019-04-02
|
||||
### Changed
|
||||
* Convert channel mentions to codified mentions (thanks to [Throne3d](https://github.com/reactiflux/discord-irc/pull/476)).
|
||||
* Match IRC style mentions at the beginning of message (thanks to [rdb](https://github.com/reactiflux/discord-irc/pull/470)).
|
||||
* Upgraded dependencies.
|
||||
|
||||
## [2.6.2] - 2018-09-19
|
||||
### Changed
|
||||
* Upgraded dependencies.
|
||||
|
||||
## [2.6.1] - 2018-05-11
|
||||
### Changed
|
||||
* Upgraded dependencies.
|
||||
|
||||
## [2.6.0] - 2018-03-22
|
||||
### Added
|
||||
* Support for posting messages to Discord using webhooks (thanks to
|
||||
[Fiaxhs](https://github.com/reactiflux/discord-irc/pull/230)!).
|
||||
|
||||
Webhooks lets you override nicknames and avatars, so messages coming from IRC
|
||||
can appear as regular Discord messages:
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
||||
### Fixed
|
||||
* Update link to IRC library in README so it points to the new irc-upd library - [#264](https://github.com/reactiflux/discord-irc/pull/264)
|
||||
|
||||
* Update dependency commander to 2.11.0 - [#262](https://github.com/reactiflux/discord-irc/pull/262)
|
||||
|
||||
* Fix deprecation warning on `TextChannel#sendMessage` - [#267](https://github.com/reactiflux/discord-irc/pull/267)
|
||||
|
||||
* Fix reconnection by updating dependency irc-upd to 0.6.2 - [#270](https://github.com/reactiflux/discord-irc/pull/270)
|
||||
|
||||
## [2.4.0] - 2017-07-01
|
||||
This project now uses [irc-upd](https://github.com/Throne3d/node-irc) as a dependency, instead of the old [irc](https://github.com/martynsmith/node-irc) package – this fork should be better maintained and will solve some bugs, detailed below.
|
||||
|
||||
### Added
|
||||
* Allow commandCharacters to work for messages sent to Discord - [#221](https://github.com/reactiflux/discord-irc/pull/221).
|
||||
|
||||
* Send nick changes from IRC to Discord with `ircStatusNotices` - [#235](https://github.com/reactiflux/discord-irc/pull/235), [#241](https://github.com/reactiflux/discord-irc/pull/241).
|
||||
|
||||
* Translate custom emoji references from IRC to Discord - [#256](https://github.com/reactiflux/discord-irc/pull/256).
|
||||
|
||||
### Fixed
|
||||
* Use `ircClient.nick` instead of `nickname` when checking if the `ircStatusNotices` event is for the bot, to prevent a potential crash - [#257](https://github.com/reactiflux/discord-irc/pull/257).
|
||||
|
||||
* Use the updated `irc-upd` library instead of `irc`, causing IRC messages to now be split by byte instead of character (fixing [#199](https://github.com/reactiflux/discord-irc/issues/199)) and adding support for certain Unicode characters in nicknames (fixing [#200](https://github.com/reactiflux/discord-irc/issues/200)) - [#258](https://github.com/reactiflux/discord-irc/pull/258).
|
||||
|
||||
* Update dependencies:
|
||||
|
||||
- discord.js to 11.1.0
|
||||
- check-env to 1.3.0
|
||||
- chai to ^4.0.2
|
||||
- nyc to ^11.0.3
|
||||
- commander to 2.10.0
|
||||
- eslint to ^4.1.1
|
||||
|
||||
## [2.3.3] - 2017-04-29
|
||||
### Fixed
|
||||
* Warn if a part/quit is received and no channelUsers is set -
|
||||
[#218](https://github.com/reactiflux/discord-irc/pull/218).
|
||||
|
||||
## [2.3.2] - 2017-04-27
|
||||
### Fixed
|
||||
* Fix ircStatucNotices when channels are not lowercase -
|
||||
[#219](https://github.com/reactiflux/discord-irc/pull/219).
|
||||
|
||||
## [2.3.1] - 2017-04-05
|
||||
### Fixed
|
||||
* Fix IRC quit messages sending to all channels by tracking users - [#214](https://github.com/reactiflux/discord-irc/pull/214#pullrequestreview-31156291).
|
||||
|
||||
## [2.3.0] - 2017-04-03
|
||||
A huge thank you to [@Throne3d](https://github.com/Throne3d),
|
||||
[@rahatarmanahmed](https://github.com/rahatarmanahmed), [@mraof](https://github.com/mraof)
|
||||
and [@Ratismal](https://github.com/Ratismal) for all the fixes and features
|
||||
in this release.
|
||||
|
||||
### Added
|
||||
* Bridge IRC join/part/quit messages to Discord
|
||||
(enable by setting ircStatusNotices to true) -
|
||||
[#207](https://github.com/reactiflux/discord-irc/pull/207).
|
||||
|
||||
* Convert text styles between IRC and Discord
|
||||
[#205](https://github.com/reactiflux/discord-irc/pull/205).
|
||||
|
||||
* Allow users to configure the patterns of messages on
|
||||
IRC and Discord using the format options object
|
||||
[#204](https://github.com/reactiflux/discord-irc/pull/204).
|
||||
|
||||
* Add Discord channel ID matching to the channel mapping
|
||||
[#202](https://github.com/reactiflux/discord-irc/pull/202).
|
||||
|
||||
### Fixed
|
||||
* Parse role mentions appropriately, as with channel and user mentions
|
||||
[#203](https://github.com/reactiflux/discord-irc/pull/203).
|
||||
|
||||
* Make the bot not crash when a channel mentioned by ID fails to exist
|
||||
[#201](https://github.com/reactiflux/discord-irc/pull/201).
|
||||
|
||||
### Changed
|
||||
* Convert username mentions even if nickname is set -
|
||||
[#208](https://github.com/reactiflux/discord-irc/pull/208).
|
||||
|
||||
## [2.2.1] - 2017-03-12
|
||||
### Fixed
|
||||
* Reverts the changes in 2.2.0 due to incompatibilities with different clients.
|
||||
See https://github.com/reactiflux/discord-irc/issues/196 for more
|
||||
information.
|
||||
|
||||
## [2.2.0] - 2017-03-06
|
||||
### Fixed
|
||||
* Added a zero width character between each letter of the IRC nicknames, to
|
||||
avoid unwanted highlights. Fixed by @Sanqui in
|
||||
[#193](https://github.com/reactiflux/discord-irc/pull/193).
|
||||
|
||||
## [2.1.6] - 2017-01-10
|
||||
### Fixed
|
||||
* Upgraded discord.js.
|
||||
|
||||
## [2.1.6] - 2016-12-08
|
||||
### Fixed
|
||||
* Listen to warn events from Discord.
|
||||
* Listen to debug events from Discord (only in development).
|
||||
* Log info events upon connection, instead of debug
|
||||
|
||||
## [2.1.5] - 2016-11-17
|
||||
### Fixed
|
||||
* Upgraded node-irc to 0.5.1, fixing #129.
|
||||
|
||||
## [2.1.4] - 2016-11-14
|
||||
### Fixed
|
||||
* Added support for highlighting users by their nicknames,
|
||||
thanks to @DarkSpyro003.
|
||||
* Upgraded to discord.js v10.
|
||||
|
||||
## [2.1.3] - 2016-11-02
|
||||
### Fixed
|
||||
* Send text messages only to text channels (thanks @dustinlacewell).
|
||||
|
||||
## [2.1.2] - 2016-11-01
|
||||
### Fixed
|
||||
* Use nickname, not username, in command prelude.
|
||||
Thanks to @williamjacksn.
|
||||
|
||||
## [2.1.1] - 2016-10-21
|
||||
### Fixed
|
||||
* A bug where Discord attachment URLs weren't posted to IRC, thanks to @LordAlderaan for the report.
|
||||
|
||||
## [2.1.0] - 2016-10-09
|
||||
### Added
|
||||
* Messages sent to IRC will now use the correct server nickname,
|
||||
instead of the user's global username (thanks to @DarkSpyro003).
|
||||
|
||||
## [2.0.2] - 2016-10-02
|
||||
### Fixed
|
||||
- Display custom emojis correctly, thanks to @macdja38.
|
||||
|
||||
## [2.0.0] - 2016-09-25
|
||||
### Fixed
|
||||
- Upgrade to version 9.3 of discord.js.
|
||||
This removes support for Node.js versions older than v6,
|
||||
as that's the oldest discord.js supports.
|
||||
|
||||
## [1.0.3] - 2016-09-09
|
||||
### Fixed
|
||||
- Replace changes in 1.0.2 with the #indev-old version
|
||||
of discord.js.
|
||||
|
||||
## [1.0.2] - 2016-09-09
|
||||
### Fixed
|
||||
- Discord's API now requires bot tokens
|
||||
to be prefixed with "Bot". This adds
|
||||
a hotfix that does exactly that.
|
||||
|
||||
## [1.0.1] - 2016-06-19
|
||||
### Fixed
|
||||
- Upgraded dependencies.
|
||||
|
||||
## [1.0.0] - 2016-05-06
|
||||
### Changed
|
||||
- Breaking: discord-irc now uses tokens for authentication, instead of
|
||||
email/password, thanks to @TheDoctorsLife. See the README for more instructions.
|
||||
|
||||
## [0.8.2] - 2016-04-21
|
||||
### Fixed
|
||||
- Enable auto reconnect for IRC and Discord.
|
||||
|
||||
## [0.8.1] - 2016-04-21
|
||||
### Fixed
|
||||
- Upgrade discord.js to 7.0.1.
|
||||
|
||||
## [0.8.0] - 2016-04-04
|
||||
Implemented by @rce:
|
||||
### Added
|
||||
- Support for messages containing both attachments and text.
|
||||
|
||||
### Changed
|
||||
- Attachment URLs are now posted by themselves, instead of including a
|
||||
preliminary message explaining that it's an attachment.
|
||||
|
||||
## [0.7.0] - 2016-04-04
|
||||
### Added
|
||||
- Added the config option `ircNickColor` to make it possible to
|
||||
disable nick colors for messages sent to IRC.
|
||||
|
||||
## [0.6.1] - 2016-04-04
|
||||
### Fixed
|
||||
- Upgrade dependencies.
|
||||
@@ -11,7 +325,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.5.0] - 2016-02-08
|
||||
### Added
|
||||
- Discord attachments will be linked to on IRC when
|
||||
- Discord attachments will be linked to on IRC when
|
||||
they're posted (fixed by @rce).
|
||||
|
||||
## [0.4.3] - 2016-01-23
|
||||
|
||||
+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"]
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Martin Ek mail@ekmartin.no
|
||||
Copyright (c) 2016 Martin Ek mail@ekmartin.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
build:
|
||||
docker-compose -f docker-compose.yml up -d --build --remove-orphans
|
||||
@@ -1,14 +1,22 @@
|
||||
# discord-irc [](https://ci.frigg.io/reactiflux/discord-irc/last/) [](https://ci.frigg.io/reactiflux/discord-irc/last/)
|
||||
[](https://coveralls.io/github/reactiflux/discord-irc?branch=main)
|
||||
|
||||
> Connects [Discord](https://discordapp.com/) and IRC channels by sending messages back and forth.
|
||||
|
||||
A mostly feature complete port of [slack-irc](https://github.com/ekmartin/slack-irc).
|
||||
> 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
|
||||
Either by installing through npm:
|
||||
**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).
|
||||
After you've done that you can replace `/path/to/config.json` in the commands
|
||||
below with the path to your newly created configuration file - or just `config.json` if it's
|
||||
in the same directory as the one you're starting the bot from.
|
||||
|
||||
When you've done that you can install and start the bot either through npm:
|
||||
|
||||
```bash
|
||||
$ npm install -g discord-irc
|
||||
$ discord-irc --config /path/to/config.json
|
||||
@@ -19,7 +27,7 @@ or by cloning the repository:
|
||||
```bash
|
||||
In the repository folder:
|
||||
$ npm install
|
||||
$ npm run build # Transpile ES6 to ES5 using Babel
|
||||
$ npm run build
|
||||
$ npm start -- --config /path/to/config.json # Note the extra double dash
|
||||
```
|
||||
|
||||
@@ -30,8 +38,26 @@ 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
|
||||
Create a discord user and join the server you want to connect to IRC.
|
||||
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).
|
||||
|
||||
### Example configuration
|
||||
```js
|
||||
@@ -40,8 +66,7 @@ Create a discord user and join the server you want to connect to IRC.
|
||||
{
|
||||
"nickname": "test2",
|
||||
"server": "irc.testbot.org",
|
||||
"discordEmail": "user@mail.com",
|
||||
"discordPassword": "letmein123",
|
||||
"discordToken": "botwantsin123",
|
||||
"channelMapping": {
|
||||
"#other-discord": "#new-irc-channel"
|
||||
}
|
||||
@@ -51,28 +76,82 @@ Create a discord user and join the server you want to connect to IRC.
|
||||
{
|
||||
"nickname": "test",
|
||||
"server": "irc.bottest.org",
|
||||
"discordEmail": "user@mail.com",
|
||||
"discordPassword": "letmein123",
|
||||
"discordToken": "botwantsin123",
|
||||
"autoSendCommands": [ // Commands that will be sent on connect
|
||||
["PRIVMSG", "NickServ", "IDENTIFY password"],
|
||||
["MODE", "test", "+x"],
|
||||
["AUTH", "test", "password"]
|
||||
],
|
||||
"channelMapping": { // Maps each Discord-channel to an IRC-channel, used to direct messages to the correct place
|
||||
"#discord": "#irc channel-password" // Add channel keys after the channel name
|
||||
"#discord": "#irc channel-password", // Add channel keys after the channel name
|
||||
"1234567890": "#channel" // Use a discord channel ID instead of its name (so you can rename it or to disambiguate)
|
||||
},
|
||||
"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
|
||||
// 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": ["!", "."]
|
||||
"commandCharacters": ["!", "."],
|
||||
"ircStatusNotices": true, // Enables notifications in Discord when people join/part in the relevant IRC channel
|
||||
"ignoreUsers": {
|
||||
"irc": ["irc_nick1", "irc_nick2"], // Ignore specified IRC nicks and do not send their messages to Discord.
|
||||
"discord": ["discord_nick1", "discord_nick2"], // Ignore specified Discord nicks and do not send their messages to IRC.
|
||||
"discordIds": ["198528216523210752"] // Ignore specified Discord ids and do not send their messages to IRC.
|
||||
},
|
||||
// List of webhooks per channel
|
||||
"webhooks": {
|
||||
"#discord": "https://discord.com/api/webhooks/id/token"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `ircOptions` object is passed directly to node-irc ([available options](http://node-irc.readthedocs.org/en/latest/API.html#irc.Client)).
|
||||
The `ircOptions` object is passed directly to irc-upd ([available options](https://node-irc-upd.readthedocs.io/en/latest/API.html#irc.Client)).
|
||||
|
||||
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:
|
||||
@@ -81,24 +160,9 @@ $ npm test
|
||||
```
|
||||
|
||||
## Style Guide
|
||||
discord-irc uses a slightly modified version of the
|
||||
[Airbnb Style Guide](https://github.com/airbnb/javascript/tree/master/es5).
|
||||
discord-irc follows the [Airbnb Style Guide](https://github.com/airbnb/javascript).
|
||||
[ESLint](http://eslint.org/) is used to make sure this is followed correctly, which can be run with:
|
||||
|
||||
```bash
|
||||
$ npm run lint
|
||||
```
|
||||
|
||||
The deviations from the Airbnb Style Guide can be seen in the [.eslintrc](.eslintrc) file.
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2015 Martin Ek <mail@ekmartin.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -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
|
||||
+494
-55
@@ -1,13 +1,19 @@
|
||||
import _ from 'lodash';
|
||||
import irc from 'irc';
|
||||
import logger from 'winston';
|
||||
import irc from 'irc-upd';
|
||||
import discord from 'discord.js';
|
||||
import logger from './logger';
|
||||
import { ConfigurationError } from './errors';
|
||||
import { validateChannelMapping } from './validators';
|
||||
import { formatFromDiscordToIRC, formatFromIRCToDiscord } from './formatting';
|
||||
|
||||
const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordEmail', 'discordPassword'];
|
||||
const NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
|
||||
// Usernames need to be between 2 and 32 characters for webhooks:
|
||||
const USERNAME_MIN_LENGTH = 2;
|
||||
const USERNAME_MAX_LENGTH = 32;
|
||||
|
||||
const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordToken'];
|
||||
const DEFAULT_NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
|
||||
'dark_green', 'magenta', 'light_magenta', 'orange', 'yellow', 'cyan', 'light_cyan'];
|
||||
const patternMatch = /{\$(.+?)}/g;
|
||||
|
||||
/**
|
||||
* An IRC bot, works as a middleman for all communication
|
||||
@@ -15,7 +21,7 @@ const NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_
|
||||
*/
|
||||
class Bot {
|
||||
constructor(options) {
|
||||
REQUIRED_FIELDS.forEach(field => {
|
||||
REQUIRED_FIELDS.forEach((field) => {
|
||||
if (!options[field]) {
|
||||
throw new ConfigurationError(`Missing configuration field ${field}`);
|
||||
}
|
||||
@@ -23,17 +29,64 @@ class Bot {
|
||||
|
||||
validateChannelMapping(options.channelMapping);
|
||||
|
||||
this.discord = new discord.Client();
|
||||
this.discord = new discord.Client({
|
||||
autoReconnect: true,
|
||||
retryLimit: 3,
|
||||
});
|
||||
|
||||
this.server = options.server;
|
||||
this.nickname = options.nickname;
|
||||
this.ircOptions = options.ircOptions;
|
||||
this.discordEmail = options.discordEmail;
|
||||
this.discordPassword = options.discordPassword;
|
||||
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
|
||||
// discordChannel: Discord channel (e.g. #general)
|
||||
// ircChannel: IRC channel (e.g. #irc)
|
||||
// text: the (appropriately formatted) message content
|
||||
this.format = options.format || {};
|
||||
|
||||
// "{$keyName}" => "variableValue"
|
||||
// displayUsername: nickname with wrapped colors
|
||||
// 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) {
|
||||
this.formatCommandPrelude = this.format.commandPrelude;
|
||||
} else {
|
||||
this.formatCommandPrelude = 'Command sent from {$side} by {$nickname}:';
|
||||
}
|
||||
|
||||
// "{$keyName}" => "variableValue"
|
||||
// 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) => {
|
||||
@@ -46,7 +99,17 @@ class Bot {
|
||||
|
||||
connect() {
|
||||
logger.debug('Connecting to IRC and Discord');
|
||||
this.discord.login(this.discordEmail, this.discordPassword);
|
||||
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,
|
||||
@@ -54,34 +117,58 @@ class Bot {
|
||||
channels: this.channels,
|
||||
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.debug('Connected to Discord');
|
||||
logger.info('Connected to Discord');
|
||||
});
|
||||
|
||||
this.ircClient.on('registered', message => {
|
||||
this.ircClient.on('registered', (message) => {
|
||||
logger.info('Connected to IRC');
|
||||
logger.debug('Registered event: ', message);
|
||||
this.autoSendCommands.forEach(element => {
|
||||
this.autoSendCommands.forEach((element) => {
|
||||
this.ircClient.send(...element);
|
||||
});
|
||||
});
|
||||
|
||||
this.ircClient.on('error', error => {
|
||||
this.ircClient.on('error', (error) => {
|
||||
logger.error('Received error event from IRC', error);
|
||||
});
|
||||
|
||||
this.discord.on('error', error => {
|
||||
this.discord.on('error', (error) => {
|
||||
logger.error('Received error event from Discord', error);
|
||||
});
|
||||
|
||||
this.discord.on('message', message => {
|
||||
this.discord.on('warn', (warning) => {
|
||||
logger.warn('Received warn event from Discord', warning);
|
||||
});
|
||||
|
||||
this.discord.on('message', (message) => {
|
||||
// Ignore bot messages and people leaving/joining
|
||||
this.sendToIRC(message);
|
||||
});
|
||||
@@ -92,6 +179,72 @@ class Bot {
|
||||
this.sendToDiscord(author, to, `*${text}*`);
|
||||
});
|
||||
|
||||
this.ircClient.on('nick', (oldNick, newNick, channels) => {
|
||||
if (!this.ircStatusNotices) return;
|
||||
channels.forEach((channelName) => {
|
||||
const channel = channelName.toLowerCase();
|
||||
if (this.channelUsers[channel]) {
|
||||
if (this.channelUsers[channel].has(oldNick)) {
|
||||
this.channelUsers[channel].delete(oldNick);
|
||||
this.channelUsers[channel].add(newNick);
|
||||
this.sendExactToDiscord(channel, `*${oldNick}* is now known as ${newNick}`);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`No channelUsers found for ${channel} when ${oldNick} changed.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.ircClient.on('join', (channelName, nick) => {
|
||||
logger.debug('Received join:', channelName, nick);
|
||||
if (!this.ircStatusNotices) return;
|
||||
if (nick === this.ircClient.nick && !this.announceSelfJoin) return;
|
||||
const channel = channelName.toLowerCase();
|
||||
// self-join is announced before names (which includes own nick)
|
||||
// so don't add nick to channelUsers
|
||||
if (nick !== this.ircClient.nick) this.channelUsers[channel].add(nick);
|
||||
this.sendExactToDiscord(channel, `*${nick}* has joined the channel`);
|
||||
});
|
||||
|
||||
this.ircClient.on('part', (channelName, nick, reason) => {
|
||||
logger.debug('Received part:', channelName, nick, reason);
|
||||
if (!this.ircStatusNotices) return;
|
||||
const channel = channelName.toLowerCase();
|
||||
// remove list of users when no longer in channel (as it will become out of date)
|
||||
if (nick === this.ircClient.nick) {
|
||||
logger.debug('Deleting channelUsers as bot parted:', channel);
|
||||
delete this.channelUsers[channel];
|
||||
return;
|
||||
}
|
||||
if (this.channelUsers[channel]) {
|
||||
this.channelUsers[channel].delete(nick);
|
||||
} else {
|
||||
logger.warn(`No channelUsers found for ${channel} when ${nick} parted.`);
|
||||
}
|
||||
this.sendExactToDiscord(channel, `*${nick}* has left the channel (${reason})`);
|
||||
});
|
||||
|
||||
this.ircClient.on('quit', (nick, reason, channels) => {
|
||||
logger.debug('Received quit:', nick, channels);
|
||||
if (!this.ircStatusNotices || nick === this.ircClient.nick) return;
|
||||
channels.forEach((channelName) => {
|
||||
const channel = channelName.toLowerCase();
|
||||
if (!this.channelUsers[channel]) {
|
||||
logger.warn(`No channelUsers found for ${channel} when ${nick} quit, ignoring.`);
|
||||
return;
|
||||
}
|
||||
if (!this.channelUsers[channel].delete(nick)) return;
|
||||
this.sendExactToDiscord(channel, `*${nick}* has quit (${reason})`);
|
||||
});
|
||||
});
|
||||
|
||||
this.ircClient.on('names', (channelName, nicks) => {
|
||||
logger.debug('Received names:', channelName, nicks);
|
||||
if (!this.ircStatusNotices) return;
|
||||
const channel = channelName.toLowerCase();
|
||||
this.channelUsers[channel] = new Set(Object.keys(nicks));
|
||||
});
|
||||
|
||||
this.ircClient.on('action', (author, to, text) => {
|
||||
this.sendToDiscord(author, to, `_${text}_`);
|
||||
});
|
||||
@@ -105,81 +258,367 @@ class Bot {
|
||||
logger.debug('Joining channel:', channel);
|
||||
}
|
||||
});
|
||||
|
||||
if (logger.level === 'debug') {
|
||||
this.discord.on('debug', (message) => {
|
||||
logger.debug('Received debug event from Discord', message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static getDiscordNicknameOnServer(user, guild) {
|
||||
if (guild) {
|
||||
const userDetails = guild.members.cache.get(user.id);
|
||||
if (userDetails) {
|
||||
return userDetails.nickname || user.username;
|
||||
}
|
||||
}
|
||||
return user.username;
|
||||
}
|
||||
|
||||
parseText(message) {
|
||||
const text = message.mentions.reduce((content, mention) => (
|
||||
content.replace(`<@${mention.id}>`, `@${mention.username}`)
|
||||
), message.content);
|
||||
const text = message.mentions.users.reduce((content, mention) => {
|
||||
const displayName = Bot.getDiscordNicknameOnServer(mention, message.guild);
|
||||
const userMentionRegex = RegExp(`<@(&|!)?${mention.id}>`, 'g');
|
||||
return content.replace(userMentionRegex, `@${displayName}`);
|
||||
}, message.content);
|
||||
|
||||
return text
|
||||
.replace(/\n|\r\n|\r/g, ' ')
|
||||
.replace(/<#(\d+)>/g, (match, channelId) => {
|
||||
const channel = this.discord.channels.get('id', channelId);
|
||||
return `#${channel.name}`;
|
||||
});
|
||||
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.cache.get(roleId);
|
||||
if (role) return `@${role.name}`;
|
||||
return '@deleted-role';
|
||||
})
|
||||
.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) {
|
||||
return message.replace(patternMatch, (match, varName) => patternMapping[varName] || match);
|
||||
}
|
||||
|
||||
sendToIRC(message) {
|
||||
const author = message.author;
|
||||
const { author } = message;
|
||||
// Ignore messages sent by the bot itself:
|
||||
if (author.id === this.discord.user.id) 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[channelName];
|
||||
const ircChannel = this.channelMapping[message.channel.id] ||
|
||||
this.channelMapping[channelName];
|
||||
|
||||
logger.debug('Channel Mapping', channelName, this.channelMapping[channelName]);
|
||||
if (ircChannel) {
|
||||
const username = author.username;
|
||||
const colorIndex = (username.charCodeAt(0) + username.length) % NICK_COLORS.length;
|
||||
const coloredUsername = irc.colors.wrap(NICK_COLORS[colorIndex], username);
|
||||
const fromGuild = message.guild;
|
||||
const nickname = Bot.getDiscordNicknameOnServer(author, fromGuild);
|
||||
let text = this.parseText(message);
|
||||
let displayUsername = nickname;
|
||||
|
||||
if (this.parallelPingFix) {
|
||||
// Prevent users of both IRC and Discord from
|
||||
// being mentioned in IRC when they talk in Discord.
|
||||
displayUsername = `${displayUsername.slice(0, 1)}\u200B${displayUsername.slice(1)}`;
|
||||
}
|
||||
|
||||
if (this.ircNickColor) {
|
||||
const colorIndex = (nickname.charCodeAt(0) + nickname.length) % this.ircNickColors.length;
|
||||
displayUsername = irc.colors.wrap(this.ircNickColors[colorIndex], displayUsername);
|
||||
}
|
||||
|
||||
const patternMap = {
|
||||
author: nickname,
|
||||
nickname,
|
||||
displayUsername,
|
||||
text,
|
||||
discordChannel: channelName,
|
||||
ircChannel
|
||||
};
|
||||
|
||||
if (this.isCommandMessage(text)) {
|
||||
const prelude = `Command sent from Discord by ${username}:`;
|
||||
this.ircClient.say(ircChannel, prelude);
|
||||
patternMap.side = 'Discord';
|
||||
logger.debug('Sending command message to IRC', ircChannel, text);
|
||||
// if (prelude) this.ircClient.say(ircChannel, prelude);
|
||||
if (this.formatCommandPrelude) {
|
||||
const prelude = Bot.substitutePattern(this.formatCommandPrelude, patternMap);
|
||||
this.ircClient.say(ircChannel, prelude);
|
||||
}
|
||||
this.ircClient.say(ircChannel, text);
|
||||
} else if (message.attachments && message.attachments.length) {
|
||||
message.attachments.forEach(a => {
|
||||
const urlMessage =
|
||||
`${coloredUsername} posted an attachment to ${channelName} on Discord: ${a.url}`;
|
||||
logger.debug('Sending attachment URL to IRC', ircChannel, urlMessage);
|
||||
this.ircClient.say(ircChannel, urlMessage);
|
||||
});
|
||||
} else {
|
||||
text = `<${coloredUsername}> ${text}`;
|
||||
logger.debug('Sending message to IRC', ircChannel, text);
|
||||
this.ircClient.say(ircChannel, text);
|
||||
if (text !== '') {
|
||||
// Convert formatting
|
||||
text = formatFromDiscordToIRC(text);
|
||||
patternMap.text = text;
|
||||
|
||||
text = Bot.substitutePattern(this.formatIRCText, patternMap);
|
||||
logger.debug('Sending message to IRC', ircChannel, text);
|
||||
this.ircClient.say(ircChannel, text);
|
||||
}
|
||||
|
||||
if (message.attachments && message.attachments.size) {
|
||||
message.attachments.forEach((a) => {
|
||||
patternMap.attachmentURL = a.url;
|
||||
const urlMessage = Bot.substitutePattern(this.formatURLAttachment, patternMap);
|
||||
|
||||
logger.debug('Sending attachment URL to IRC', ircChannel, urlMessage);
|
||||
this.ircClient.say(ircChannel, urlMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToDiscord(author, channel, text) {
|
||||
const discordChannelName = this.invertedMapping[channel.toLowerCase()];
|
||||
findDiscordChannel(ircChannel) {
|
||||
const discordChannelName = this.invertedMapping[ircChannel.toLowerCase()];
|
||||
if (discordChannelName) {
|
||||
// #channel -> channel before retrieving:
|
||||
const discordChannel = this.discord.channels.get('name', discordChannelName.slice(1));
|
||||
// #channel -> channel before retrieving and select only text channels:
|
||||
let discordChannel = null;
|
||||
|
||||
if (!discordChannel) {
|
||||
logger.info('Tried to send a message to a channel the bot isn\'t in: ',
|
||||
discordChannelName);
|
||||
return;
|
||||
if (this.discord.channels.cache.has(discordChannelName)) {
|
||||
discordChannel = this.discord.channels.cache.get(discordChannelName);
|
||||
} else if (discordChannelName.startsWith('#')) {
|
||||
discordChannel = this.discord.channels.cache
|
||||
.filter(c => c.type === 'text')
|
||||
.find(c => c.name === discordChannelName.slice(1));
|
||||
}
|
||||
|
||||
const withMentions = text.replace(/@[^\s]+\b/g, match => {
|
||||
const user = this.discord.users.get('username', match.substring(1));
|
||||
return user ? user.mention() : match;
|
||||
if (!discordChannel) {
|
||||
logger.info(
|
||||
'Tried to send a message to a channel the bot isn\'t in: ',
|
||||
discordChannelName
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return discordChannel;
|
||||
}
|
||||
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
|
||||
};
|
||||
|
||||
if (this.isCommandMessage(text)) {
|
||||
patternMap.side = 'IRC';
|
||||
logger.debug('Sending command message to Discord', `#${discordChannel.name}`, text);
|
||||
if (this.formatCommandPrelude) {
|
||||
const prelude = Bot.substitutePattern(this.formatCommandPrelude, patternMap);
|
||||
discordChannel.send(prelude);
|
||||
}
|
||||
discordChannel.send(text);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = discordChannel;
|
||||
const withMentions = withFormat.replace(/@([^\s#]+)#(\d+)/g, (match, username, discriminator) => {
|
||||
// @username#1234 => mention
|
||||
// skips usernames including spaces for ease (they cannot include hashes)
|
||||
// checks case insensitively as Discord does
|
||||
const user = guild.members.cache.find(x =>
|
||||
Bot.caseComp(x.user.username, username)
|
||||
&& x.user.discriminator === discriminator);
|
||||
if (user) return user;
|
||||
|
||||
return match;
|
||||
}).replace(/^([^@\s:,]+)[:,]|@([^\s]+)/g, (match, startRef, atRef) => {
|
||||
const reference = startRef || atRef;
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// Add bold formatting:
|
||||
const withAuthor = `**<${author}>** ${withMentions}`;
|
||||
logger.debug('Sending message to Discord', withAuthor, channel, '->', discordChannelName);
|
||||
this.discord.sendMessage(discordChannel, withAuthor);
|
||||
// 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) => {
|
||||
// :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:
|
||||
// Use custom formatting from config / default formatting with bold author
|
||||
const withAuthor = Bot.substitutePattern(this.formatDiscord, patternMap);
|
||||
logger.debug('Sending message to Discord', withAuthor, channel, '->', `#${discordChannel.name}`);
|
||||
discordChannel.send(withAuthor);
|
||||
}
|
||||
|
||||
/* Sends a message to Discord exactly as it appears */
|
||||
sendExactToDiscord(channel, text) {
|
||||
const discordChannel = this.findDiscordChannel(channel);
|
||||
if (!discordChannel) return;
|
||||
|
||||
logger.debug('Sending special message to Discord', text, channel, '->', `#${discordChannel.name}`);
|
||||
discordChannel.send(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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') ?
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import ircFormatting from 'irc-formatting';
|
||||
import SimpleMarkdown from 'simple-markdown';
|
||||
import colors from 'irc-colors';
|
||||
|
||||
function mdNodeToIRC(node) {
|
||||
let { content } = node;
|
||||
if (Array.isArray(content)) content = content.map(mdNodeToIRC).join('');
|
||||
switch (node.type) {
|
||||
case 'em':
|
||||
return colors.italic(content);
|
||||
case 'strong':
|
||||
return colors.bold(content);
|
||||
case 'u':
|
||||
return colors.underline(content);
|
||||
default:
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatFromDiscordToIRC(text) {
|
||||
const markdownAST = SimpleMarkdown.defaultInlineParse(text);
|
||||
return markdownAST.map(mdNodeToIRC).join('');
|
||||
}
|
||||
|
||||
export function formatFromIRCToDiscord(text) {
|
||||
const blocks = ircFormatting.parse(text).map(block => ({
|
||||
// Consider reverse as italic, some IRC clients use that
|
||||
...block,
|
||||
italic: block.italic || block.reverse
|
||||
}));
|
||||
let mdText = '';
|
||||
|
||||
for (let i = 0; i <= blocks.length; i += 1) {
|
||||
// Default to unstyled blocks when index out of range
|
||||
const block = blocks[i] || {};
|
||||
const prevBlock = blocks[i - 1] || {};
|
||||
|
||||
// Add start markers when style turns from false to true
|
||||
if (!prevBlock.italic && block.italic) mdText += '*';
|
||||
if (!prevBlock.bold && block.bold) mdText += '**';
|
||||
if (!prevBlock.underline && block.underline) mdText += '__';
|
||||
|
||||
// Add end markers when style turns from true to false
|
||||
// (and apply in reverse order to maintain nesting)
|
||||
if (prevBlock.underline && !block.underline) mdText += '__';
|
||||
if (prevBlock.bold && !block.bold) mdText += '**';
|
||||
if (prevBlock.italic && !block.italic) mdText += '*';
|
||||
|
||||
mdText += block.text || '';
|
||||
}
|
||||
|
||||
return mdText;
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ export function createBots(configFile) {
|
||||
|
||||
// The config file can be both an array and an object
|
||||
if (Array.isArray(configFile)) {
|
||||
configFile.forEach(config => {
|
||||
configFile.forEach((config) => {
|
||||
const bot = new Bot(config);
|
||||
bot.connect();
|
||||
bots.push(bot);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createBots } from './helpers';
|
||||
import logger from 'winston';
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
logger.level = 'debug';
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
|
||||
@@ -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
+6644
File diff suppressed because it is too large
Load Diff
+31
-24
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discord-irc",
|
||||
"version": "0.6.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": ">=0.12.7"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"bin": "dist/index.js",
|
||||
@@ -25,9 +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 -- $(find test -name '*.test.js') && nyc report --reporter=cobertura",
|
||||
"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": {
|
||||
@@ -35,27 +36,33 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"check-env": "1.2.0",
|
||||
"commander": "2.9.0",
|
||||
"discord.js": "6.1.0",
|
||||
"irc": "0.5.0",
|
||||
"lodash": "4.7.0",
|
||||
"strip-json-comments": "2.0.1",
|
||||
"winston": "2.2.0"
|
||||
"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": "^6.0.2",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"chai": "3.5.0",
|
||||
"eslint": "^2.6.0",
|
||||
"eslint-config-airbnb": "^6.2.0",
|
||||
"eslint-config-webkom": "^1.3.4",
|
||||
"mocha": "2.4.5",
|
||||
"nyc": "^6.1.1",
|
||||
"sinon": "1.17.3",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
+260
-21
@@ -2,47 +2,66 @@
|
||||
import chai from 'chai';
|
||||
import sinonChai from 'sinon-chai';
|
||||
import sinon from 'sinon';
|
||||
import irc from 'irc';
|
||||
import irc from 'irc-upd';
|
||||
import discord from 'discord.js';
|
||||
import logger from 'winston';
|
||||
import Bot from '../lib/bot';
|
||||
import DiscordStub from './stubs/discord-stub';
|
||||
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';
|
||||
|
||||
chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('Bot Events', function() {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
describe('Bot Events', function () {
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
const createBot = (optConfig = null) => {
|
||||
const useConfig = optConfig || config;
|
||||
const bot = new Bot(useConfig);
|
||||
bot.sendToIRC = sandbox.stub();
|
||||
bot.sendToDiscord = sandbox.stub();
|
||||
bot.sendExactToDiscord = sandbox.stub();
|
||||
return bot;
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
this.infoSpy = sandbox.stub(logger, 'info');
|
||||
this.debugSpy = sandbox.stub(logger, 'debug');
|
||||
this.warnSpy = sandbox.stub(logger, 'warn');
|
||||
this.errorSpy = sandbox.stub(logger, 'error');
|
||||
this.sendStub = sandbox.stub();
|
||||
irc.Client = ClientStub;
|
||||
discord.Client = DiscordStub;
|
||||
discord.Client = createDiscordStub(this.sendStub);
|
||||
discord.WebhookClient = createWebhookStub(this.sendStub);
|
||||
ClientStub.prototype.send = sandbox.stub();
|
||||
ClientStub.prototype.join = sandbox.stub();
|
||||
this.bot = new Bot(config);
|
||||
this.bot.sendToIRC = sandbox.stub();
|
||||
this.bot.sendToDiscord = sandbox.stub();
|
||||
this.bot = createBot();
|
||||
this.bot.connect();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
this.bot.disconnect();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should log on discord ready event', function() {
|
||||
it('should log on discord ready event', function () {
|
||||
this.bot.discord.emit('ready');
|
||||
this.debugSpy.should.have.been.calledWithExactly('Connected to Discord');
|
||||
this.infoSpy.should.have.been.calledWithExactly('Connected to Discord');
|
||||
});
|
||||
|
||||
it('should try to send autoSendCommands on registered IRC event', function() {
|
||||
it('should log on irc registered event', function () {
|
||||
const message = 'registered';
|
||||
this.bot.ircClient.emit('registered', message);
|
||||
this.infoSpy.should.have.been.calledWithExactly('Connected to IRC');
|
||||
this.debugSpy.should.have.been.calledWithExactly('Registered event: ', message);
|
||||
});
|
||||
|
||||
it('should try to send autoSendCommands on registered IRC event', function () {
|
||||
this.bot.ircClient.emit('registered');
|
||||
ClientStub.prototype.send.should.have.been.calledTwice;
|
||||
ClientStub.prototype.send.getCall(0)
|
||||
@@ -51,7 +70,7 @@ describe('Bot Events', function() {
|
||||
.args.should.deep.equal(config.autoSendCommands[1]);
|
||||
});
|
||||
|
||||
it('should error log on error events', function() {
|
||||
it('should error log on error events', function () {
|
||||
const discordError = new Error('discord');
|
||||
const ircError = new Error('irc');
|
||||
this.bot.discord.emit('error', discordError);
|
||||
@@ -62,7 +81,15 @@ describe('Bot Events', function() {
|
||||
this.errorSpy.getCall(1).args[1].should.equal(ircError);
|
||||
});
|
||||
|
||||
it('should send messages to irc if correct', function() {
|
||||
it('should warn log on warn events from discord', function () {
|
||||
const discordError = new Error('discord');
|
||||
this.bot.discord.emit('warn', discordError);
|
||||
const [message, error] = this.warnSpy.firstCall.args;
|
||||
message.should.equal('Received warn event from Discord');
|
||||
error.should.equal(discordError);
|
||||
});
|
||||
|
||||
it('should send messages to irc if correct', function () {
|
||||
const message = {
|
||||
type: 'message'
|
||||
};
|
||||
@@ -71,7 +98,7 @@ describe('Bot Events', function() {
|
||||
this.bot.sendToIRC.should.have.been.calledWithExactly(message);
|
||||
});
|
||||
|
||||
it('should send messages to discord', function() {
|
||||
it('should send messages to discord', function () {
|
||||
const channel = '#channel';
|
||||
const author = 'user';
|
||||
const text = 'hi';
|
||||
@@ -79,7 +106,7 @@ describe('Bot Events', function() {
|
||||
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, text);
|
||||
});
|
||||
|
||||
it('should send notices to discord', function() {
|
||||
it('should send notices to discord', function () {
|
||||
const channel = '#channel';
|
||||
const author = 'user';
|
||||
const text = 'hi';
|
||||
@@ -88,7 +115,36 @@ describe('Bot Events', function() {
|
||||
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, formattedText);
|
||||
});
|
||||
|
||||
it('should send actions to discord', function() {
|
||||
it('should not send name change event to discord', function () {
|
||||
const channel = '#channel';
|
||||
const oldnick = 'user1';
|
||||
const newnick = 'user2';
|
||||
this.bot.ircClient.emit('nick', oldnick, newnick, [channel]);
|
||||
this.bot.sendExactToDiscord.should.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should send name change event to discord', function () {
|
||||
const channel1 = '#channel1';
|
||||
const channel2 = '#channel2';
|
||||
const channel3 = '#channel3';
|
||||
const oldNick = 'user1';
|
||||
const newNick = 'user2';
|
||||
const user3 = 'user3';
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
const staticChannel = new Set([bot.nickname, user3]);
|
||||
bot.connect();
|
||||
bot.ircClient.emit('names', channel1, { [bot.nickname]: '', [oldNick]: '' });
|
||||
bot.ircClient.emit('names', channel2, { [bot.nickname]: '', [user3]: '' });
|
||||
const channelNicksPre = new Set([bot.nickname, oldNick]);
|
||||
bot.channelUsers.should.deep.equal({ '#channel1': channelNicksPre, '#channel2': staticChannel });
|
||||
const formattedText = `*${oldNick}* is now known as ${newNick}`;
|
||||
const channelNicksAfter = new Set([bot.nickname, newNick]);
|
||||
bot.ircClient.emit('nick', oldNick, newNick, [channel1, channel2, channel3]);
|
||||
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel1, formattedText);
|
||||
bot.channelUsers.should.deep.equal({ '#channel1': channelNicksAfter, '#channel2': staticChannel });
|
||||
});
|
||||
|
||||
it('should send actions to discord', function () {
|
||||
const channel = '#channel';
|
||||
const author = 'user';
|
||||
const text = 'hi';
|
||||
@@ -98,7 +154,190 @@ describe('Bot Events', function() {
|
||||
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, formattedText);
|
||||
});
|
||||
|
||||
it('should join channels when invited', function() {
|
||||
it('should keep track of users through names event when irc status notices enabled', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
bot.channelUsers.should.be.an('object');
|
||||
const channel = '#channel';
|
||||
// nick => '' means the user is not a special user
|
||||
const nicks = {
|
||||
[bot.nickname]: '', user: '', user2: '@', user3: '+'
|
||||
};
|
||||
bot.ircClient.emit('names', channel, nicks);
|
||||
const channelNicks = new Set([bot.nickname, 'user', 'user2', 'user3']);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
|
||||
});
|
||||
|
||||
it('should lowercase the channelUsers mapping', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channelName';
|
||||
const nicks = { [bot.nickname]: '' };
|
||||
bot.ircClient.emit('names', channel, nicks);
|
||||
const channelNicks = new Set([bot.nickname]);
|
||||
bot.channelUsers.should.deep.equal({ '#channelname': channelNicks });
|
||||
});
|
||||
|
||||
it('should send join messages to discord when config enabled', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
|
||||
const nick = 'user';
|
||||
const text = `*${nick}* has joined the channel`;
|
||||
bot.ircClient.emit('join', channel, nick);
|
||||
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
|
||||
const channelNicks = new Set([bot.nickname, nick]);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
|
||||
});
|
||||
|
||||
it('should not announce itself joining by default', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
|
||||
const nick = bot.nickname;
|
||||
bot.ircClient.emit('join', channel, nick);
|
||||
bot.sendExactToDiscord.should.not.have.been.called;
|
||||
const channelNicks = new Set([bot.nickname]);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
|
||||
});
|
||||
|
||||
it('should announce the bot itself when config enabled', function () {
|
||||
// self-join is announced before names (which includes own nick)
|
||||
// hence don't trigger a names and don't expect anything of bot.channelUsers
|
||||
const bot = createBot({ ...config, ircStatusNotices: true, announceSelfJoin: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
const nick = this.bot.nickname;
|
||||
const text = `*${nick}* has joined the channel`;
|
||||
bot.ircClient.emit('join', channel, nick);
|
||||
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
|
||||
});
|
||||
|
||||
it('should send part messages to discord when config enabled', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
const nick = 'user';
|
||||
bot.ircClient.emit('names', channel, { [bot.nickname]: '', [nick]: '' });
|
||||
const originalNicks = new Set([bot.nickname, nick]);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': originalNicks });
|
||||
const reason = 'Leaving';
|
||||
const text = `*${nick}* has left the channel (${reason})`;
|
||||
bot.ircClient.emit('part', channel, nick, reason);
|
||||
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
|
||||
// it should remove the nickname from the channelUsers list
|
||||
const channelNicks = new Set([bot.nickname]);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
|
||||
});
|
||||
|
||||
it('should not announce itself leaving a channel', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
bot.ircClient.emit('names', channel, { [bot.nickname]: '', user: '' });
|
||||
const originalNicks = new Set([bot.nickname, 'user']);
|
||||
bot.channelUsers.should.deep.equal({ '#channel': originalNicks });
|
||||
const reason = 'Leaving';
|
||||
bot.ircClient.emit('part', channel, bot.nickname, reason);
|
||||
bot.sendExactToDiscord.should.not.have.been.called;
|
||||
// it should remove the nickname from the channelUsers list
|
||||
bot.channelUsers.should.deep.equal({});
|
||||
});
|
||||
|
||||
it('should only send quit messages to discord for channels the user is tracked in', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel1 = '#channel1';
|
||||
const channel2 = '#channel2';
|
||||
const channel3 = '#channel3';
|
||||
const nick = 'user';
|
||||
bot.ircClient.emit('names', channel1, { [bot.nickname]: '', [nick]: '' });
|
||||
bot.ircClient.emit('names', channel2, { [bot.nickname]: '' });
|
||||
bot.ircClient.emit('names', channel3, { [bot.nickname]: '', [nick]: '' });
|
||||
const reason = 'Quit: Leaving';
|
||||
const text = `*${nick}* has quit (${reason})`;
|
||||
// send quit message for all channels on server, as the node-irc library does
|
||||
bot.ircClient.emit('quit', nick, reason, [channel1, channel2, channel3]);
|
||||
bot.sendExactToDiscord.should.have.been.calledTwice;
|
||||
bot.sendExactToDiscord.getCall(0).args.should.deep.equal([channel1, text]);
|
||||
bot.sendExactToDiscord.getCall(1).args.should.deep.equal([channel3, text]);
|
||||
});
|
||||
|
||||
it('should not crash with join/part/quit messages and weird channel casing', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
|
||||
function wrap() {
|
||||
const nick = 'user';
|
||||
const reason = 'Leaving';
|
||||
bot.ircClient.emit('names', '#Channel', { [bot.nickname]: '' });
|
||||
bot.ircClient.emit('join', '#cHannel', nick);
|
||||
bot.ircClient.emit('part', '#chAnnel', nick, reason);
|
||||
bot.ircClient.emit('join', '#chaNnel', nick);
|
||||
bot.ircClient.emit('quit', nick, reason, ['#chanNel']);
|
||||
}
|
||||
(wrap).should.not.throw();
|
||||
});
|
||||
|
||||
it('should be possible to disable join/part/quit messages', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: false });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
const nick = 'user';
|
||||
const reason = 'Leaving';
|
||||
|
||||
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
|
||||
bot.ircClient.emit('join', channel, nick);
|
||||
bot.ircClient.emit('part', channel, nick, reason);
|
||||
bot.ircClient.emit('join', channel, nick);
|
||||
bot.ircClient.emit('quit', nick, reason, [channel]);
|
||||
bot.sendExactToDiscord.should.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should warn if it receives a part/quit before a names event', function () {
|
||||
const bot = createBot({ ...config, ircStatusNotices: true });
|
||||
bot.connect();
|
||||
const channel = '#channel';
|
||||
const reason = 'Leaving';
|
||||
|
||||
bot.ircClient.emit('part', channel, 'user1', reason);
|
||||
bot.ircClient.emit('quit', 'user2', reason, [channel]);
|
||||
this.warnSpy.should.have.been.calledTwice;
|
||||
this.warnSpy.getCall(0).args.should.deep.equal([`No channelUsers found for ${channel} when user1 parted.`]);
|
||||
this.warnSpy.getCall(1).args.should.deep.equal([`No channelUsers found for ${channel} when user2 quit, ignoring.`]);
|
||||
});
|
||||
|
||||
it('should not crash if it uses a different name from config', function () {
|
||||
// this can happen when a user with the same name is already connected
|
||||
const bot = createBot({ ...config, nickname: 'testbot' });
|
||||
bot.connect();
|
||||
const newName = 'testbot1';
|
||||
bot.ircClient.nick = newName;
|
||||
function wrap() {
|
||||
bot.ircClient.emit('join', '#channel', newName);
|
||||
}
|
||||
(wrap).should.not.throw;
|
||||
});
|
||||
|
||||
it('should not listen to discord debug messages in production', function () {
|
||||
logger.level = 'info';
|
||||
const bot = createBot();
|
||||
bot.connect();
|
||||
const listeners = bot.discord.listeners('debug');
|
||||
listeners.length.should.equal(0);
|
||||
});
|
||||
|
||||
it('should listen to discord debug messages in development', function () {
|
||||
logger.level = 'debug';
|
||||
const bot = createBot();
|
||||
bot.connect();
|
||||
const listeners = bot.discord.listeners('debug');
|
||||
listeners.length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should join channels when invited', function () {
|
||||
const channel = '#irc';
|
||||
const author = 'user';
|
||||
this.bot.ircClient.emit('invite', channel, author);
|
||||
@@ -113,7 +352,7 @@ describe('Bot Events', function() {
|
||||
secondCall.args[1].should.equal(channel);
|
||||
});
|
||||
|
||||
it('should not join channels that aren\'t in the channel mapping', function() {
|
||||
it('should not join channels that aren\'t in the channel mapping', function () {
|
||||
const channel = '#wrong';
|
||||
const author = 'user';
|
||||
this.bot.ircClient.emit('invite', channel, author);
|
||||
|
||||
+1099
-117
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import chai from 'chai';
|
||||
import irc from 'irc';
|
||||
import irc from 'irc-upd';
|
||||
import discord from 'discord.js';
|
||||
import Bot from '../lib/bot';
|
||||
import config from './fixtures/single-test-config.json';
|
||||
@@ -38,7 +38,7 @@ describe('Channel Mapping', () => {
|
||||
const bot = new Bot(config);
|
||||
bot.channelMapping['#discord'].should.equal('#irc');
|
||||
bot.invertedMapping['#irc'].should.equal('#discord');
|
||||
bot.channels[0].should.equal('#irc channelKey');
|
||||
bot.channels.should.contain('#irc channelKey');
|
||||
});
|
||||
|
||||
it('should lowercase IRC channel names', () => {
|
||||
@@ -46,4 +46,10 @@ describe('Channel Mapping', () => {
|
||||
bot.channelMapping['#discord'].should.equal('#irc');
|
||||
bot.channelMapping['#otherDiscord'].should.equal('#otherirc');
|
||||
});
|
||||
|
||||
it('should work with ID maps', () => {
|
||||
const bot = new Bot(config);
|
||||
bot.channelMapping['1234'].should.equal('#channelforid');
|
||||
bot.invertedMapping['#channelforid'].should.equal('1234');
|
||||
});
|
||||
});
|
||||
|
||||
+9
-9
@@ -10,49 +10,49 @@ import singleTestConfig from './fixtures/single-test-config.json';
|
||||
chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('CLI', function() {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
describe('CLI', function () {
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
this.createBotsStub = sandbox.stub(helpers, 'createBots');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should be possible to give the config as an env var', function() {
|
||||
it('should be possible to give the config as an env var', function () {
|
||||
process.env.CONFIG_FILE = `${process.cwd()}/test/fixtures/test-config.json`;
|
||||
process.argv = ['node', 'index.js'];
|
||||
cli();
|
||||
this.createBotsStub.should.have.been.calledWith(testConfig);
|
||||
});
|
||||
|
||||
it('should strip comments from JSON config', function() {
|
||||
it('should strip comments from JSON config', function () {
|
||||
process.env.CONFIG_FILE = `${process.cwd()}/test/fixtures/test-config-comments.json`;
|
||||
process.argv = ['node', 'index.js'];
|
||||
cli();
|
||||
this.createBotsStub.should.have.been.calledWith(testConfig);
|
||||
});
|
||||
|
||||
it('should support JS configs', function() {
|
||||
it('should support JS configs', function () {
|
||||
process.env.CONFIG_FILE = `${process.cwd()}/test/fixtures/test-javascript-config.js`;
|
||||
process.argv = ['node', 'index.js'];
|
||||
cli();
|
||||
this.createBotsStub.should.have.been.calledWith(testConfig);
|
||||
});
|
||||
|
||||
it('should throw a ConfigurationError for invalid JSON', function() {
|
||||
it('should throw a ConfigurationError for invalid JSON', function () {
|
||||
process.env.CONFIG_FILE = `${process.cwd()}/test/fixtures/invalid-json-config.json`;
|
||||
process.argv = ['node', 'index.js'];
|
||||
const wrap = () => cli();
|
||||
(wrap).should.throw('The configuration file contains invalid JSON');
|
||||
});
|
||||
|
||||
it('should be possible to give the config as an option', function() {
|
||||
it('should be possible to give the config as an option', function () {
|
||||
delete process.env.CONFIG_FILE;
|
||||
process.argv = [
|
||||
'node',
|
||||
|
||||
@@ -13,33 +13,33 @@ import { createBots } from '../lib/helpers';
|
||||
chai.should();
|
||||
chai.use(sinonChai);
|
||||
|
||||
describe('Create Bots', function() {
|
||||
const sandbox = sinon.sandbox.create({
|
||||
describe('Create Bots', function () {
|
||||
const sandbox = sinon.createSandbox({
|
||||
useFakeTimers: false,
|
||||
useFakeServer: false
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
this.connectStub = sandbox.stub(Bot.prototype, 'connect');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should work when given an array of configs', function() {
|
||||
it('should work when given an array of configs', function () {
|
||||
const bots = createBots(testConfig);
|
||||
bots.length.should.equal(2);
|
||||
this.connectStub.should.have.been.called;
|
||||
});
|
||||
|
||||
it('should work when given an object as a config file', function() {
|
||||
it('should work when given an object as a config file', function () {
|
||||
const bots = createBots(singleTestConfig);
|
||||
bots.length.should.equal(1);
|
||||
this.connectStub.should.have.been.called;
|
||||
});
|
||||
|
||||
it('should throw a configuration error if any fields are missing', function() {
|
||||
it('should throw a configuration error if any fields are missing', function () {
|
||||
function wrap() {
|
||||
createBots(badConfig);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ describe('Create Bots', function() {
|
||||
(wrap).should.throw('Missing configuration field nickname');
|
||||
});
|
||||
|
||||
it('should throw if a configuration file is neither an object or an array', function() {
|
||||
it('should throw if a configuration file is neither an object or an array', function () {
|
||||
function wrap() {
|
||||
createBots(stringConfig);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ describe('Create Bots', function() {
|
||||
(wrap).should.throw('Invalid configuration file given');
|
||||
});
|
||||
|
||||
it('should be possible to run it through require(\'discord-irc\')', function() {
|
||||
it('should be possible to run it through require(\'discord-irc\')', function () {
|
||||
const bots = index(singleTestConfig);
|
||||
bots.length.should.equal(1);
|
||||
this.connectStub.should.have.been.called;
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"server": "irc.bottest.org",
|
||||
"token": "hei",
|
||||
"discordToken": "hei",
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"nickname": "Reactiflux",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": "whatapassword",
|
||||
"discordToken": "discord@test.com",
|
||||
"commandCharacters": ["!", "."],
|
||||
"autoSendCommands": [
|
||||
["MODE", "test", "+x"],
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"nickname": "Reactiflux",
|
||||
"server": "irc.freenode.net",
|
||||
"discordToken": "whatapassword",
|
||||
"commandCharacters": ["!", "."],
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
}
|
||||
+15
-3
@@ -1,15 +1,27 @@
|
||||
{
|
||||
"nickname": "Reactiflux",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": "whatapassword",
|
||||
"discordToken": "whatapassword",
|
||||
"commandCharacters": ["!", "."],
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"autoSendCommands": [
|
||||
["MODE", "test", "+x"],
|
||||
["AUTH", "test", "password"]
|
||||
],
|
||||
"channelMapping": {
|
||||
"#discord": "#irc channelKey",
|
||||
"#notinchannel": "#otherIRC"
|
||||
"#notinchannel": "#otherIRC",
|
||||
"1234": "#channelforid",
|
||||
"#withwebhook": "#ircwebhook"
|
||||
},
|
||||
"webhooks": {
|
||||
"#withwebhook": "https://discord.com/api/webhooks/id/token"
|
||||
},
|
||||
"ignoreUsers": {
|
||||
"irc": ["irc_ignored_user"],
|
||||
"discord": ["discord_ignored_user"],
|
||||
"discordIds": ["4499"]
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -2,8 +2,10 @@
|
||||
{
|
||||
"nickname": "test",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": /* comment */ "whatapassword",
|
||||
"discordToken": /* comment */ "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc" // Comment
|
||||
}
|
||||
@@ -12,8 +14,10 @@
|
||||
{
|
||||
"nickname": "test2",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": "whatapassword",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
|
||||
Vendored
+8
-4
@@ -2,8 +2,10 @@
|
||||
{
|
||||
"nickname": "test",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": "whatapassword",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
@@ -11,8 +13,10 @@
|
||||
{
|
||||
"nickname": "test2",
|
||||
"server": "irc.freenode.net",
|
||||
"discordEmail": "discord@test.com",
|
||||
"discordPassword": "whatapassword",
|
||||
"discordToken": "whatapassword",
|
||||
"ircOptions": {
|
||||
"encoding": "utf-8"
|
||||
},
|
||||
"channelMapping": {
|
||||
"#discord": "#irc"
|
||||
}
|
||||
|
||||
+8
-4
@@ -2,8 +2,10 @@ module.exports = [
|
||||
{
|
||||
nickname: 'test',
|
||||
server: 'irc.freenode.net',
|
||||
discordEmail: 'discord@test.com',
|
||||
discordPassword: 'whatapassword',
|
||||
discordToken: 'whatapassword',
|
||||
ircOptions: {
|
||||
encoding: 'utf-8'
|
||||
},
|
||||
channelMapping: {
|
||||
'#discord': '#irc'
|
||||
}
|
||||
@@ -11,8 +13,10 @@ module.exports = [
|
||||
{
|
||||
nickname: 'test2',
|
||||
server: 'irc.freenode.net',
|
||||
discordEmail: 'discord@test.com',
|
||||
discordPassword: 'whatapassword',
|
||||
discordToken: 'whatapassword',
|
||||
ircOptions: {
|
||||
encoding: 'utf-8'
|
||||
},
|
||||
channelMapping: {
|
||||
'#discord': '#irc'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
import chai from 'chai';
|
||||
import { formatFromDiscordToIRC, formatFromIRCToDiscord } from '../lib/formatting';
|
||||
|
||||
chai.should();
|
||||
|
||||
describe('Formatting', () => {
|
||||
describe('Discord to IRC', () => {
|
||||
it('should convert bold markdown', () => {
|
||||
formatFromDiscordToIRC('**text**').should.equal('\x02text\x02');
|
||||
});
|
||||
|
||||
it('should convert italic markdown', () => {
|
||||
formatFromDiscordToIRC('*text*').should.equal('\x1dtext\x1d');
|
||||
formatFromDiscordToIRC('_text_').should.equal('\x1dtext\x1d');
|
||||
});
|
||||
|
||||
it('should convert underline markdown', () => {
|
||||
formatFromDiscordToIRC('__text__').should.equal('\x1ftext\x1f');
|
||||
});
|
||||
|
||||
it('should ignore strikethrough markdown', () => {
|
||||
formatFromDiscordToIRC('~~text~~').should.equal('text');
|
||||
});
|
||||
|
||||
it('should convert nested markdown', () => {
|
||||
formatFromDiscordToIRC('**bold *italics***')
|
||||
.should.equal('\x02bold \x1ditalics\x1d\x02');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IRC to Discord', () => {
|
||||
it('should convert bold IRC format', () => {
|
||||
formatFromIRCToDiscord('\x02text\x02').should.equal('**text**');
|
||||
});
|
||||
|
||||
it('should convert reverse IRC format', () => {
|
||||
formatFromIRCToDiscord('\x16text\x16').should.equal('*text*');
|
||||
});
|
||||
|
||||
it('should convert italic IRC format', () => {
|
||||
formatFromIRCToDiscord('\x1dtext\x1d').should.equal('*text*');
|
||||
});
|
||||
|
||||
it('should convert underline IRC format', () => {
|
||||
formatFromIRCToDiscord('\x1ftext\x1f').should.equal('__text__');
|
||||
});
|
||||
|
||||
it('should ignore color IRC format', () => {
|
||||
formatFromIRCToDiscord('\x0306,08text\x03').should.equal('text');
|
||||
});
|
||||
|
||||
it('should convert nested IRC format', () => {
|
||||
formatFromIRCToDiscord('\x02bold \x16italics\x16\x02')
|
||||
.should.equal('**bold *italics***');
|
||||
});
|
||||
|
||||
it('should convert nested IRC format', () => {
|
||||
formatFromIRCToDiscord('\x02bold \x1funderline\x1f\x02')
|
||||
.should.equal('**bold __underline__**');
|
||||
});
|
||||
});
|
||||
});
|
||||
+76
-24
@@ -1,29 +1,81 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import events from 'events';
|
||||
import sinon from 'sinon';
|
||||
import discord from 'discord.js';
|
||||
|
||||
export function getChannel(key, value) {
|
||||
if (key === 'name' && value !== 'discord') return null;
|
||||
return {
|
||||
name: 'discord',
|
||||
id: 1234
|
||||
export default function createDiscordStub(sendStub) {
|
||||
return class DiscordStub extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.user = {
|
||||
id: 'testid'
|
||||
};
|
||||
this.channels = new discord.ChannelManager(this, []);
|
||||
this.options = {
|
||||
http: {
|
||||
cdn: ''
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
return sinon.stub();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class DiscordStub extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.user = {
|
||||
id: 'testid'
|
||||
};
|
||||
|
||||
this.channels = {
|
||||
get: getChannel
|
||||
};
|
||||
}
|
||||
|
||||
login() {
|
||||
return sinon.stub();
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscordStub;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import events from 'events';
|
||||
|
||||
class ClientStub extends events.EventEmitter {}
|
||||
/* eslint-disable class-methods-use-this */
|
||||
class ClientStub extends events.EventEmitter {
|
||||
constructor(...args) {
|
||||
super();
|
||||
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