diff --git a/config/config.sample.yaml b/config/config.sample.yaml index 2b8350597ca9ef0ddf984570efa16e2d3eda00cd..925df2bbadf7195dd2049b2c01c901c61620e0fa 100644 --- a/config/config.sample.yaml +++ b/config/config.sample.yaml @@ -87,5 +87,9 @@ channel: # Make all the discord users leave the room. ghostsLeave: true limits: - # Delay between discord users joining a room. + # Delay in milliseconds between discord users joining a room. roomGhostJoinDelay: 6000 + # Delay in milliseconds before sending messages to discord to avoid echos. + # (Copies of a sent message may arrive from discord before we've + # fininished handling it, causing us to echo it back to the room) + discordSendDelay: 750 diff --git a/config/config.schema.yaml b/config/config.schema.yaml index 56de3a8585438f47d06b7636f0849b9163a72c6a..4f832fa6ce069b1a2560c318a806e09c9127b00a 100644 --- a/config/config.schema.yaml +++ b/config/config.schema.yaml @@ -89,6 +89,8 @@ properties: properties: roomGhostJoinDelay: type: "number" + discordSendDelay: + type: "number" channel: type: "object" properties: diff --git a/src/bot.ts b/src/bot.ts index deed4d7e4a38bd2d9b5a68eb405405d8e67935b9..eb23cf919ca1da4509f977ccf175a62c81f4c7b7 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -19,9 +19,6 @@ import * as mime from "mime"; const log = new Log("DiscordBot"); -// Due to messages often arriving before we get a response from the send call, -// messages get delayed from discord. -const MSG_PROCESS_DELAY = 750; const MIN_PRESENCE_UPDATE_DELAY = 250; // TODO: This is bad. We should be serving the icon from the own homeserver. @@ -46,6 +43,9 @@ export class DiscordBot { private channelSync: ChannelSyncroniser; private roomHandler: MatrixRoomHandler; + /* Handles messages queued up to be sent to discord. */ + private discordMessageQueue: { [channelId: string]: Promise<any> }; + constructor(config: DiscordBridgeConfig, store: DiscordStore, private provisioner: Provisioner) { this.config = config; this.store = store; @@ -55,6 +55,7 @@ export class DiscordBot { new MessageProcessorOpts(this.config.bridge.domain, this), ); this.presenceHandler = new PresenceHandler(this); + this.discordMessageQueue = {}; } public setBridge(bridge: Bridge) { @@ -84,7 +85,7 @@ export class DiscordBot { return this.bridge.getIntentFromLocalpart(`_discord_${member.id}`); } - public run (): Promise<void> { + public run(): Promise<void> { return this.clientFactory.init().then(() => { return this.clientFactory.getClient(); }).then((client: any) => { @@ -103,11 +104,32 @@ export class DiscordBot { client.on("guildUpdate", (_, newGuild) => { this.channelSync.OnGuildUpdate(newGuild); }); client.on("guildDelete", (guild) => { this.channelSync.OnGuildDelete(guild); }); - client.on("messageDelete", (msg) => { this.DeleteDiscordMessage(msg); }); - client.on("messageUpdate", (oldMessage, newMessage) => { this.OnMessageUpdate(oldMessage, newMessage); }); - client.on("message", (msg) => { Bluebird.delay(MSG_PROCESS_DELAY).then(() => { - this.OnMessage(msg); - }); + // Due to messages often arriving before we get a response from the send call, + // messages get delayed from discord. We use Bluebird.delay to handle this. + + client.on("messageDelete", async (msg: Discord.Message) => { + // tslint:disable-next-line:await-promise + await Bluebird.delay(this.config.limits.discordSendDelay); + this.discordMessageQueue[msg.channel.id] = (async () => { + await (this.discordMessageQueue[msg.channel.id] || Promise.resolve()); + await this.OnMessage(msg); + })(); + }); + client.on("messageUpdate", async (oldMessage: Discord.Message, newMessage: Discord.Message) => { + // tslint:disable-next-line:await-promise + await Bluebird.delay(this.config.limits.discordSendDelay); + this.discordMessageQueue[newMessage.channel.id] = (async () => { + await (this.discordMessageQueue[newMessage.channel.id] || Promise.resolve()); + await this.OnMessageUpdate(oldMessage, newMessage); + })(); + }); + client.on("message", async (msg: Discord.Message) => { + // tslint:disable-next-line:await-promise + await Bluebird.delay(this.config.limits.discordSendDelay); + this.discordMessageQueue[msg.channel.id] = (async () => { + await (this.discordMessageQueue[msg.channel.id] || Promise.resolve()); + await this.OnMessage(msg); + })(); }); const jsLog = new Log("discord.js"); @@ -173,12 +195,12 @@ export class DiscordBot { } } - public LookupRoom (server: string, room: string, sender?: string): Promise<ChannelLookupResult> { + public LookupRoom(server: string, room: string, sender?: string): Promise<ChannelLookupResult> { const hasSender = sender !== null; return this.clientFactory.getClient(sender).then((client) => { const guild = client.guilds.get(server); if (!guild) { - throw `Guild "${server}" not found`; + throw new Error(`Guild "${server}" not found`); } const channel = guild.channels.get(room); if (channel) { @@ -187,7 +209,7 @@ export class DiscordBot { lookupResult.botUser = this.bot.user.id === client.user.id; return lookupResult; } - throw `Channel "${room}" not found`; + throw new Error(`Channel "${room}" not found`); }).catch((err) => { log.verbose("LookupRoom => ", err); if (hasSender) { @@ -200,7 +222,7 @@ export class DiscordBot { public async ProcessMatrixStateEvent(event: any): Promise<void> { log.verbose(`Got state event from ${event.room_id} ${event.type}`); - const channel = <Discord.TextChannel> await this.GetChannelFromRoomId(event.room_id); + const channel = await this.GetChannelFromRoomId(event.room_id) as Discord.TextChannel; const msg = this.mxEventProcessor.StateEventToMessage(event, channel); if (!msg) { return; @@ -327,7 +349,7 @@ export class DiscordBot { } } - public OnUserQuery (userId: string): any { + public OnUserQuery(userId: string): any { return false; } @@ -435,7 +457,7 @@ export class DiscordBot { private async OnMessage(msg: Discord.Message) { const indexOfMsg = this.sentMessages.indexOf(msg.id); - const chan = <Discord.TextChannel> msg.channel; + const chan = msg.channel as Discord.TextChannel; if (indexOfMsg !== -1) { log.verbose("Got repeated message, ignoring."); delete this.sentMessages[indexOfMsg]; @@ -486,7 +508,7 @@ export class DiscordBot { } // Update presence because sometimes discord misses people. - this.userSync.OnUpdateUser(msg.author).then(() => { + return this.userSync.OnUpdateUser(msg.author).then(() => { return this.channelSync.GetRoomIdsFromChannel(msg.channel).catch((err) => { log.verbose("No bridged rooms to send message to. Oh well."); return null; @@ -576,18 +598,18 @@ export class DiscordBot { } } - private async DeleteDiscordMessage(msg: Discord.Message) { - log.info(`Got delete event for ${msg.id}`); - const storeEvent = await this.store.Get(DbEvent, {discord_id: msg.id}); - if (!storeEvent.Result) { - log.warn(`Could not redact because the event was not in the store.`); - return; - } - while (storeEvent.Next()) { - log.info(`Deleting discord msg ${storeEvent.DiscordId}`); - const intent = this.GetIntentFromDiscordMember(msg.author); - const matrixIds = storeEvent.MatrixId.split(";"); - await intent.getClient().redactEvent(matrixIds[1], matrixIds[0]); - } + private async DeleteDiscordMessage(msg: Discord.Message) { + log.info(`Got delete event for ${msg.id}`); + const storeEvent = await this.store.Get(DbEvent, {discord_id: msg.id}); + if (!storeEvent.Result) { + log.warn(`Could not redact because the event was not in the store.`); + return; + } + while (storeEvent.Next()) { + log.info(`Deleting discord msg ${storeEvent.DiscordId}`); + const intent = this.GetIntentFromDiscordMember(msg.author); + const matrixIds = storeEvent.MatrixId.split(";"); + await intent.getClient().redactEvent(matrixIds[1], matrixIds[0]); } + } } diff --git a/src/channelsyncroniser.ts b/src/channelsyncroniser.ts index 6e1f9b30d4b8142a8fde44a0468350df49c0e9ec..84fa42a4b7756f76c2de452a20e742a8e40208a7 100644 --- a/src/channelsyncroniser.ts +++ b/src/channelsyncroniser.ts @@ -31,13 +31,13 @@ export interface ISingleChannelState { iconUrl: string; // nullable iconId: string; // nullable removeIcon: boolean; -}; +} export interface IChannelState { id: string; mxChannels: ISingleChannelState[]; iconMxcUrl: string; // nullable -}; +} export class ChannelSyncroniser { @@ -75,7 +75,7 @@ export class ChannelSyncroniser { log.error("Failed to get channel state", e); } } - + let iconMxcUrl = null; for (const channelState of channelStates) { channelState.iconMxcUrl = channelState.iconMxcUrl || iconMxcUrl; @@ -103,7 +103,7 @@ export class ChannelSyncroniser { log.warn(`Couldn't find roomids for deleted channel ${channel.id}`); return; } - for (const roomid of roomids){ + for (const roomid of roomids) { try { await this.handleChannelDeletionForRoom(channel as Discord.TextChannel, roomid, entries[roomid][0]); } catch (e) { @@ -139,13 +139,13 @@ export class ChannelSyncroniser { id: channel.id, mxChannels: [], }); - + const remoteRooms = await this.roomStore.getEntriesByRemoteRoomData({discord_channel: channel.id}); if (remoteRooms.length === 0) { log.verbose(`Could not find any channels in room store.`); return channelState; } - + const patternMap = { name: "#" + channel.name, guild: channel.guild.name, @@ -165,19 +165,19 @@ export class ChannelSyncroniser { const singleChannelState = Object.assign({}, DEFAULT_SINGLECHANNEL_STATE, { mxid, }); - + const oldName = remoteRoom.remote.get("discord_name"); if (remoteRoom.remote.get("update_name") && (forceUpdate || oldName !== name)) { log.verbose(`Channel ${mxid} name should be updated`); singleChannelState.name = name; } - + const oldTopic = remoteRoom.remote.get("discord_topic"); if (remoteRoom.remote.get("update_topic") && (forceUpdate || oldTopic !== topic)) { log.verbose(`Channel ${mxid} topic should be updated`); singleChannelState.topic = topic; } - + const oldIconUrl = remoteRoom.remote.get("discord_iconurl"); // no force on icon update as we don't want to duplicate ALL the icons if (remoteRoom.remote.get("update_icon") && oldIconUrl !== iconUrl) { @@ -211,14 +211,14 @@ export class ChannelSyncroniser { remoteRoom.remote.set("discord_name", channelState.name); roomUpdated = true; } - + if (channelState.topic !== null) { log.verbose(`Updating channeltopic for ${channelState.mxid} to "${channelState.topic}"`); await intent.setRoomTopic(channelState.mxid, channelState.topic); remoteRoom.remote.set("discord_topic", channelState.topic); roomUpdated = true; } - + if (channelState.iconUrl !== null) { log.verbose(`Updating icon_url for ${channelState.mxid} to "${channelState.iconUrl}"`); if (channelsState.iconMxcUrl === null) { @@ -234,7 +234,7 @@ export class ChannelSyncroniser { remoteRoom.remote.set("discord_iconurl_mxc", channelsState.iconMxcUrl); roomUpdated = true; } - + if (channelState.removeIcon) { log.verbose(`Clearing icon_url for ${channelState.mxid}`); await intent.setRoomAvatar(channelState.mxid, null); @@ -242,7 +242,7 @@ export class ChannelSyncroniser { remoteRoom.remote.set("discord_iconurl_mxc", null); roomUpdated = true; } - + if (roomUpdated) { await this.roomStore.upsertEntry(remoteRoom); } @@ -260,7 +260,7 @@ export class ChannelSyncroniser { this.roomStore.upsertEntry(entry); if (options.ghostsLeave) { - for (const member of channel.members.array()){ + for (const member of channel.members.array()) { try { const mIntent = await this.bot.GetIntentFromDiscordMember(member); mIntent.leave(roomId); @@ -288,7 +288,7 @@ export class ChannelSyncroniser { log.error(`Failed to set topic of room ${roomId} ${e}`); } } - + if (plumbed !== true) { if (options.unsetRoomAlias) { try { diff --git a/src/config.ts b/src/config.ts index c231adc639e3d24b11f221b49d7ce96886da6f4c..abba23fdc26b038de2e4ea309757ed0c2e35527e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -77,6 +77,7 @@ class DiscordBridgeConfigChannelDeleteOptions { class DiscordBridgeConfigLimits { public roomGhostJoinDelay: number = 6000; + public discordSendDelay: number = 750; } export class LoggingFile { diff --git a/src/db/postgres.ts b/src/db/postgres.ts index b6655c49ec8c0406f4dfc24a3850e01c1c20476e..c5a7cde731ca22d27a28c7904fbbd6b71739fd25 100644 --- a/src/db/postgres.ts +++ b/src/db/postgres.ts @@ -16,7 +16,7 @@ export class Postgres implements IDatabaseConnector { } private db: IDatabase<any>; - constructor (private connectionString: string) { + constructor(private connectionString: string) { } public Open() { diff --git a/src/db/sqlite3.ts b/src/db/sqlite3.ts index 2d68ab176bd7bde36e0a99890688622577881bcb..47f5db5bd65a806fbc25faa5baed44136851962c 100644 --- a/src/db/sqlite3.ts +++ b/src/db/sqlite3.ts @@ -5,7 +5,7 @@ const log = new Log("SQLite3"); export class SQLite3 implements IDatabaseConnector { private db: Database; - constructor (private filename: string) { + constructor(private filename: string) { } diff --git a/src/discordas.ts b/src/discordas.ts index 33b70b2b3404766b314538ebbad82e7693a0aa74..07a3b0042c3937a2f183a414dc16b64213233883 100644 --- a/src/discordas.ts +++ b/src/discordas.ts @@ -39,7 +39,7 @@ function generateRegistration(reg, callback) { callback(reg); } -function run (port: number, fileConfig: DiscordBridgeConfig) { +function run(port: number, fileConfig: DiscordBridgeConfig) { const config = new DiscordBridgeConfig(); config.ApplyConfig(fileConfig); Log.Configure(config.logging); @@ -67,7 +67,9 @@ function run (port: number, fileConfig: DiscordBridgeConfig) { controller: { // onUserQuery: userQuery, onAliasQuery: roomhandler.OnAliasQuery.bind(roomhandler), - onEvent: roomhandler.OnEvent.bind(roomhandler), + onEvent: (request, context) => + request.outcomeFrom(Promise.resolve(roomhandler.OnEvent(request, context))) + , onAliasQueried: roomhandler.OnAliasQueried.bind(roomhandler), thirdPartyLookup: roomhandler.ThirdPartyLookup, onLog: (line, isError) => { @@ -84,6 +86,11 @@ function run (port: number, fileConfig: DiscordBridgeConfig) { registration, userStore: config.database.userStorePath, roomStore: config.database.roomStorePath, + // To avoid out of order message sending. + queue: { + type: "per_room", + perRequest: true, + }, }); provisioner.SetBridge(bridge); roomhandler.setBridge(bridge); diff --git a/src/log.ts b/src/log.ts index 3e507a0f85b2e75fe962c35eeef01fa1ec6771b2..2b775da7e18e70212b8e4c48abbda2576c4d98f4 100644 --- a/src/log.ts +++ b/src/log.ts @@ -1,6 +1,6 @@ -import { createLogger, Logger, format, transports } from "winston"; +import { createLogger, Logger, format, transports } from "winston"; import { DiscordBridgeConfigLogging, LoggingFile} from "./config"; -import { inspect } from "util"; +import { inspect } from "util"; import * as moment from "moment"; import "winston-daily-rotate-file"; diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts index 93db39566c3f7a91b6f5039cd1c9fa65fb61330a..bfce1154dce66d63ac1861f10c7e293ae053ac96 100644 --- a/src/matrixeventprocessor.ts +++ b/src/matrixeventprocessor.ts @@ -36,7 +36,7 @@ export class MatrixEventProcessor { private bridge: any; private discord: DiscordBot; - constructor (opts: MatrixEventProcessorOpts) { + constructor(opts: MatrixEventProcessorOpts) { this.config = opts.config; this.bridge = opts.bridge; this.discord = opts.discord; @@ -250,7 +250,7 @@ export class MatrixEventProcessor { private async SetEmbedAuthor(embed: Discord.RichEmbed, sender: string, profile?: any) { const intent = this.bridge.getIntent(); let displayName = sender; - let avatarUrl = undefined; + let avatarUrl; // Are they a discord user. if (this.bridge.getBot().isRemoteUser(sender)) { diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts index cd02b7049e7ceb027c5a588b5b0375152afecd26..bb63c0956b85814028dd1e91da4956fb9bec794a 100644 --- a/src/matrixroomhandler.ts +++ b/src/matrixroomhandler.ts @@ -41,7 +41,7 @@ export class MatrixRoomHandler { private bridge: Bridge; private discord: DiscordBot; private botUserId: string; - constructor (discord: DiscordBot, config: DiscordBridgeConfig, botUserId: string, private provisioner: Provisioner) { + constructor(discord: DiscordBot, config: DiscordBridgeConfig, botUserId: string, private provisioner: Provisioner) { this.discord = discord; this.config = config; this.botUserId = botUserId; @@ -62,7 +62,7 @@ export class MatrixRoomHandler { this.bridge = bridge; } - public async OnAliasQueried (alias: string, roomId: string) { + public async OnAliasQueried(alias: string, roomId: string) { log.verbose("OnAliasQueried", `Got OnAliasQueried for ${alias} ${roomId}`); const channel = await this.discord.GetChannelFromRoomId(roomId) as Discord.GuildChannel; @@ -78,7 +78,7 @@ export class MatrixRoomHandler { // Join a whole bunch of users. /* We delay the joins to give some implementations a chance to breathe */ let delay = this.config.limits.roomGhostJoinDelay; - for (const member of (<Discord.TextChannel> channel).members.array()) { + for (const member of (channel as Discord.TextChannel).members.array()) { if (member.id === this.discord.GetBotId()) { continue; } @@ -100,7 +100,7 @@ export class MatrixRoomHandler { await promiseChain; } - public OnEvent (request, context): Promise<any> { + public OnEvent(request, context): Promise<any> { const event = request.getData(); if (event.unsigned.age > AGE_LIMIT) { log.warn(`Skipping event due to age ${event.unsigned.age} > ${AGE_LIMIT}`); @@ -247,7 +247,7 @@ export class MatrixRoomHandler { const channelId = args[1]; try { const discordResult = await this.discord.LookupRoom(guildId, channelId); - const channel = <Discord.TextChannel> discordResult.channel; + const channel = discordResult.channel as Discord.TextChannel; log.info(`Bridging matrix room ${event.room_id} to ${guildId}/${channelId}`); this.bridge.getIntent().sendMessage(event.room_id, { @@ -321,7 +321,7 @@ export class MatrixRoomHandler { } } - public OnAliasQuery (alias: string, aliasLocalpart: string): Promise<any> { + public OnAliasQuery(alias: string, aliasLocalpart: string): Promise<any> { log.info("Got request for #", aliasLocalpart); const srvChanPair = aliasLocalpart.substr("_discord_".length).split("_", ROOM_NAME_PARTS); if (srvChanPair.length < ROOM_NAME_PARTS || srvChanPair[0] === "" || srvChanPair[1] === "") { @@ -400,7 +400,7 @@ export class MatrixRoomHandler { } public async HandleDiscordCommand(msg: Discord.Message) { - if (!(<Discord.TextChannel> msg.channel).guild) { + if (!(msg.channel as Discord.TextChannel).guild) { msg.channel.send("**ERROR:** only available for guild channels"); } @@ -526,7 +526,7 @@ export class MatrixRoomHandler { return doJoin().catch(errorHandler); } - private createMatrixRoom (channel: Discord.TextChannel, alias: string) { + private createMatrixRoom(channel: Discord.TextChannel, alias: string) { const remote = new RemoteRoom(`discord_${channel.guild.id}_${channel.id}`); remote.set("discord_type", "text"); remote.set("discord_guild", channel.guild.id); diff --git a/src/messageprocessor.ts b/src/messageprocessor.ts index eb35751615a4b0e13d03b54f24de783083c9ac13..32beb2f6b6a9b89fe26fc3ace80254e0685e0ccd 100644 --- a/src/messageprocessor.ts +++ b/src/messageprocessor.ts @@ -24,7 +24,7 @@ function _setupMarked() { sanitize: true, tables: false, }); - + const markedLexer = new marked.Lexer(); // as discord doesn't support these markdown rules // we want to disable them by setting their regexes to non-matchable ones @@ -34,7 +34,7 @@ function _setupMarked() { } // paragraph-end matching is different, as we don't have headers and thelike markedLexer.rules.paragraph = /^((?:[^\n]+\n\n)+)\n*/; - + const markedInlineLexer = new marked.InlineLexer(true); // same again, remove tags discord doesn't support for (const r of ["tag", "link", "reflink", "nolink", "br"]) { @@ -45,7 +45,7 @@ function _setupMarked() { } export class MessageProcessorOpts { - constructor (readonly domain: string, readonly bot: DiscordBot = null) { + constructor(readonly domain: string, readonly bot: DiscordBot = null) { } } @@ -57,7 +57,7 @@ export class MessageProcessorMatrixResult { export class MessageProcessor { private readonly opts: MessageProcessorOpts; - constructor (opts: MessageProcessorOpts, bot: DiscordBot = null) { + constructor(opts: MessageProcessorOpts, bot: DiscordBot = null) { // Backwards compat if (bot != null) { this.opts = new MessageProcessorOpts(opts.domain, bot); @@ -70,23 +70,23 @@ export class MessageProcessor { const result = new MessageProcessorMatrixResult(); let content = msg.content; - + // for the formatted body we need to parse markdown first // as else it'll HTML escape the result of the discord syntax let contentPostmark = marked(content).replace(/\n/g, "<br>").replace(/(<br>)?<\/p>(<br>)?/g, "</p>"); - + // parse the plain text stuff content = this.InsertEmbeds(content, msg); content = this.ReplaceMembers(content, msg); content = this.ReplaceChannels(content, msg); content = await this.ReplaceEmoji(content, msg); - + // parse postmark stuff contentPostmark = this.InsertEmbedsPostmark(contentPostmark, msg); contentPostmark = this.ReplaceMembersPostmark(contentPostmark, msg); contentPostmark = this.ReplaceChannelsPostmark(contentPostmark, msg); contentPostmark = await this.ReplaceEmojiPostmark(contentPostmark, msg); - + result.body = content; result.formattedBody = contentPostmark; return result; diff --git a/src/presencehandler.ts b/src/presencehandler.ts index 76ab6bacd2dd788ecce7afcb500aac7169ca2298..88a6cc8ac74f206e263b327a43a75f8428fa551e 100644 --- a/src/presencehandler.ts +++ b/src/presencehandler.ts @@ -14,12 +14,12 @@ export class PresenceHandler { private readonly bot: DiscordBot; private presenceQueue: User[]; private interval: number; - constructor (bot: DiscordBot) { + constructor(bot: DiscordBot) { this.bot = bot; this.presenceQueue = []; } - get QueueCount (): number { + get QueueCount(): number { return this.presenceQueue.length; } diff --git a/src/store.ts b/src/store.ts index 562f52a74ae55a20060cd23e04c203ac4703c617..56769f9b5455b2370cd210ae25a70c7012423b94 100644 --- a/src/store.ts +++ b/src/store.ts @@ -19,7 +19,7 @@ export class DiscordStore { public db: IDatabaseConnector; private version: number; private config: DiscordBridgeConfigDatabase; - constructor (private configOrFile: DiscordBridgeConfigDatabase|string) { + constructor(private configOrFile: DiscordBridgeConfigDatabase|string) { if (typeof(configOrFile) === "string") { this.config = new DiscordBridgeConfigDatabase(); this.config.filename = configOrFile; @@ -64,7 +64,7 @@ export class DiscordStore { /** * Checks the database has all the tables needed. */ - public async init (overrideSchema: number = 0): Promise<void> { + public async init(overrideSchema: number = 0): Promise<void> { log.info("Starting DB Init"); await this.open_database(); let version = await this.getSchemaVersion(); @@ -95,11 +95,11 @@ export class DiscordStore { log.info("Updated database to the latest schema"); } - public close () { + public close() { this.db.Close(); } - public create_table (statement: string, tablename: string): Promise<void|Error> { + public create_table(statement: string, tablename: string): Promise<void|Error> { return this.db.Exec(statement).then(() => { log.info("Created table", tablename); }).catch((err) => { @@ -266,7 +266,7 @@ export class DiscordStore { return data.Delete(this); } - private async getSchemaVersion ( ): Promise<number> { + private async getSchemaVersion( ): Promise<number> { log.silly("_get_schema_version"); let version = 0; try { @@ -277,7 +277,7 @@ export class DiscordStore { return version; } - private setSchemaVersion (ver: number): Promise<any> { + private setSchemaVersion(ver: number): Promise<any> { log.silly("_set_schema_version => ", ver); return this.db.Run( ` diff --git a/src/usersyncroniser.ts b/src/usersyncroniser.ts index 81605f0390080c53641d67f11cfffdde5068496b..d91101abedfd800ba9824b2f75d7acb9169dd5b4 100644 --- a/src/usersyncroniser.ts +++ b/src/usersyncroniser.ts @@ -33,13 +33,13 @@ export interface IUserState { avatarUrl: string; // Nullable avatarId: string; removeAvatar: boolean; // If the avatar has been removed from the user. -}; +} export interface IGuildMemberRole { name: string; color: number; position: number; -}; +} export interface IGuildMemberState { id: string; diff --git a/src/util.ts b/src/util.ts index 6346ffe5c140b625fff72acb24f751b95f57c6ae..85575f4826212123345483c6fa69d72360cb7d7e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -15,20 +15,20 @@ export interface ICommandAction { description?: string; permission?: string; run(params: any): Promise<any>; -}; +} export interface ICommandActions { [index: string]: ICommandAction; -}; +} export interface ICommandParameter { description?: string; get(param: string): Promise<any>; -}; +} export interface ICommandParameters { [index: string]: ICommandParameter; -}; +} export class Util { @@ -36,7 +36,7 @@ export class Util { * downloadFile - This function will take a URL and store the resulting data into * a buffer. */ - public static DownloadFile (url: string): Promise<Buffer> { + public static DownloadFile(url: string): Promise<Buffer> { return new Promise((resolve, reject) => { let ht; if (url.startsWith("https")) { @@ -67,7 +67,7 @@ export class Util { * uploadContentFromUrl - Upload content from a given URL to the homeserver * and return a MXC URL. */ - public static UploadContentFromUrl (url: string, intent: Intent, name: string): Promise<IUploadResult> { + public static UploadContentFromUrl(url: string, intent: Intent, name: string): Promise<IUploadResult> { let contenttype; let size; name = name || null; diff --git a/test/mocks/channel.ts b/test/mocks/channel.ts index 08a0922980a95674f5635945f11d24042e5faa25..0ce4a288e139a5c32abe5b01e7187885149c6b3c 100644 --- a/test/mocks/channel.ts +++ b/test/mocks/channel.ts @@ -4,7 +4,7 @@ import {MockCollection} from "./collection"; // Mocking TextChannel export class MockChannel { public members = new MockCollection<string, MockMember>(); - constructor ( + constructor( public id: string = "", public guild: any = null, public type: string = "text", diff --git a/test/mocks/discordclient.ts b/test/mocks/discordclient.ts index d431ebf1dd0b0a768d691aeeac08560148ef8a24..20da7ba2e72ef8d2dcbb5b801e2b5acfcce10b44 100644 --- a/test/mocks/discordclient.ts +++ b/test/mocks/discordclient.ts @@ -1,12 +1,13 @@ import {MockCollection} from "./collection"; import {MockGuild} from "./guild"; import {MockUser} from "./user"; +import { EventEmitter } from "events"; export class MockDiscordClient { public guilds = new MockCollection<string, MockGuild>(); public user: MockUser; private testLoggedIn: boolean = false; - private testCallbacks: Map<string, () => void> = new Map(); + private testCallbacks: Map<string, (...data: any[]) => void> = new Map(); constructor() { const channels = [ @@ -30,10 +31,14 @@ export class MockDiscordClient { this.user = new MockUser("12345"); } - public on(event: string, callback: () => void) { + public on(event: string, callback: (...data: any[]) => void) { this.testCallbacks.set(event, callback); } + public emit(event: string, ...data: any[]) { + return this.testCallbacks.get(event).apply(this, data); + } + public async login(token: string): Promise<void> { if (token !== "passme") { throw new Error("Mock Discord Client only logins with the token 'passme'"); diff --git a/test/mocks/discordclientfactory.ts b/test/mocks/discordclientfactory.ts index 99bb5b2e47e6fd4ffc5becc86a9e5f1a6082cff6..489adba780b375bbdae808026258095a0a089ef0 100644 --- a/test/mocks/discordclientfactory.ts +++ b/test/mocks/discordclientfactory.ts @@ -1,8 +1,9 @@ import {MockDiscordClient} from "./discordclient"; export class DiscordClientFactory { + private botClient: MockDiscordClient = null; constructor(config: any, store: any) { - ; + } public init(): Promise<void> { @@ -10,6 +11,9 @@ export class DiscordClientFactory { } public getClient(userId?: string): Promise<MockDiscordClient> { - return Promise.resolve(new MockDiscordClient()); + if (userId == null && !this.botClient) { + this.botClient = new MockDiscordClient(); + } + return Promise.resolve(this.botClient); } } diff --git a/test/mocks/emoji.ts b/test/mocks/emoji.ts index 06c65350b86cd05a35754a25244c8382cc705970..c02734bdcce767512d9b2df8d2bb77b2830efd48 100644 --- a/test/mocks/emoji.ts +++ b/test/mocks/emoji.ts @@ -1,3 +1,3 @@ export class MockEmoji { - constructor (public id: string = "", public name = "") { } + constructor(public id: string = "", public name = "") { } } diff --git a/test/test_channelsyncroniser.ts b/test/test_channelsyncroniser.ts index bc583596afbb03629ea940f61ab894afcf65c5b5..01b7aabcd1c83e48f4355e8a3056c9f553e4c4b4 100644 --- a/test/test_channelsyncroniser.ts +++ b/test/test_channelsyncroniser.ts @@ -148,7 +148,7 @@ function CreateChannelSync(remoteChannels: any[] = []): ChannelSyncroniser { }, }; const discordbot: any = { - + }; const config = new DiscordBridgeConfig(); config.bridge.domain = "localhost"; @@ -172,9 +172,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.OnDelete(<any> chan).then(() => { + return channelSync.OnDelete(chan as any).then(() => { expect(REMOTECHANNEL_REMOVED).is.false; }); }); @@ -192,9 +192,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.OnDelete(<any> chan).then(() => { + return channelSync.OnDelete(chan as any).then(() => { expect(REMOTECHANNEL_REMOVED).is.true; }); }); @@ -213,9 +213,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetRoomIdsFromChannel(<any> chan).then((chans) => { + return channelSync.GetRoomIdsFromChannel(chan as any).then((chans) => { expect(chans.length).equals(1); expect(chans[0]).equals("!1:localhost"); }); @@ -249,9 +249,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetRoomIdsFromChannel(<any> chan).then((chans) => { + return channelSync.GetRoomIdsFromChannel(chan as any).then((chans) => { /* tslint:disable:no-magic-numbers */ expect(chans.length).equals(2); /* tslint:enable:no-magic-numbers */ @@ -263,7 +263,7 @@ describe("ChannelSyncroniser", () => { const chan = new MockChannel(); chan.id = "blah"; const channelSync = CreateChannelSync(); - expect(channelSync.GetRoomIdsFromChannel(<any> chan)).to.eventually.be.rejected; + expect(channelSync.GetRoomIdsFromChannel(chan as any)).to.eventually.be.rejected; }); }); describe("GetChannelUpdateState", () => { @@ -271,9 +271,9 @@ describe("ChannelSyncroniser", () => { const chan = new MockChannel(); chan.type = "text"; chan.id = "blah"; - + const channelSync = CreateChannelSync(); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.id).equals(chan.id); expect(state.mxChannels.length).equals(0); }); @@ -286,7 +286,7 @@ describe("ChannelSyncroniser", () => { chan.name = "newName"; chan.topic = "newTopic"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -301,9 +301,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].name).equals("[Discord] newGuild #newName"); expect(state.mxChannels[0].topic).equals("newTopic"); @@ -317,7 +317,7 @@ describe("ChannelSyncroniser", () => { chan.name = "newName"; chan.topic = "newTopic"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -330,9 +330,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].name).is.null; expect(state.mxChannels[0].topic).is.null; @@ -346,7 +346,7 @@ describe("ChannelSyncroniser", () => { chan.name = "newName"; chan.topic = "newTopic"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -361,9 +361,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].name).is.null; expect(state.mxChannels[0].topic).is.null; @@ -376,7 +376,7 @@ describe("ChannelSyncroniser", () => { chan.type = "text"; chan.id = "blah"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -389,9 +389,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].iconUrl).equals("https://cdn.discordapp.com/icons/654321/new_icon.png"); expect(state.mxChannels[0].iconId).equals("new_icon"); @@ -404,7 +404,7 @@ describe("ChannelSyncroniser", () => { chan.type = "text"; chan.id = "blah"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -417,9 +417,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].iconUrl).is.null; expect(state.mxChannels[0].iconId).is.null; @@ -432,7 +432,7 @@ describe("ChannelSyncroniser", () => { chan.type = "text"; chan.id = "blah"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -445,9 +445,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.GetChannelUpdateState(<any> chan).then((state) => { + return channelSync.GetChannelUpdateState(chan as any).then((state) => { expect(state.mxChannels.length).equals(1); expect(state.mxChannels[0].removeIcon).is.true; }); @@ -463,7 +463,7 @@ describe("ChannelSyncroniser", () => { chan.name = "newName"; chan.topic = "newTopic"; chan.guild = guild; - + const testStore = [ new Entry({ id: "1", @@ -480,9 +480,9 @@ describe("ChannelSyncroniser", () => { }, }), ]; - + const channelSync = CreateChannelSync(testStore); - return channelSync.OnUpdate(<any> chan).then((state) => { + return channelSync.OnUpdate(chan as any).then((state) => { expect(ROOM_NAME_SET).equals("[Discord] newGuild #newName"); expect(ROOM_TOPIC_SET).equals("newTopic"); expect(ROOM_AVATAR_SET).equals("avatarset"); diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index 1233fb299e4437b5b30d6de931c14851c4bf703a..a350b1784949c320af1cdf1fa9c093b155ca23ae 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -7,6 +7,8 @@ import { Log } from "../src/log"; import { MessageProcessorMatrixResult } from "../src/messageprocessor"; import { MockGuild } from "./mocks/guild"; import { MockMember } from "./mocks/member"; +import { DiscordBot } from "../src/bot"; +import { MockDiscordClient } from "./mocks/discordclient"; Chai.use(ChaiAsPromised); @@ -52,6 +54,9 @@ describe("DiscordBot", () => { bridge: { domain: "localhost", }, + limits: { + discordSendDelay: 50, + }, }; describe("run()", () => { it("should resolve when ready.", () => { @@ -140,6 +145,30 @@ describe("DiscordBot", () => { }); }); }); + describe("event:message", () => { + it("should delay messages so they arrive in order", async () => { + discordBot = new modDiscordBot.DiscordBot( + config, + mockBridge, + ); + let expected = 0; + discordBot.OnMessage = (msg: any) => { + assert.equal(msg.n, expected); + expected++; + return Promise.resolve(); + }; + const client: MockDiscordClient = (await discordBot.ClientFactory.getClient()) as MockDiscordClient; + discordBot.setBridge(mockBridge); + await discordBot.run(); + const ITERATIONS = 25; + const CHANID = 123; + // Send delay of 50ms, 2 seconds / 50ms - 5 for safety. + for (let i = 0; i < ITERATIONS; i++) { + client.emit("message", { n: i, channel: { id: CHANID} }); + } + await discordBot.discordMessageQueue[CHANID]; + }); + }); // describe("ProcessMatrixMsgEvent()", () => { // diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts index e70d16950bdd7e4c710576ee279c97b1b6eade71..beb033672d8afc01c5ed81dfeeee336110f7201e 100644 --- a/test/test_matrixeventprocessor.ts +++ b/test/test_matrixeventprocessor.ts @@ -36,7 +36,7 @@ const mxClient = { }; function createMatrixEventProcessor - (disableMentions: boolean = false, disableEveryone = false, disableHere = false): MatrixEventProcessor { + (disableMentions: boolean = false, disableEveryone = false, disableHere = false): MatrixEventProcessor { const bridge = { getClientFactory: () => { return { diff --git a/test/test_messageprocessor.ts b/test/test_messageprocessor.ts index 4d3bd3d615245c4d8fc8aa5e3560919711f24ba2..ccdb5bbfe14eb6366db53140c01b78563db5cb57 100644 --- a/test/test_messageprocessor.ts +++ b/test/test_messageprocessor.ts @@ -21,12 +21,12 @@ const bot = { describe("MessageProcessor", () => { describe("init", () => { it("constructor", () => { - const mp = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const mp = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); }); }); describe("FormatDiscordMessage", () => { it("processes plain text messages correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = []; msg.content = "Hello World!"; @@ -35,7 +35,7 @@ describe("MessageProcessor", () => { Chai.assert(result.formattedBody, "Hello World!"); }); it("processes markdown messages correctly.", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = []; msg.content = "Hello *World*!"; @@ -43,8 +43,8 @@ describe("MessageProcessor", () => { Chai.assert.equal(result.body, "Hello *World*!"); Chai.assert.equal(result.formattedBody, "<p>Hello <em>World</em>!</p>"); }); - it("processes non-discord markdown correctly.", async() => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + it("processes non-discord markdown correctly.", async () => { + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = []; msg.content = "> inb4 tests"; @@ -58,8 +58,8 @@ describe("MessageProcessor", () => { Chai.assert.equal(result.body, "[test](http://example.com)"); Chai.assert.equal(result.formattedBody, "<p>[test](<a href=\"http://example.com\">http://example.com</a>)</p>"); }); - it("processes discord-specific markdown correctly.", async() => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + it("processes discord-specific markdown correctly.", async () => { + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = []; msg.content = "_ italic _"; @@ -70,7 +70,7 @@ describe("MessageProcessor", () => { }); describe("FormatEmbeds", () => { it("should format embeds correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ { @@ -102,12 +102,12 @@ describe("MessageProcessor", () => { }); describe("FormatEdit", () => { it("should format basic edits appropriately", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const oldMsg = new Discord.Message(null, null, null); const newMsg = new Discord.Message(null, null, null); oldMsg.embeds = []; newMsg.embeds = []; - + // Content updated but not changed oldMsg.content = "a"; newMsg.content = "b"; @@ -118,12 +118,12 @@ describe("MessageProcessor", () => { }); it("should format markdown heavy edits apropriately", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const oldMsg = new Discord.Message(null, null, null); const newMsg = new Discord.Message(null, null, null); oldMsg.embeds = []; newMsg.embeds = []; - + // Content updated but not changed oldMsg.content = "a slice of **cake**"; newMsg.content = "*a* slice of cake"; @@ -135,10 +135,10 @@ describe("MessageProcessor", () => { }); }); - + describe("ReplaceMembers", () => { it("processes members missing from the guild correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, null); const msg = new Discord.Message(channel, null, null); @@ -147,7 +147,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Hello @_discord_12345:localhost"); }); it("processes members with usernames correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); guild._mockAddMember(new MockMember("12345", "TestUsername")); const channel = new Discord.TextChannel(guild, null); @@ -157,7 +157,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Hello TestUsername"); }); it("processes members with nickname correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); guild._mockAddMember(new MockMember("12345", "TestUsername", null, "TestNickname")); const channel = new Discord.TextChannel(guild, null); @@ -169,7 +169,7 @@ describe("MessageProcessor", () => { }); describe("ReplaceMembersPostmark", () => { it("processes members missing from the guild correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, null); const msg = new Discord.Message(channel, null, null); @@ -179,7 +179,7 @@ describe("MessageProcessor", () => { "Hello <a href=\"https://matrix.to/#/@_discord_12345:localhost\">@_discord_12345:localhost</a>"); }); it("processes members with usernames correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); guild._mockAddMember(new MockMember("12345", "TestUsername")); const channel = new Discord.TextChannel(guild, null); @@ -192,7 +192,7 @@ describe("MessageProcessor", () => { }); describe("ReplaceChannels", () => { it("processes unknown channel correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); const msg = new Discord.Message(channel, null, null); @@ -201,7 +201,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Hello #123456789"); }); it("processes channels correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); guild.channels.set("456", channel); @@ -213,7 +213,7 @@ describe("MessageProcessor", () => { }); describe("ReplaceChannelsPostmark", () => { it("processes unknown channel correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); const msg = new Discord.Message(channel, null, null); @@ -223,7 +223,7 @@ describe("MessageProcessor", () => { "Hello <a href=\"https://matrix.to/#/#_discord_123_123456789:localhost\">#123456789</a>"); }); it("processes channels correctly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); guild.channels.set("456", channel); @@ -236,7 +236,7 @@ describe("MessageProcessor", () => { }); describe("ReplaceEmoji", () => { it("processes unknown emoji correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); const msg = new Discord.Message(channel, null, null); @@ -245,7 +245,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Hello <:hello:123456789>"); }); it("processes emoji correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); guild.channels.set("456", channel); @@ -257,7 +257,7 @@ describe("MessageProcessor", () => { }); describe("ReplaceEmojiPostmark", () => { it("processes unknown emoji correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); const msg = new Discord.Message(channel, null, null); @@ -266,7 +266,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Hello <:hello:123456789>"); }); it("processes emoji correctly", async () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const guild: any = new MockGuild("123", []); const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"}); guild.channels.set("456", channel); @@ -278,7 +278,7 @@ describe("MessageProcessor", () => { }); describe("InsertEmbeds", () => { it("processes titleless embeds properly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { @@ -290,7 +290,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "\n\n----\nTestDescription"); }); it("processes urlless embeds properly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { @@ -303,7 +303,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "\n\n----\n##### TestTitle\nTestDescription"); }); it("processes linked embeds properly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { @@ -317,7 +317,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "\n\n----\n##### [TestTitle](testurl)\nTestDescription"); }); it("rejects titleless and descriptionless embeds", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { @@ -329,7 +329,7 @@ describe("MessageProcessor", () => { Chai.assert.equal(content, "Some content..."); }); it("processes multiple embeds properly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { @@ -351,7 +351,7 @@ describe("MessageProcessor", () => { ); }); it("inserts embeds properly", () => { - const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot); + const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), bot as DiscordBot); const msg = new Discord.Message(null, null, null); msg.embeds = [ new Discord.MessageEmbed(msg, { diff --git a/test/test_presencehandler.ts b/test/test_presencehandler.ts index d36335dbefd98b0180b61668cbd0855751fcfddb..c6c652cbcacde39c0d275d73c84d4600eb9f804c 100644 --- a/test/test_presencehandler.ts +++ b/test/test_presencehandler.ts @@ -33,49 +33,49 @@ const bot = { describe("PresenceHandler", () => { describe("init", () => { it("constructor", () => { - const handler = new PresenceHandler(<DiscordBot> bot); + const handler = new PresenceHandler(bot as DiscordBot); }); }); describe("Start", () => { it("should start without errors", () => { - const handler = new PresenceHandler(<DiscordBot> bot); + const handler = new PresenceHandler(bot as DiscordBot); handler.Start(INTERVAL); }); }); describe("Stop", () => { it("should stop without errors", () => { - const handler = new PresenceHandler(<DiscordBot> bot); + const handler = new PresenceHandler(bot as DiscordBot); handler.Start(INTERVAL); handler.Stop(); }); }); describe("EnqueueUser", () => { it("adds a user properly", () => { - const handler = new PresenceHandler(<DiscordBot> bot); + const handler = new PresenceHandler(bot as DiscordBot); const COUNT = 2; - handler.EnqueueUser(<any> new MockUser("abc", "def")); - handler.EnqueueUser(<any> new MockUser("123", "ghi")); + handler.EnqueueUser(new MockUser("abc", "def") as any); + handler.EnqueueUser(new MockUser("123", "ghi") as any); Chai.assert.equal(handler.QueueCount, COUNT); }); it("does not add duplicate users", () => { - const handler = new PresenceHandler(<DiscordBot> bot); - handler.EnqueueUser(<any> new MockUser("abc", "def")); - handler.EnqueueUser(<any> new MockUser("abc", "def")); + const handler = new PresenceHandler(bot as DiscordBot); + handler.EnqueueUser(new MockUser("abc", "def") as any); + handler.EnqueueUser(new MockUser("abc", "def") as any); Chai.assert.equal(handler.QueueCount, 1); }); it("does not add the bot user", () => { - const handler = new PresenceHandler(<DiscordBot> bot); - handler.EnqueueUser(<any> new MockUser("1234", "def")); + const handler = new PresenceHandler(bot as DiscordBot); + handler.EnqueueUser(new MockUser("1234", "def") as any); Chai.assert.equal(handler.QueueCount, 0); }); }); describe("DequeueUser", () => { it("removes users properly", () => { - const handler = new PresenceHandler(<DiscordBot> bot); + const handler = new PresenceHandler(bot as DiscordBot); const members = [ - <any> new MockUser("abc", "def"), - <any> new MockUser("def", "ghi"), - <any> new MockUser("ghi", "wew"), + new MockUser("abc", "def") as any, + new MockUser("def", "ghi") as any, + new MockUser("ghi", "wew") as any, ]; handler.EnqueueUser(members[0]); handler.EnqueueUser(members[1]); @@ -92,8 +92,8 @@ describe("PresenceHandler", () => { describe("ProcessUser", () => { it("processes an online user", () => { lastStatus = null; - const handler = new PresenceHandler(<DiscordBot> bot); - const member = <any> new MockUser("abc", "def"); + const handler = new PresenceHandler(bot as DiscordBot); + const member = new MockUser("abc", "def") as any; member.MockSetPresence(new Discord.Presence({ status: "online", })); @@ -104,8 +104,8 @@ describe("PresenceHandler", () => { }); it("processes an offline user", () => { lastStatus = null; - const handler = new PresenceHandler(<DiscordBot> bot); - const member = <any> new MockUser("abc", "def"); + const handler = new PresenceHandler(bot as DiscordBot); + const member = new MockUser("abc", "def") as any; member.MockSetPresence(new Discord.Presence({ status: "offline", })); @@ -117,8 +117,8 @@ describe("PresenceHandler", () => { }); it("processes an idle user", () => { lastStatus = null; - const handler = new PresenceHandler(<DiscordBot> bot); - const member = <any> new MockUser("abc", "def"); + const handler = new PresenceHandler(bot as DiscordBot); + const member = new MockUser("abc", "def") as any; member.MockSetPresence(new Discord.Presence({ status: "idle", })); @@ -129,8 +129,8 @@ describe("PresenceHandler", () => { }); it("processes an dnd user", () => { lastStatus = null; - const handler = new PresenceHandler(<DiscordBot> bot); - const member = <any> new MockUser("abc", "def"); + const handler = new PresenceHandler(bot as DiscordBot); + const member = new MockUser("abc", "def") as any; member.MockSetPresence(new Discord.Presence({ status: "dnd", })); @@ -151,8 +151,8 @@ describe("PresenceHandler", () => { }); it("processes a user playing games", () => { lastStatus = null; - const handler = new PresenceHandler(<DiscordBot> bot); - const member = <any> new MockUser("abc", "def"); + const handler = new PresenceHandler(bot as DiscordBot); + const member = new MockUser("abc", "def") as any; member.MockSetPresence(new Discord.Presence({ status: "online", game: new Discord.Game({name: "Test Game"}), diff --git a/test/test_usersyncroniser.ts b/test/test_usersyncroniser.ts index f63e4399827e0ec031867859a11ecc07edf51af5..c1daddc4c3d0d17e388e022f8a6bee38a7a9f0c5 100644 --- a/test/test_usersyncroniser.ts +++ b/test/test_usersyncroniser.ts @@ -126,7 +126,7 @@ function CreateUserSync(remoteUsers: any[] = []): UserSyncroniser { guild.channels.set("543345", chan as any); return chan; } - throw new Error("Channel not found"); + throw new Error("Channel not found"); }, GetGuilds: () => { return []; diff --git a/tools/chanfix.ts b/tools/chanfix.ts index 446f9d2e457cb89e91edcd9a4146fbdfc29a0ec2..3824c28e3225fa293ebe07412f7736f9fe0cd707 100644 --- a/tools/chanfix.ts +++ b/tools/chanfix.ts @@ -101,7 +101,7 @@ bridge.loadDatabases().catch((e) => { }); }).then((clientTmp: any) => { client = clientTmp; - + // first set update_icon to true if needed return bridge.getRoomStore().getEntriesByRemoteRoomData({ update_name: true, @@ -109,7 +109,7 @@ bridge.loadDatabases().catch((e) => { }); }).then((mxRoomEntries) => { const promiseList = []; - + mxRoomEntries.forEach((entry) => { if (entry.remote.get("plumbed")) { return; // skipping plumbed rooms @@ -125,7 +125,7 @@ bridge.loadDatabases().catch((e) => { }).then(() => { // now it is time to actually run the updates let promiseChain: Bluebird<any> = Bluebird.resolve(); - + let delay = config.limits.roomGhostJoinDelay; // we'll just re-use this client.guilds.forEach((guild) => { promiseChain = promiseChain.return(Bluebird.delay(delay).then(() => { diff --git a/tools/ghostfix.ts b/tools/ghostfix.ts index 139a104c406470d4e3b81e36f466397caf5658e1..94dbedc224a85ca6afd4e987e52584113a2dca13 100644 --- a/tools/ghostfix.ts +++ b/tools/ghostfix.ts @@ -114,7 +114,7 @@ bridge.loadDatabases().catch((e) => { }).then((clientTmp: any) => { client = clientTmp; let promiseChain: Bluebird<any> = Bluebird.resolve(); - + let delay = config.limits.roomGhostJoinDelay; client.guilds.forEach((guild) => { guild.channels.forEach((channel) => { diff --git a/tools/userClientTools.ts b/tools/userClientTools.ts index 8b09af9ec2f950b6538079b82640b441019fcc9a..94bd2ad9c3f548aa544a675a1e2b1d8c29649002 100644 --- a/tools/userClientTools.ts +++ b/tools/userClientTools.ts @@ -99,7 +99,7 @@ Please enter your Discord Token }); } -function addUserToken (userid: string, token: string): Bluebird<null> { +function addUserToken(userid: string, token: string): Bluebird<null> { const clientFactory = new DiscordClientFactory(discordstore); return clientFactory.getDiscordId(token).then((discordid: string) => { return discordstore.add_user_token(userid, discordid, token);