diff --git a/src/bot.ts b/src/bot.ts index 3f55abc122ff34429a9105275c69fa8c80bd9df4..4af4b861f7ccf270e5e1259f5eb75fa0aebccceb 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -79,8 +79,9 @@ export class DiscordBot { /* Caches */ private roomIdsForGuildCache: Map<string, {roomIds: string[], ts: number}> = new Map(); - /* Handles messages queued up to be sent to discord. */ + /* 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} }; constructor( private botUserId: string, @@ -105,6 +106,7 @@ export class DiscordBot { // init vars this.sentMessages = []; this.discordMessageQueue = {}; + this.channelLocks = {}; this.lastEventIds = {}; } @@ -136,6 +138,32 @@ export class DiscordBot { return this.provisioner; } + public lockChannel(channel: Discord.Channel) { + if (this.channelLocks[channel.id]) { + return; + } + let i: NodeJS.Timeout; + const p = new Promise((resolve) => { + i = setInterval(resolve, this.config.limits.discordSendDelay); + this.channelLocks[channel.id] = {i, p}; + }); + } + + public unlockChannel(channel: Discord.Channel) { + const lock = this.channelLocks[channel.id]; + if (lock) { + clearTimeout(lock.i); + } + delete this.channelLocks[channel.id]; + } + + public async waitUnlock(channel: Discord.Channel) { + const lock = this.channelLocks[channel.id]; + if (lock) { + await lock.p; + } + } + public GetIntentFromDiscordMember(member: Discord.GuildMember | Discord.User, webhookID?: string): Intent { if (webhookID) { // webhookID and user IDs are the same, they are unique, so no need to prefix _webhook_ @@ -154,7 +182,6 @@ export class DiscordBot { public async run(): Promise<void> { const client = await this.clientFactory.getClient(); - if (!this.config.bridge.disableTypingNotifications) { client.on("typingStart", async (c, u) => { try { @@ -200,7 +227,7 @@ export class DiscordBot { client.on("messageDelete", async (msg: Discord.Message) => { try { - await Util.DelayedPromise(this.config.limits.discordSendDelay); + await this.waitUnlock(msg.channel); this.discordMessageQueue[msg.channel.id] = (async () => { await (this.discordMessageQueue[msg.channel.id] || Promise.resolve()); try { @@ -220,6 +247,7 @@ export class DiscordBot { msgs.forEach((msg) => { promiseArr.push(async () => { try { + await this.waitUnlock(msg.channel); await this.DeleteDiscordMessage(msg); } catch (err) { log.error("Caught while handling 'messageDeleteBulk'", err); @@ -233,7 +261,7 @@ export class DiscordBot { }); client.on("messageUpdate", async (oldMessage: Discord.Message, newMessage: Discord.Message) => { try { - await Util.DelayedPromise(this.config.limits.discordSendDelay); + await this.waitUnlock(newMessage.channel); this.discordMessageQueue[newMessage.channel.id] = (async () => { await (this.discordMessageQueue[newMessage.channel.id] || Promise.resolve()); try { @@ -248,7 +276,7 @@ export class DiscordBot { }); client.on("message", async (msg: Discord.Message) => { try { - await Util.DelayedPromise(this.config.limits.discordSendDelay); + await this.waitUnlock(msg.channel); this.discordMessageQueue[msg.channel.id] = (async () => { await (this.discordMessageQueue[msg.channel.id] || Promise.resolve()); try { @@ -369,7 +397,9 @@ export class DiscordBot { if (!msg) { return; } + this.lockChannel(channel); const res = await channel.send(msg); + this.unlockChannel(channel); await this.StoreMessagesSent(res, channel, event); } @@ -401,6 +431,7 @@ export class DiscordBot { } } try { + this.lockChannel(chan); if (!botUser) { opts.embed = embedSet.replyEmbed; msg = await chan.send(embed.description, opts); @@ -419,6 +450,7 @@ export class DiscordBot { opts.embed = embed; msg = await chan.send("", opts); } + this.unlockChannel(chan); await this.StoreMessagesSent(msg, chan, event); } catch (err) { log.error("Couldn't send message. ", err); @@ -446,7 +478,9 @@ export class DiscordBot { const msg = await chan.fetchMessage(storeEvent.DiscordId); try { + this.lockChannel(msg.channel); await msg.delete(); + this.unlockChannel(msg.channel); log.info(`Deleted message`); } catch (ex) { log.warn(`Failed to delete message`, ex); @@ -597,9 +631,11 @@ export class DiscordBot { /* tslint:disable-next-line no-any */ } as any, // XXX: Discord.js typings are wrong. `Unbanned.`); + this.lockChannel(botChannel); res = await botChannel.send( `${kickee} was unbanned from this channel by ${kicker}.`, ) as Discord.Message; + this.unlockChannel(botChannel); this.sentMessages.push(res.id); return; } @@ -609,10 +645,12 @@ export class DiscordBot { return; } const word = `${kickban === "ban" ? "banned" : "kicked"}`; + this.lockChannel(botChannel); res = await botChannel.send( `${kickee} was ${word} from this channel by ${kicker}.` + (reason ? ` Reason: ${reason}` : ""), ) as Discord.Message; + this.unlockChannel(botChannel); this.sentMessages.push(res.id); log.info(`${word} ${kickee}`); diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index 2eeccfc065c3506ed596029794b071f94654f6af..4dd1ad806e8173127c73e73aaaad0d00f93b44ec 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -382,7 +382,7 @@ describe("DiscordBot", () => { const CHANID = 123; // Send delay of 50ms, 2 seconds / 50ms - 5 for safety. for (let i = 0; i < ITERATIONS; i++) { - await client.emit("message", { n: i, channel: { id: CHANID} }); + await client.emit("message", { channel: { guild: { id: CHANID }, id: CHANID} }); } await discordBot.discordMessageQueue[CHANID]; }); @@ -409,7 +409,7 @@ describe("DiscordBot", () => { const CHANID = 123; // Send delay of 50ms, 2 seconds / 50ms - 5 for safety. for (let n = 0; n < ITERATIONS; n++) { - await client.emit("message", { n, channel: { id: CHANID} }); + await client.emit("message", { n, channel: { guild: { id: CHANID }, id: CHANID} }); } await discordBot.discordMessageQueue[CHANID]; assert.equal(expected, ITERATIONS);