diff --git a/package-lock.json b/package-lock.json index c3c73ec5a86ec1dd109bfca82f5dca35ab4066bc..8990ae98a097da0b5bd3cd745519e4ff5d9382b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-discord", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -180,11 +180,6 @@ "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==", "dev": true }, - "@types/p-queue": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/p-queue/-/p-queue-3.0.0.tgz", - "integrity": "sha512-brMFTEZiltJnAxNy07LzRVw2bdQX1ElCElHEmo5WEI70oWQe00aXew9RKmpf8MOzBMiMfNeuQoEyQCWKawPzmQ==" - }, "@types/sqlite3": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/sqlite3/-/sqlite3-3.1.3.tgz", @@ -1014,15 +1009,15 @@ } }, "discord.js": { - "version": "11.4.2", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.4.2.tgz", - "integrity": "sha512-MDwpu0lMFTjqomijDl1Ed9miMQe6kB4ifKdP28QZllmLv/HVOJXhatRgjS8urp/wBlOfx+qAYSXcdI5cKGYsfg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.5.0.tgz", + "integrity": "sha512-7TAyr2p1ZP9k4gaIhQWOxZpybznT6ND55HbhntUqwQqXkAjIxU3ATbwiOSuCMd4AXz7L9wk1rioRL0sOjXs5CA==", "requires": { "long": "^4.0.0", "prism-media": "^0.0.3", "snekfetch": "^3.6.4", "tweetnacl": "^1.0.0", - "ws": "^4.0.0" + "ws": "^6.0.0" } }, "doctrine": { @@ -1353,6 +1348,11 @@ "es5-ext": "~0.10.14" } }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -3054,9 +3054,12 @@ } }, "p-queue": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-3.0.0.tgz", - "integrity": "sha512-2tv/MRmPXfmfnjLLJAHl+DdU8p2DhZafAnlpm/C/T5BpF5L9wKz5tMin4A4N2zVpJL2YMhPlRmtO7s5EtNrjfA==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-5.0.0.tgz", + "integrity": "sha512-6QfeouDf236N+MAxHch0CVIy8o/KBnmhttKjxZoOkUlzqU+u9rZgEyXH3OdckhTgawbqf5rpzmyR+07+Lv0+zg==", + "requires": { + "eventemitter3": "^3.1.0" + } }, "p-try": { "version": "2.2.0", @@ -4141,9 +4144,9 @@ } }, "tweetnacl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz", - "integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz", + "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==" }, "type-check": { "version": "0.3.2", @@ -4452,12 +4455,11 @@ } }, "ws": { - "version": "4.1.0", - "resolved": "http://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" + "async-limiter": "~1.0.0" } }, "xtend": { diff --git a/package.json b/package.json index d0addede45c4c2aa5eba11d3119dbc0b10eb905d..4e19eaafab2e26daad71163d7d7bdf4ea1839f68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-discord", - "version": "0.4.0", + "version": "0.5.0-rc2", "description": "A bridge between Matrix and Discord", "main": "discordas.js", "scripts": { @@ -34,20 +34,18 @@ }, "homepage": "https://github.com/Half-Shot/matrix-appservice-discord#readme", "dependencies": { - "@types/p-queue": "^3.0.0", "better-sqlite3": "^5.0.1", "command-line-args": "^4.0.1", "command-line-usage": "^4.1.0", "discord-markdown": "^2.0.0", - "discord.js": "^11.4.2", + "discord.js": "^11.5.0", "escape-html": "^1.0.3", "escape-string-regexp": "^1.0.5", "js-yaml": "^3.13.1", "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#8a7288edf1d1d1d1395a83d330d836d9c9bf1e76", "mime": "^1.6.0", - "moment": "^2.22.2", "node-html-parser": "^1.1.11", - "p-queue": "^3.0.0", + "p-queue": "^5.0.0", "pg-promise": "^8.5.1", "tslint": "^5.11.0", "typescript": "^3.1.3", diff --git a/src/bot.ts b/src/bot.ts index ab5abffae88099bacdb8612e3675823c964def2e..2c7ec056e84340912f46988dd067875841472b48 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -81,8 +81,8 @@ export class DiscordBot { /* Handles messages queued up to be sent to matrix from discord. */ private discordMessageQueue: { [channelId: string]: Promise<void> }; - private channelLocks: { [channelId: string]: {p: Promise<{}>, i: NodeJS.Timeout|null} }; - + private channelLocks: Map<string, {i: NodeJS.Timeout|null, r: (() => void)|null}>; + private channelLockPromises: Map<string, Promise<{}>>; constructor( private botUserId: string, private config: DiscordBridgeConfig, @@ -106,7 +106,8 @@ export class DiscordBot { // init vars this.sentMessages = []; this.discordMessageQueue = {}; - this.channelLocks = {}; + this.channelLocks = new Map(); + this.channelLockPromises = new Map(); this.lastEventIds = {}; } @@ -139,34 +140,39 @@ export class DiscordBot { } public lockChannel(channel: Discord.Channel) { - if (this.channelLocks[channel.id]) { + if (this.channelLocks.has(channel.id)) { return; } + this.channelLocks[channel.id] = {i: null, r: null, p: null}; const p = new Promise((resolve) => { - if (!this.channelLocks[channel.id]) { - resolve(); - return; - } - const i = setInterval(resolve, this.config.limits.discordSendDelay); - this.channelLocks[channel.id].i = i; + const i = setTimeout(() => { + log.warn(`Lock on channel ${channel.id} expired. Discord is lagging behind?`); + this.unlockChannel(channel); + }, this.config.limits.discordSendDelay); + const o = Object.assign({r: resolve, i, p: null}, this.channelLocks.get(channel.id) || {}); + this.channelLocks.set(channel.id, o); }); - - this.channelLocks[channel.id] = {i: null, p}; + this.channelLockPromises.set(channel.id, p); } public unlockChannel(channel: Discord.Channel) { - const lock = this.channelLocks[channel.id]; - if (lock && lock.i !== null) { + if (!this.channelLocks.has(channel.id)) { + return; + } + const lock = this.channelLocks.get(channel.id)!; + if (lock.i !== null) { + lock.r!(); clearTimeout(lock.i); } - delete this.channelLocks[channel.id]; + this.channelLocks.delete(channel.id); + this.channelLockPromises.delete(channel.id); } public async waitUnlock(channel: Discord.Channel) { - const lock = this.channelLocks[channel.id]; - if (lock) { - await lock.p; + const promise = this.channelLockPromises.get(channel.id); + if (promise) { + await promise; } } @@ -439,7 +445,7 @@ export class DiscordBot { try { this.lockChannel(chan); if (!botUser) { - opts.embed = embedSet.replyEmbed; + // NOTE: Don't send replies to discord if we are a puppet. msg = await chan.send(embed.description, opts); } else if (hook) { msg = await hook.send(embed.description, { diff --git a/src/log.ts b/src/log.ts index 11c86888f23a6cfa5a4dcd90ff0ad1bc6e355840..0477d8a1af5e77d3e47289f56a034a6afc3248e3 100644 --- a/src/log.ts +++ b/src/log.ts @@ -17,7 +17,6 @@ limitations under the License. import { createLogger, Logger, format, transports } from "winston"; import { DiscordBridgeConfigLogging, LoggingFile} from "./config"; import { inspect } from "util"; -import * as moment from "moment"; import "winston-daily-rotate-file"; const FORMAT_FUNC = format.printf((info) => { @@ -47,10 +46,6 @@ export class Log { private static config: DiscordBridgeConfigLogging; private static logger: Logger; - private static now() { - return moment().format(Log.config.lineDateFormat); - } - private static setupLogger() { if (Log.logger) { Log.logger.close(); @@ -64,7 +59,7 @@ export class Log { Log.logger = createLogger({ format: format.combine( format.timestamp({ - format: Log.now, + format: Log.config.lineDateFormat, }), format.colorize(), FORMAT_FUNC, diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts index 3e39518ed5680ab4b38ac8355d2b90b28882b999..5751c8d962edca82431fd663075cfe55a41dd18f 100644 --- a/src/matrixeventprocessor.ts +++ b/src/matrixeventprocessor.ts @@ -196,31 +196,28 @@ export class MatrixEventProcessor { let msg = `\`${event.sender}\` `; + const isNew = event.unsigned === undefined || event.unsigned.prev_content === undefined; + const allowJoinLeave = !this.config.bridge.disableJoinLeaveNotifications; + if (event.type === "m.room.name") { msg += `set the name to \`${event.content!.name}\``; } else if (event.type === "m.room.topic") { msg += `set the topic to \`${event.content!.topic}\``; } else if (event.type === "m.room.member") { const membership = event.content!.membership; - if (membership === "join" - && event.unsigned.prev_content === undefined) { - if (!this.config.bridge.disableJoinLeaveNotifications) { - msg += `joined the room`; - } else { - return; - } + if (membership === "join" && isNew && allowJoinLeave) { + msg += "joined the room"; } else if (membership === "invite") { msg += `invited \`${event.state_key}\` to the room`; } else if (membership === "leave" && event.state_key !== event.sender) { msg += `kicked \`${event.state_key}\` from the room`; - } else if (membership === "leave") { - if (!this.config.bridge.disableJoinLeaveNotifications) { - msg += `left the room`; - } else { - return; - } + } else if (membership === "leave" && allowJoinLeave) { + msg += "left the room"; } else if (membership === "ban") { msg += `banned \`${event.state_key}\` from the room`; + } else { + // Ignore anything else + return; } } diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index 4dd1ad806e8173127c73e73aaaad0d00f93b44ec..f6208be1d18c3d8d32377f56c27a94458ddcde8f 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -25,6 +25,7 @@ import { DiscordBot } from "../src/bot"; import { MockDiscordClient } from "./mocks/discordclient"; import { MockMessage } from "./mocks/message"; import { Util } from "../src/util"; +import { MockChannel } from "./mocks/channel"; // we are a test file and thus need those /* tslint:disable:no-unused-expression max-file-line-count no-any */ @@ -415,6 +416,46 @@ describe("DiscordBot", () => { assert.equal(expected, ITERATIONS); }); }); + describe("locks", () => { + it("should lock and unlock a channel", async () => { + const bot = new modDiscordBot.DiscordBot( + "", + config, + mockBridge, + {}, + ) as DiscordBot; + const chan = new MockChannel("123") as any; + const t = Date.now(); + bot.lockChannel(chan); + await bot.waitUnlock(chan); + const diff = Date.now() - t; + expect(diff).to.be.greaterThan(config.limits.discordSendDelay - 1); + }); + it("should lock and unlock a channel early, if unlocked", async () => { + const discordSendDelay = 500; + const SHORTDELAY = 100; + const bot = new modDiscordBot.DiscordBot( + "", + { + bridge: { + domain: "localhost", + }, + limits: { + discordSendDelay, + }, + }, + mockBridge, + {}, + ) as DiscordBot; + const chan = new MockChannel("123") as any; + setTimeout(() => bot.unlockChannel(chan), SHORTDELAY); + const t = Date.now(); + bot.lockChannel(chan); + await bot.waitUnlock(chan); + const diff = Date.now() - t; + expect(diff).to.be.greaterThan(SHORTDELAY - 1); + }); + }); // }); // describe("ProcessMatrixMsgEvent()", () => { // diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts index dd1a5d2e543b6740a5079750921b3af2626d472d..e5dbf0c9f14582bd47369391de7938d4f568a628 100644 --- a/test/test_matrixeventprocessor.ts +++ b/test/test_matrixeventprocessor.ts @@ -748,7 +748,8 @@ This is the reply`, sender: "@test:localhost", type: "m.room.message", } as IMatrixEvent, mockChannel as any); - expect(result!.timestamp!.getTime()).to.be.equal(TEST_TIMESTAMP); + // NOTE: Due to https://github.com/discordjs/discord.js/issues/3283, the typing is wrong here. + expect(result!.timestamp!).to.be.equal(TEST_TIMESTAMP); }); it("should add field for discord replies", async () => { const processor = createMatrixEventProcessor();