diff --git a/src/bot.ts b/src/bot.ts
index 44688901193671603f4b2513eaef93bd6b22fb3c..65a8968f8b7612dbe84837fde781ccd7b33656e1 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -28,7 +28,7 @@ import { UserSyncroniser } from "./usersyncroniser";
 import { ChannelSyncroniser } from "./channelsyncroniser";
 import { MatrixRoomHandler } from "./matrixroomhandler";
 import { Log } from "./log";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import * as mime from "mime";
 import { IMatrixEvent, IMatrixMediaInfo, IMatrixMessage } from "./matrixtypes";
 import { Appservice, Intent } from "matrix-bot-sdk";
@@ -40,6 +40,7 @@ import { Util } from "./util";
 const log = new Log("DiscordBot");
 
 const MIN_PRESENCE_UPDATE_DELAY = 250;
+const TYPING_TIMEOUT_MS = 30 * 1000;
 const CACHE_LIFETIME = 90000;
 
 // TODO: This is bad. We should be serving the icon from the own homeserver.
@@ -82,6 +83,7 @@ export class DiscordBot {
     /* Handles messages queued up to be sent to matrix from discord. */
     private discordMessageQueue: { [channelId: string]: Promise<void> };
     private channelLock: Lock<string>;
+    private typingTimers: Record<string, NodeJS.Timeout> = {}; // DiscordUser+channel -> Timeout
     constructor(
         private config: DiscordBridgeConfig,
         private bridge: Appservice,
@@ -134,10 +136,14 @@ export class DiscordBot {
         return this.provisioner;
     }
 
-    public GetIntentFromDiscordMember(member: Discord.GuildMember | Discord.User, webhookID?: string): Intent {
+    public GetIntentFromDiscordMember(member: Discord.GuildMember | Discord.PartialUser | Discord.User, webhookID: string|null = null): Intent {
         if (webhookID) {
             // webhookID and user IDs are the same, they are unique, so no need to prefix _webhook_
-            const name = member instanceof Discord.User ? member.username : member.user.username;
+            const name = member instanceof Discord.GuildMember ? member.user.username : member.username;
+            if (!name) {
+                log.error("Couldn't get intent for Discord member, name was null:", member);
+                throw Error("Couldn't get intent for Discord member, name was null");
+            }
             // TODO: We need to sanitize name
             return this.bridge.getIntentForSuffix(`${webhookID}_${Util.EscapeStringForUserId(name)}`);
         }
@@ -153,21 +159,16 @@ 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) => {
+            client.on("typingStart", async (channel, user) => {
                 try {
-                    await this.OnTyping(c, u, true);
+                    await this.OnTyping(channel, user, true);
                 } catch (err) { log.warning("Exception thrown while handling \"typingStart\" event", err); }
             });
-            client.on("typingStop", async (c, u) => {
-                try {
-                    await this.OnTyping(c, u, false);
-                } catch (err) { log.warning("Exception thrown while handling \"typingStop\" event", err); }
-            });
         }
         if (!this.config.bridge.disablePresence) {
-            client.on("presenceUpdate", (_, newMember: Discord.GuildMember) => {
+            client.on("presenceUpdate", (_, newPresence) => {
                 try {
-                    this.presenceHandler.EnqueueUser(newMember.user);
+                    this.presenceHandler.EnqueueUser(newPresence);
                 } catch (err) { log.warning("Exception thrown while handling \"presenceUpdate\" event", err); }
             });
         }
@@ -269,21 +270,37 @@ export class DiscordBot {
 
         client.on("userUpdate", async (_, user) => {
             try {
+                if (!(user instanceof Discord.User)) {
+                    log.warn(`Ignoring update for ${user.username}. User was partial.`);
+                    return;
+                }
                 await this.userSync.OnUpdateUser(user);
             } catch (err) { log.error("Exception thrown while handling \"userUpdate\" event", err); }
         });
-        client.on("guildMemberAdd", async (user) => {
+        client.on("guildMemberAdd", async (member) => {
             try {
-                await this.userSync.OnAddGuildMember(user);
+                if (!(member instanceof Discord.GuildMember)) {
+                    log.warn(`Ignoring update for ${member.guild.id} ${member.id}. User was partial.`);
+                    return;
+                }
+                await this.userSync.OnAddGuildMember(member);
             } catch (err) { log.error("Exception thrown while handling \"guildMemberAdd\" event", err); }
         });
-        client.on("guildMemberRemove", async (user) =>  {
+        client.on("guildMemberRemove", async (member) =>  {
             try {
-                await this.userSync.OnRemoveGuildMember(user);
+                if (!(member instanceof Discord.GuildMember)) {
+                    log.warn(`Ignoring update for ${member.guild.id} ${member.id}. User was partial.`);
+                    return;
+                }
+                await this.userSync.OnRemoveGuildMember(member);
             } catch (err) { log.error("Exception thrown while handling \"guildMemberRemove\" event", err); }
         });
         client.on("guildMemberUpdate", async (_, member) => {
             try {
+                if (!(member instanceof Discord.GuildMember)) {
+                    log.warn(`Ignoring update for ${member.guild.id} ${member.id}. User was partial.`);
+                    return;
+                }
                 await this.userSync.OnUpdateGuildMember(member);
             } catch (err) { log.error("Exception thrown while handling \"guildMemberUpdate\" event", err); }
         });
@@ -297,10 +314,10 @@ export class DiscordBot {
             if (!this.config.bridge.presenceInterval) {
                 this.config.bridge.presenceInterval = MIN_PRESENCE_UPDATE_DELAY;
             }
-            this.bot.guilds.forEach((guild) => {
-                guild.members.forEach((member) => {
+            this.bot.guilds.cache.forEach((guild) => {
+                guild.members.cache.forEach((member) => {
                     if (member.id !== this.GetBotId()) {
-                        this.presenceHandler.EnqueueUser(member.user);
+                        this.presenceHandler.EnqueueUser(member.user.presence);
                     }
                 });
             });
@@ -311,20 +328,21 @@ export class DiscordBot {
     }
 
     public GetBotId(): string {
-        return this.bot.user.id;
+        // TODO: What do we do here?
+        return this.bot.user!.id;
     }
 
     public GetGuilds(): Discord.Guild[] {
-        return this.bot.guilds.array();
+        return this.bot.guilds.cache.array();
     }
 
     public ThirdpartySearchForChannels(guildId: string, channelName: string): IThirdPartyLookup[] {
         if (channelName.startsWith("#")) {
             channelName = channelName.substr(1);
         }
-        if (this.bot.guilds.has(guildId) ) {
-            const guild = this.bot.guilds.get(guildId);
-            return guild!.channels.filter((channel) => {
+        if (this.bot.guilds.cache.has(guildId) ) {
+            const guild = this.bot.guilds.cache.get(guildId);
+            return guild!.channels.cache.filter((channel) => {
                 return channel.name.toLowerCase() === channelName.toLowerCase(); // Implement searching in the future.
             }).map((channel) => {
                 return {
@@ -347,14 +365,14 @@ export class DiscordBot {
         const hasSender = sender !== null && sender !== undefined;
         try {
             const client = await this.clientFactory.getClient(sender);
-            const guild = client.guilds.get(server);
+            const guild = await client.guilds.resolve(server);
             if (!guild) {
                 throw new Error(`Guild "${server}" not found`);
             }
-            const channel = guild.channels.get(room);
+            const channel = await guild.channels.resolve(room);
             if (channel && channel.type === "text") {
                 if (hasSender) {
-                    const permissions = channel.permissionsFor(guild.me);
+                    const permissions = guild.me && channel.permissionsFor(guild.me);
                     if (!permissions || !permissions.has("VIEW_CHANNEL") || !permissions.has("SEND_MESSAGES")) {
                         throw new Error(`Can't send into channel`);
                     }
@@ -363,8 +381,8 @@ export class DiscordBot {
                 this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
                 const lookupResult = new ChannelLookupResult();
                 lookupResult.channel = channel as Discord.TextChannel;
-                lookupResult.botUser = this.bot.user.id === client.user.id;
-                lookupResult.canSendEmbeds = client.user.bot; // only bots can send embeds
+                lookupResult.botUser = this.BotUserId === client.user?.id;
+                lookupResult.canSendEmbeds = client.user?.bot || false; // only bots can send embeds
                 return lookupResult;
             }
             throw new Error(`Channel "${room}" not found`);
@@ -402,7 +420,7 @@ export class DiscordBot {
         const chan = roomLookup.channel;
         const botUser = roomLookup.botUser;
         const embed = embedSet.messageEmbed;
-        const oldMsg = await chan.fetchMessage(editEventId);
+        const oldMsg = await chan.messages.fetch(editEventId);
         if (!oldMsg) {
             // old message not found, just sending this normally
             await this.send(embedSet, opts, roomLookup, event);
@@ -476,14 +494,16 @@ export class DiscordBot {
         let hook: Discord.Webhook | undefined;
         if (botUser) {
             const webhooks = await chan.fetchWebhooks();
-            hook = webhooks.filterArray((h) => h.name === "_matrix").pop();
+            hook = webhooks.filter((h) => h.name === "_matrix").first();
             // Create a new webhook if none already exists
             try {
                 if (!hook) {
                     hook = await chan.createWebhook(
                         "_matrix",
-                        MATRIX_ICON_URL,
-                        "Matrix Bridge: Allow rich user messages");
+                        {
+                            avatar: MATRIX_ICON_URL,
+                            reason: "Matrix Bridge: Allow rich user messages"
+                        });
                 }
             } catch (err) {
                // throw wrapError(err, Unstable.ForeignNetworkError, "Unable to create \"_matrix\" webhook");
@@ -502,11 +522,11 @@ export class DiscordBot {
                 MetricPeg.get.remoteCall("hook.send");
                 const embeds = this.prepareEmbedSetWebhook(embedSet);
                 msg = await hook.send(embed.description, {
-                    avatarURL: embed!.author!.icon_url,
+                    avatarURL: embed!.author!.iconURL,
                     embeds,
-                    files: opts.file ? [opts.file] : undefined,
+                    files: opts.files,
                     username: embed!.author!.name,
-                } as Discord.WebhookMessageOptions);
+                });
             } else {
                 opts.embed = this.prepareEmbedSetBot(embedSet);
                 msg = await chan.send("", opts);
@@ -547,7 +567,7 @@ export class DiscordBot {
             const result = await this.LookupRoom(storeEvent.GuildId, storeEvent.ChannelId, event.sender);
             const chan = result.channel;
 
-            const msg = await chan.fetchMessage(storeEvent.DiscordId);
+            const msg = await chan.messages.fetch(storeEvent.DiscordId);
             try {
                 this.channelLock.set(msg.channel.id);
                 await msg.delete();
@@ -567,10 +587,11 @@ export class DiscordBot {
         userId: Discord.Snowflake, guildId?: Discord.Snowflake,
     ): Promise<Discord.User|Discord.GuildMember|undefined> {
         try {
-            if (guildId && this.bot.guilds.has(guildId)) {
-                return await this.bot.guilds.get(guildId)!.fetchMember(userId);
+            const guild = guildId && await this.bot.guilds.fetch(guildId);
+            if (guild) {
+                return await guild.members.fetch(userId);
             }
-            return await this.bot.fetchUser(userId);
+            return await this.bot.users.fetch(userId);
         } catch (ex) {
             log.warn(`Could not fetch user data for ${userId} (guild: ${guildId})`);
             return undefined;
@@ -594,9 +615,9 @@ export class DiscordBot {
         if (!entry.remote) {
             throw Error("Room had no remote component");
         }
-        const guild = client.guilds.get(entry.remote!.get("discord_guild") as string);
+        const guild = await client.guilds.fetch(entry.remote!.get("discord_guild") as string);
         if (guild) {
-            const channel = client.channels.get(entry.remote!.get("discord_channel") as string);
+            const channel = await client.channels.fetch(entry.remote!.get("discord_channel") as string);
             if (channel) {
                 this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
                 return channel;
@@ -640,7 +661,7 @@ export class DiscordBot {
 
         if (member) {
             let rooms: string[] = [];
-            await Util.AsyncForEach(guild.channels.array(), async (channel) => {
+            await Util.AsyncForEach(guild.channels.cache.array(), async (channel) => {
                 if (channel.type !== "text" || !channel.members.has(member.id)) {
                     return;
                 }
@@ -698,13 +719,13 @@ export class DiscordBot {
         let res: Discord.Message;
         const botChannel = await this.GetChannelFromRoomId(roomId) as Discord.TextChannel;
         if (restore) {
-            await tchan.overwritePermissions(kickee,
+            await tchan.overwritePermissions([
                 {
-                  SEND_MESSAGES: null,
-                  VIEW_CHANNEL: null,
-                  /* tslint:disable-next-line no-any */
-              } as any, // XXX: Discord.js typings are wrong.
-                `Unbanned.`);
+                    id: kickee.id,
+                    allow: ['SEND_MESSAGES', 'VIEW_CHANNEL']
+                }],
+                `Unbanned.`
+            );
             this.channelLock.set(botChannel.id);
             res = await botChannel.send(
                 `${kickee} was unbanned from this channel by ${kicker}.`,
@@ -713,7 +734,7 @@ export class DiscordBot {
             this.channelLock.release(botChannel.id);
             return;
         }
-        const existingPerms = tchan.memberPermissions(kickee);
+        const existingPerms = tchan.permissionsFor(kickee);
         if (existingPerms && existingPerms.has(Discord.Permissions.FLAGS.VIEW_CHANNEL as number) === false ) {
             log.warn("User isn't allowed to read anyway.");
             return;
@@ -728,23 +749,24 @@ export class DiscordBot {
         this.channelLock.release(botChannel.id);
         log.info(`${word} ${kickee}`);
 
-        await tchan.overwritePermissions(kickee,
+        await tchan.overwritePermissions([
             {
-              SEND_MESSAGES: false,
-              VIEW_CHANNEL: false,
-            },
-            `Matrix user was ${word} by ${kicker}`);
+                id: kickee.id,
+                deny: ['SEND_MESSAGES', 'VIEW_CHANNEL']
+            }],
+            `Matrix user was ${word} by ${kicker}.`
+        );
         if (kickban === "leave") {
             // Kicks will let the user back in after ~30 seconds.
             setTimeout(async () => {
                 log.info(`Kick was lifted for ${kickee.displayName}`);
-                await tchan.overwritePermissions(kickee,
+                await tchan.overwritePermissions([
                     {
-                      SEND_MESSAGES: null,
-                      VIEW_CHANNEL: null,
-                      /* tslint:disable: no-any */
-                  } as any, // XXX: Discord.js typings are wrong.
-                    `Lifting kick since duration expired.`);
+                        id: kickee.id,
+                        deny: ['SEND_MESSAGES', 'VIEW_CHANNEL']
+                    }],
+                    `Lifting kick since duration expired.`
+                );
             }, this.config.room.kickFor);
         }
     }
@@ -768,11 +790,11 @@ export class DiscordBot {
         return embed.description += addText;
     }
 
-    private prepareEmbedSetBotAccount(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed | undefined {
+    private prepareEmbedSetBotAccount(embedSet: IMatrixEventProcessorResult): Discord.MessageEmbed | undefined {
         if (!embedSet.imageEmbed && !embedSet.replyEmbed) {
             return undefined;
         }
-        let sendEmbed = new Discord.RichEmbed();
+        let sendEmbed = new Discord.MessageEmbed();
         if (embedSet.imageEmbed) {
             if (!embedSet.replyEmbed) {
                 sendEmbed = embedSet.imageEmbed;
@@ -791,8 +813,8 @@ export class DiscordBot {
         return sendEmbed;
     }
 
-    private prepareEmbedSetWebhook(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed[] {
-        const embeds: Discord.RichEmbed[] = [];
+    private prepareEmbedSetWebhook(embedSet: IMatrixEventProcessorResult): Discord.MessageEmbed[] {
+        const embeds: Discord.MessageEmbed[] = [];
         if (embedSet.imageEmbed) {
             embeds.push(embedSet.imageEmbed);
         }
@@ -802,7 +824,7 @@ export class DiscordBot {
         return embeds;
     }
 
-    private prepareEmbedSetBot(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed {
+    private prepareEmbedSetBot(embedSet: IMatrixEventProcessorResult): Discord.MessageEmbed {
         const embed = embedSet.messageEmbed;
         if (embedSet.imageEmbed) {
             embed.setImage(embedSet.imageEmbed.image!.url);
@@ -840,7 +862,7 @@ export class DiscordBot {
         return true;
     }
 
-    private async OnTyping(channel: Discord.Channel, user: Discord.User, isTyping: boolean) {
+    private async OnTyping(channel: Discord.Channel, user: Discord.User|Discord.PartialUser, isTyping: boolean) {
         const rooms = await this.channelSync.GetRoomIdsFromChannel(channel);
         try {
             const intent = this.GetIntentFromDiscordMember(user);
@@ -848,6 +870,16 @@ export class DiscordBot {
             await Promise.all(rooms.map( async (roomId) => {
                 return intent.underlyingClient.setTyping(roomId, isTyping);
             }));
+            const typingKey = `${user.id}:${channel.id}`;
+            if (isTyping) {
+                if (this.typingTimers[typingKey]) {
+                    clearTimeout(this.typingTimers[typingKey]);
+                }
+                this.typingTimers[typingKey] = setTimeout(async () => {
+                    this.OnTyping(channel, user, false);
+                    delete this.typingTimers[typingKey];
+                }, TYPING_TIMEOUT_MS);
+            }
         } catch (err) {
             log.warn("Failed to send typing indicator.", err);
         }
@@ -862,7 +894,7 @@ export class DiscordBot {
             return; // Skip *our* messages
         }
         const chan = msg.channel as Discord.TextChannel;
-        if (msg.author.id === this.bot.user.id) {
+        if (msg.author.id === this.BotUserId) {
             // We don't support double bridging.
             log.verbose("Not reflecting bot's own messages");
             MetricPeg.get.requestOutcome(msg.id, true, "dropped");
@@ -871,7 +903,7 @@ export class DiscordBot {
         // Test for webhooks
         if (msg.webhookID) {
             const webhook = (await chan.fetchWebhooks())
-                            .filterArray((h) => h.name === "_matrix").pop();
+                            .filter((h) => h.name === "_matrix").first();
             if (webhook && msg.webhookID === webhook.id) {
                 // Filter out our own webhook messages.
                 log.verbose("Not reflecting own webhook messages");
@@ -947,7 +979,9 @@ export class DiscordBot {
                         evt.MatrixId = `${eventId};${room}`;
                         evt.DiscordId = msg.id;
                         evt.ChannelId = msg.channel.id;
-                        evt.GuildId = msg.guild.id;
+                        if (msg.guild) {
+                            evt.GuildId = msg.guild.id;
+                        }
                         await this.store.Insert(evt);
                     });
                 });
@@ -987,7 +1021,9 @@ export class DiscordBot {
                     evt.MatrixId = `${eventId};${room}`;
                     evt.DiscordId = msg.id;
                     evt.ChannelId = msg.channel.id;
-                    evt.GuildId = msg.guild.id;
+                    if (msg.guild) {
+                        evt.GuildId = msg.guild.id;
+                    }
                     await this.store.Insert(evt);
                 };
                 let res;
diff --git a/src/channelsyncroniser.ts b/src/channelsyncroniser.ts
index 2877f3d6a6c86a8d0ff1b69453bb4cfb23e6e16c..f03ef3d38e931a30146143a38372169760aa6b40 100644
--- a/src/channelsyncroniser.ts
+++ b/src/channelsyncroniser.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { DiscordBot } from "./bot";
 import { Util } from "./util";
 import { DiscordBridgeConfig, DiscordBridgeConfigChannelDeleteOptions } from "./config";
@@ -81,7 +81,7 @@ export class ChannelSyncroniser {
     public async OnGuildUpdate(guild: Discord.Guild, force = false) {
         log.verbose(`Got guild update for guild ${guild.id}`);
         const channelStates: IChannelState[] = [];
-        for (const [_, channel] of guild.channels) {
+        for (const [_, channel] of guild.channels.cache) {
             if (channel.type !== "text") {
                 continue; // not supported for now
             }
@@ -144,7 +144,7 @@ export class ChannelSyncroniser {
     }
 
     public async OnGuildDelete(guild: Discord.Guild) {
-        for (const [_, channel] of guild.channels) {
+        for (const [_, channel] of guild.channels.cache) {
             try {
                 await this.OnDelete(channel);
             } catch (e) {
diff --git a/src/clientfactory.ts b/src/clientfactory.ts
index 1b2adcfb706231d74d0667c0612962b2208d55c6..8379840e868073e472d5eb9cd75eef81a3687562 100644
--- a/src/clientfactory.ts
+++ b/src/clientfactory.ts
@@ -16,7 +16,7 @@ limitations under the License.
 
 import { DiscordBridgeConfigAuth } from "./config";
 import { DiscordStore } from "./store";
-import { Client as DiscordClient, TextChannel } from "discord.js";
+import { Client as DiscordClient, TextChannel } from "better-discord.js"
 import { Log } from "./log";
 import { MetricPeg } from "./metrics";
 
@@ -42,11 +42,10 @@ export class DiscordClientFactory {
         this.botClient = new DiscordClient({
             fetchAllMembers: true,
             messageCacheLifetime: 5,
-            sync: true,
         });
 
         try {
-            await this.botClient.login(this.config.botToken);
+            await this.botClient.login(this.config.botToken, true);
         } catch (err) {
             log.error("Could not login as the bot user. This is bad!", err);
             throw err;
@@ -58,16 +57,14 @@ export class DiscordClientFactory {
         const client = new DiscordClient({
             fetchAllMembers: false,
             messageCacheLifetime: 5,
-            sync: false,
         });
 
-        await client.login(token);
-        const id = client.user.id;
-
-        // This can be done asynchronously, because we don't need to block to return the id.
-        client.destroy().catch((err) => {
-            log.warn("Failed to destroy client ", id);
-        });
+        await client.login(token, false);
+        const id = client.user?.id;
+        client.destroy();
+        if (!id) {
+            throw Error('Client did not have a user object, cannot determine ID');
+        }
         return id;
     }
 
@@ -90,7 +87,6 @@ export class DiscordClientFactory {
         const client = new DiscordClient({
             fetchAllMembers: true,
             messageCacheLifetime: 5,
-            sync: true,
         });
 
         const jsLog = new Log("discord.js-ppt");
@@ -99,7 +95,7 @@ export class DiscordClientFactory {
         client.on("warn", (msg) => { jsLog.warn(msg); });
 
         try {
-            await client.login(token);
+            await client.login(token, false);
             log.verbose("Logged in. Storing ", userId);
             this.clients.set(userId, client);
             return client;
diff --git a/src/db/schema/dbschema.ts b/src/db/schema/dbschema.ts
index 132e7c003155f0175ddb8b8891e78d863273baea..9f215f1b6d6aa32783030406660cb3dbf36dfed9 100644
--- a/src/db/schema/dbschema.ts
+++ b/src/db/schema/dbschema.ts
@@ -15,7 +15,6 @@ limitations under the License.
 */
 
 import { DiscordStore } from "../../store";
-import { DiscordBridgeConfigDatabase } from "../../config";
 export interface IDbSchema {
     description: string;
     run(store: DiscordStore): Promise<null|void|Error|Error[]>;
diff --git a/src/db/schema/v3.ts b/src/db/schema/v3.ts
index a24a13943ea97285fdccd25b7456d98b518e2043..f8c17265c76106708954a83dd6979f75220afe47 100644
--- a/src/db/schema/v3.ts
+++ b/src/db/schema/v3.ts
@@ -78,8 +78,8 @@ directory.`);
             log.info("Moving ", row.userId);
             try {
                 const client = await clientFactory.getClient(row.token);
-                const dId = client.user.id;
-                if (dId === null) {
+                const dId = client.user?.id;
+                if (!dId) {
                     continue;
                 }
                 log.verbose("INSERT INTO discord_id_token.");
diff --git a/src/db/schema/v8.ts b/src/db/schema/v8.ts
index b9db732198542abd170b000dcde8da28e5fc1cf8..575869ab6b3ee1b588e358ac747c5604163e7f5d 100644
--- a/src/db/schema/v8.ts
+++ b/src/db/schema/v8.ts
@@ -17,8 +17,6 @@ limitations under the License.
 import {IDbSchema} from "./dbschema";
 import {DiscordStore} from "../../store";
 import { Log } from "../../log";
-import { RemoteStoreRoom, MatrixStoreRoom } from "../roomstore";
-const log = new Log("SchemaV8");
 
 export class Schema implements IDbSchema {
     public description = "create room store tables";
diff --git a/src/db/schema/v9.ts b/src/db/schema/v9.ts
index abb63e4448d4f3dd5c4d6b56e8a2973d5e5d296c..fc9e270d96c25cfcfcc74f54892e0dbc788f335c 100644
--- a/src/db/schema/v9.ts
+++ b/src/db/schema/v9.ts
@@ -16,7 +16,6 @@ limitations under the License.
 
 import { IDbSchema } from "./dbschema";
 import { DiscordStore } from "../../store";
-import { Log } from "../../log";
 
 export class Schema implements IDbSchema {
     public description = "create user store tables";
diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts
index 7606383448f242eb16a307d9ce58dcea533e5fc6..978433b9249e07784eb3b5f8a260dacd172a6f3c 100644
--- a/src/discordcommandhandler.ts
+++ b/src/discordcommandhandler.ts
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import { DiscordBot } from "./bot";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from "./util";
 import { Log } from "./log";
 import { Appservice } from "matrix-bot-sdk";
@@ -34,6 +34,12 @@ export class DiscordCommandHandler {
             await msg.channel.send("**ERROR:** only available for guild channels");
             return;
         }
+        if (!msg.member) {
+            await msg.channel.send("**ERROR:** could not determine message member");
+            return;
+        }
+
+        const discordMember = msg.member;
 
         const intent = this.bridge.botIntent;
 
@@ -43,7 +49,7 @@ export class DiscordCommandHandler {
                 params: [],
                 permission: "MANAGE_WEBHOOKS",
                 run: async () => {
-                    if (await this.discord.Provisioner.MarkApproved(chan, msg.member, true)) {
+                    if (await this.discord.Provisioner.MarkApproved(chan, discordMember, true)) {
                         return "Thanks for your response! The matrix bridge has been approved";
                     } else {
                         return "Thanks for your response, however" +
@@ -62,7 +68,7 @@ export class DiscordCommandHandler {
                 params: [],
                 permission: "MANAGE_WEBHOOKS",
                 run: async () => {
-                    if (await this.discord.Provisioner.MarkApproved(chan, msg.member, false)) {
+                    if (await this.discord.Provisioner.MarkApproved(chan, discordMember, false)) {
                         return "Thanks for your response! The matrix bridge has been declined";
                     } else {
                         return "Thanks for your response, however" +
@@ -105,7 +111,7 @@ export class DiscordCommandHandler {
             if (!Array.isArray(permission)) {
                 permission = [permission];
             }
-            return permission.every((p) => msg.member.hasPermission(p as Discord.PermissionResolvable));
+            return permission.every((p) => discordMember.hasPermission(p as Discord.PermissionResolvable));
         };
 
         const reply = await Util.ParseCommand("!matrix", msg.content, actions, parameters, permissionCheck);
@@ -115,7 +121,7 @@ export class DiscordCommandHandler {
     private ModerationActionGenerator(discordChannel: Discord.TextChannel, funcKey: "kick"|"ban"|"unban") {
         return async ({name}) => {
             let allChannelMxids: string[] = [];
-            await Promise.all(discordChannel.guild.channels.map(async (chan) => {
+            await Promise.all(discordChannel.guild.channels.cache.map(async (chan) => {
                 try {
                     const chanMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(chan);
                     allChannelMxids = allChannelMxids.concat(chanMxids);
diff --git a/src/discordmessageprocessor.ts b/src/discordmessageprocessor.ts
index 95cbc0d44af5134924bd53d4b8839f255d340017..13ed3d8fb2ea7197f82ebca19cdaebf3e64c6bf8 100644
--- a/src/discordmessageprocessor.ts
+++ b/src/discordmessageprocessor.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { DiscordBot } from "./bot";
 import { Log } from "./log";
 import {
@@ -54,7 +54,7 @@ export class DiscordMessageProcessor {
     private getParserCallbacks(msg: Discord.Message): IDiscordMessageParserCallbacks {
         return {
             getChannel: async (id: string) => {
-                const channel = msg.guild.channels.get(id);
+                const channel = await msg.guild?.channels.resolve(id);
                 if (!channel) {
                     return null;
                 }
@@ -77,7 +77,7 @@ export class DiscordMessageProcessor {
                 return null;
             },
             getUser: async (id: string) => {
-                const member = msg.guild.members.get(id);
+                const member = await msg.guild?.members.fetch(id);
                 const mxid = `@_discord_${id}:${this.domain}`;
                 const name = member ? member.displayName : mxid;
                 return {
diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts
index 3d266b0fdfde51ee595f7b998ea33f10e0860a63..7af39573797f407fdb324bd52e1ea1a3bba1cfe0 100644
--- a/src/matrixcommandhandler.ts
+++ b/src/matrixcommandhandler.ts
@@ -20,7 +20,7 @@ import { DiscordBridgeConfig } from "./config";
 import { IMatrixEvent } from "./matrixtypes";
 import { Provisioner } from "./provisioner";
 import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from "./util";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { Appservice } from "matrix-bot-sdk";
 import * as markdown from "discord-markdown";
 import { IRoomStoreEntry } from "./db/roomstore";
diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts
index 388fe1521f6a78fce94bfc3ed43d2b1b40e8c8f7..f1d66d372211cee4350d4ccb713b6655c4d64db4 100644
--- a/src/matrixeventprocessor.ts
+++ b/src/matrixeventprocessor.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { DiscordBot } from "./bot";
 import { DiscordBridgeConfig } from "./config";
 import { Util, wrapError } from "./util";
@@ -54,9 +54,9 @@ export class MatrixEventProcessorOpts {
 }
 
 export interface IMatrixEventProcessorResult {
-    messageEmbed: Discord.RichEmbed;
-    replyEmbed?: Discord.RichEmbed;
-    imageEmbed?: Discord.RichEmbed;
+    messageEmbed: Discord.MessageEmbed;
+    replyEmbed?: Discord.MessageEmbed;
+    imageEmbed?: Discord.MessageEmbed;
 }
 
 export class MatrixEventProcessor {
@@ -204,9 +204,9 @@ export class MatrixEventProcessor {
         if (typeof(file) === "string") {
             embedSet.messageEmbed.description += " " + file;
         } else if ((file as Discord.FileOptions).name && (file as Discord.FileOptions).attachment) {
-            opts.file = file as Discord.FileOptions;
+            opts.files = [file as Discord.FileOptions];
         } else {
-            embedSet.imageEmbed = file as Discord.RichEmbed;
+            embedSet.imageEmbed = file as Discord.MessageEmbed;
         }
 
     // Throws an `Unstable.ForeignNetworkError` when sending the message fails.
@@ -303,7 +303,7 @@ export class MatrixEventProcessor {
             body = await this.matrixMsgProcessor.FormatMessage(content as IMatrixMessage, channel.guild, params);
         }
 
-        const messageEmbed = new Discord.RichEmbed();
+        const messageEmbed = new Discord.MessageEmbed();
         messageEmbed.setDescription(body);
         await this.SetEmbedAuthor(messageEmbed, event.sender, profile);
         const replyEmbed = getReply ? (await this.GetEmbedForReply(event, channel)) : undefined;
@@ -327,7 +327,7 @@ export class MatrixEventProcessor {
         event: IMatrixEvent,
         mxClient: MatrixClient,
         sendEmbeds: boolean = false,
-    ): Promise<string|Discord.FileOptions|Discord.RichEmbed> {
+    ): Promise<string|Discord.FileOptions|Discord.MessageEmbed> {
         if (!this.HasAttachment(event)) {
             return "";
         }
@@ -361,7 +361,7 @@ export class MatrixEventProcessor {
             }
         }
         if (sendEmbeds && event.content.info.mimetype.split("/")[0] === "image") {
-            return new Discord.RichEmbed()
+            return new Discord.MessageEmbed()
                 .setImage(url);
         }
         return `[${name}](${url})`;
@@ -370,7 +370,7 @@ export class MatrixEventProcessor {
     public async GetEmbedForReply(
         event: IMatrixEvent,
         channel: Discord.TextChannel,
-    ): Promise<Discord.RichEmbed|undefined> {
+    ): Promise<Discord.MessageEmbed|undefined> {
         if (!event.content) {
             event.content = {};
         }
@@ -416,7 +416,7 @@ export class MatrixEventProcessor {
             log.warn("Failed to handle reply, showing a unknown embed:", ex);
         }
         // For some reason we failed to get the event, so using fallback.
-        const embed = new Discord.RichEmbed();
+        const embed = new Discord.MessageEmbed();
         embed.setDescription("Reply with unknown content");
         embed.setAuthor("Unknown");
         return embed;
@@ -487,7 +487,7 @@ export class MatrixEventProcessor {
         return hasAttachment;
     }
 
-    private async SetEmbedAuthor(embed: Discord.RichEmbed, sender: string, profile?: {
+    private async SetEmbedAuthor(embed: Discord.MessageEmbed, sender: string, profile?: {
         displayname: string,
         avatar_url: string|undefined }) {
         let displayName = sender;
@@ -500,13 +500,13 @@ export class MatrixEventProcessor {
             if (userOrMember instanceof Discord.User) {
                 embed.setAuthor(
                     userOrMember.username,
-                    userOrMember.avatarURL,
+                    userOrMember.avatarURL() || undefined,
                 );
                 return;
             } else if (userOrMember instanceof Discord.GuildMember) {
                 embed.setAuthor(
                     userOrMember.displayName,
-                    userOrMember.user.avatarURL,
+                    userOrMember.user.avatarURL() || undefined,
                 );
                 return;
             }
diff --git a/src/matrixmessageprocessor.ts b/src/matrixmessageprocessor.ts
index eac3d67564db9b156254ad1f19053ea116c73bfd..2c855c587c20b9c2118d18cf417b84a48dd0fb07 100644
--- a/src/matrixmessageprocessor.ts
+++ b/src/matrixmessageprocessor.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { IMatrixMessage } from "./matrixtypes";
 import * as Parser from "node-html-parser";
 import { Util } from "./util";
@@ -86,7 +86,8 @@ export class MatrixMessageProcessor {
             getChannelId: async (mxid: string) => {
                 const CHANNEL_REGEX = /^#_discord_[0-9]*_([0-9]*):/;
                 const match = mxid.match(CHANNEL_REGEX);
-                if (!match || !guild.channels.get(match[1])) {
+                const channel = match && await guild.channels.resolve(match[1]);
+                if (!channel) {
                     /*
                     This isn't formatted in #_discord_, so let's fetch the internal room ID
                     and see if it is still a bridged room!
@@ -103,26 +104,27 @@ export class MatrixMessageProcessor {
                     }
                     return null;
                 }
-                return match[1];
+                return match && match[1] || null;
             },
             getEmoji: async (mxc: string, name: string) => {
-                let emoji: Discord.Emoji | null = null;
+                let emoji: {id: string, animated: boolean, name: string} | null = null;
                 try {
                     const emojiDb = await this.bot.GetEmojiByMxc(mxc);
                     const id = emojiDb.EmojiId;
-                    emoji = guild.emojis.find((e) => e.id === id);
+                    emoji = await guild.emojis.resolve(id);
                 } catch (e) {
                     emoji = null;
                 }
                 if (!emoji) {
-                    emoji = guild.emojis.find((e) => e.name === name);
+                    emoji = await guild.emojis.resolve(name);
                 }
                 return emoji;
             },
             getUserId: async (mxid: string) => {
                 const USER_REGEX = /^@_discord_([0-9]*)/;
                 const match = mxid.match(USER_REGEX);
-                if (!match || !guild.members.get(match[1])) {
+                const member = match && await guild.members.fetch(match[1]);
+                if (!match || member) {
                     return null;
                 }
                 return match[1];
diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts
index e101ddecd50b829e92163b8b8339546c9df27799..ffe225282757e664e7e47987ac16e36862781a1d 100644
--- a/src/matrixroomhandler.ts
+++ b/src/matrixroomhandler.ts
@@ -17,7 +17,7 @@ limitations under the License.
 import { DiscordBot, IThirdPartyLookup } from "./bot";
 import { DiscordBridgeConfig } from "./config";
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { Util } from "./util";
 import { Provisioner } from "./provisioner";
 import { Log } from "./log";
diff --git a/src/presencehandler.ts b/src/presencehandler.ts
index 6a8b602eb1f9b9f665cae5f48bec173e6f16d6d1..79018809f5e0d29e48550e64e2abe0d76b919eca 100644
--- a/src/presencehandler.ts
+++ b/src/presencehandler.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { User, Presence } from "discord.js";
+import { User, Presence } from "better-discord.js"
 import { DiscordBot } from "./bot";
 import { Log } from "./log";
 import { MetricPeg } from "./metrics";
@@ -32,7 +32,7 @@ interface IMatrixPresence {
 }
 
 export class PresenceHandler {
-    private presenceQueue: User[];
+    private presenceQueue: Presence[];
     private interval: NodeJS.Timeout | null;
     constructor(private bot: DiscordBot) {
         this.presenceQueue = [];
@@ -62,17 +62,17 @@ export class PresenceHandler {
         this.interval = null;
     }
 
-    public EnqueueUser(user: User) {
-        if (user.id !== this.bot.GetBotId() && this.presenceQueue.find((u) => u.id === user.id) === undefined) {
-            log.verbose(`Adding ${user.id} (${user.username}) to the presence queue`);
-            this.presenceQueue.push(user);
+    public EnqueueUser(presence: Presence) {
+        if (presence.userID !== this.bot.GetBotId() && this.presenceQueue.find((u) => u.userID === presence.userID) === undefined) {
+            log.verbose(`Adding ${presence.userID} (${presence.user?.username}) to the presence queue`);
+            this.presenceQueue.push(presence);
             MetricPeg.get.setPresenceCount(this.presenceQueue.length);
         }
     }
 
     public DequeueUser(user: User) {
         const index = this.presenceQueue.findIndex((item) => {
-            return user.id === item.id;
+            return user.id === item.userID;
         });
         if (index !== -1) {
             this.presenceQueue.splice(index, 1);
@@ -84,20 +84,23 @@ export class PresenceHandler {
         }
     }
 
-    public async ProcessUser(user: User): Promise<boolean> {
-        const status = this.getUserPresence(user.presence);
-        await this.setMatrixPresence(user, status);
+    public async ProcessUser(presence: Presence): Promise<boolean> {
+        if (!presence.user) {
+            return true;
+        }
+        const status = this.getUserPresence(presence);
+        await this.setMatrixPresence(presence.user, status);
         return status.ShouldDrop;
     }
 
     private async processIntervalThread() {
-        const user = this.presenceQueue.shift();
-        if (user) {
-            const proccessed = await this.ProcessUser(user);
+        const presence = this.presenceQueue.shift();
+        if (presence) {
+            const proccessed = await this.ProcessUser(presence);
             if (!proccessed) {
-                this.presenceQueue.push(user);
+                this.presenceQueue.push(presence);
             } else {
-                log.verbose(`Dropping ${user.id} from the presence queue.`);
+                log.verbose(`Dropping ${presence.userID} from the presence queue.`);
                 MetricPeg.get.setPresenceCount(this.presenceQueue.length);
             }
         }
@@ -106,10 +109,13 @@ export class PresenceHandler {
     private getUserPresence(presence: Presence): PresenceHandlerStatus {
         const status = new PresenceHandlerStatus();
 
-        if (presence.game) {
-            status.StatusMsg = `${presence.game.streaming ? "Streaming" : "Playing"} ${presence.game.name}`;
-            if (presence.game.url) {
-                status.StatusMsg += ` | ${presence.game.url}`;
+        // How do we show multiple activities?
+        if (presence.activities[0]) {
+            const activity = presence.activities[0];
+            const type = activity.type[0] + activity.type.substring(1).toLowerCase(); // STREAMING -> Streaming;
+            status.StatusMsg = `${type} ${activity.name}`;
+            if (activity.url) {
+                status.StatusMsg += ` | ${activity.url}`;
             }
         }
 
diff --git a/src/provisioner.ts b/src/provisioner.ts
index 77cd3cc0a73448d41e167ccb2fd7d2c00206e76c..0afb3d97a4cf76b39c09c13aaf3921aadb467fed 100644
--- a/src/provisioner.ts
+++ b/src/provisioner.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { DbRoomStore, RemoteStoreRoom, MatrixStoreRoom } from "./db/roomstore";
 import { ChannelSyncroniser } from "./channelsyncroniser";
 import { Log } from "./log";
diff --git a/src/usersyncroniser.ts b/src/usersyncroniser.ts
index 5633574cedd88e703a3bcca6ce3905d72134fff7..124907774dc21287f544d3e130eb86aa9d16668c 100644
--- a/src/usersyncroniser.ts
+++ b/src/usersyncroniser.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { User, GuildMember } from "discord.js";
+import { User, GuildMember } from "better-discord.js"
 import { DiscordBot } from "./bot";
 import { Util } from "./util";
 import { DiscordBridgeConfig } from "./config";
@@ -256,8 +256,10 @@ export class UserSyncroniser {
             log.verbose(`Could not find user in remote user store.`);
             userState.createUser = true;
             userState.displayName = displayName;
-            userState.avatarUrl = discordUser.avatarURL;
-            userState.avatarId = discordUser.avatar;
+            if (discordUser.avatar) {
+                userState.avatarUrl = discordUser.avatarURL();
+                userState.avatarId = discordUser.avatar;
+            }
             return userState;
         }
 
@@ -268,13 +270,13 @@ export class UserSyncroniser {
         }
 
         const oldAvatarUrl = remoteUser.avatarurl;
-        if (oldAvatarUrl !== discordUser.avatarURL) {
+        if (oldAvatarUrl !== discordUser.avatarURL()) {
             log.verbose(`User ${discordUser.id} avatarurl should be updated`);
-            if (discordUser.avatarURL !== null) {
-                userState.avatarUrl = discordUser.avatarURL;
+            if (discordUser.avatar) {
+                userState.avatarUrl = discordUser.avatarURL();
                 userState.avatarId = discordUser.avatar;
             } else {
-                userState.removeAvatar = oldAvatarUrl !== null;
+                userState.removeAvatar = userState.avatarId === null;
             }
         }
 
@@ -296,7 +298,7 @@ export class UserSyncroniser {
             displayName: name,
             id: newMember.id,
             mxUserId: `@_discord_${newMember.id}:${this.config.bridge.domain}`,
-            roles: newMember.roles.map((role) => { return {
+            roles: newMember.roles.cache.map((role) => { return {
                 color: role.color,
                 name: role.name,
                 position: role.position,
diff --git a/src/util.ts b/src/util.ts
index 46b30d521d98600132c51d8fd6cda7b3ab5b71fe..e9edffba18f3cf786d4927bda93eec9b591ca8ff 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -17,7 +17,7 @@ limitations under the License.
 import * as http from "http";
 import * as https from "https";
 import { Buffer } from "buffer";
-import { Permissions } from "discord.js";
+import { Permissions } from "better-discord.js"
 import { DiscordBridgeConfig } from "./config";
 import { IMatrixEvent } from "./matrixtypes";
 
@@ -108,8 +108,7 @@ export class Util {
 
     public static GetBotLink(config: DiscordBridgeConfig): string {
         /* tslint:disable:no-bitwise */
-        const perms = Permissions.FLAGS.READ_MESSAGES! |
-            Permissions.FLAGS.SEND_MESSAGES! |
+        const perms = Permissions.FLAGS.SEND_MESSAGES! |
             Permissions.FLAGS.CHANGE_NICKNAME! |
             Permissions.FLAGS.CONNECT! |
             Permissions.FLAGS.SPEAK! |
diff --git a/test/mocks/channel.ts b/test/mocks/channel.ts
index 438bdc568c4db8b49cdc669a99f912f81805b233..f209cc2cefee3d4d351f781b6a7a047f49dade7f 100644
--- a/test/mocks/channel.ts
+++ b/test/mocks/channel.ts
@@ -16,7 +16,8 @@ limitations under the License.
 
 import {MockMember} from "./member";
 import {MockCollection} from "./collection";
-import {Permissions, PermissionResolvable} from "discord.js";
+import {Permissions, PermissionResolvable, TextChannel} from "better-discord.js"
+import { MockGuild } from "./guild";
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
@@ -40,3 +41,16 @@ export class MockChannel {
         return new Permissions(Permissions.FLAGS.MANAGE_WEBHOOKS as PermissionResolvable);
     }
 }
+
+export class MockTextChannel extends TextChannel {
+    constructor(guild?: MockGuild, channelData: any = {}) {
+        // Mock the nessacery
+        super(guild || {
+            client: {
+                options: { 
+                    messageCacheMaxSize: -1,
+                }
+            }
+        } as any, channelData);
+    }
+}
diff --git a/test/mocks/collection.ts b/test/mocks/collection.ts
index ad830450b01ef44724ff4ec26a254112a035804f..d83bfe3cfdbcf23d198c9a4406d035547314be9e 100644
--- a/test/mocks/collection.ts
+++ b/test/mocks/collection.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { Collection } from "discord.js";
+import { Collection } from "better-discord.js"
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
diff --git a/test/mocks/guild.ts b/test/mocks/guild.ts
index 228decc0ca384b8f03d485946ba7f412272c5a48..9f1c394fa02ce5e70cf4dbaa963be17e9e9d9675 100644
--- a/test/mocks/guild.ts
+++ b/test/mocks/guild.ts
@@ -17,7 +17,7 @@ limitations under the License.
 import {MockCollection} from "./collection";
 import {MockMember} from "./member";
 import {MockEmoji} from "./emoji";
-import {Channel} from "discord.js";
+import {Channel} from "better-discord.js"
 import {MockRole} from "./role";
 
 // we are a test file and thus need those
@@ -39,6 +39,14 @@ export class MockGuild {
         });
     }
 
+    public get client() {
+        return {
+            options: { 
+                messageCacheMaxSize: -1,
+            }
+        };
+    }
+
     public async fetchMember(id: string): Promise<MockMember|Error> {
         if (this.members.has(id)) {
             return this.members.get(id)!;
diff --git a/test/mocks/member.ts b/test/mocks/member.ts
index df3b22cb30848e8af65a2e8b914402d75529b08e..8e9c06cc857eb7c7338136aa92aa947587dc071e 100644
--- a/test/mocks/member.ts
+++ b/test/mocks/member.ts
@@ -17,7 +17,7 @@ limitations under the License.
 import {MockCollection} from "./collection";
 import {MockUser} from "./user";
 import {MockRole} from "./role";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
@@ -30,7 +30,11 @@ export class MockMember {
     public roles = new MockCollection<string, MockRole>();
     constructor(id: string, username: string, public guild: any = null, public displayName: string = username) {
         this.id = id;
-        this.presence = new Discord.Presence({}, {} as any);
+        this.presence = new Discord.Presence({} as any, {
+            user: {
+                id: this.id,
+            }
+        });
         this.user = new MockUser(this.id, username);
         this.nickname = displayName;
     }
diff --git a/test/mocks/message.ts b/test/mocks/message.ts
index 148a743e81b98fc7a77743f68868d30a14104426..44b2aa6f3186663742346ae6918f57d40712ec31 100644
--- a/test/mocks/message.ts
+++ b/test/mocks/message.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { MockUser } from "./user";
 import { MockCollection } from "./collection";
 
diff --git a/test/mocks/user.ts b/test/mocks/user.ts
index ef127130107013e0407db9a28dcea1c4b9e5134e..83c6f1f2c9228e4eb2ec5b36824162a20133e7ea 100644
--- a/test/mocks/user.ts
+++ b/test/mocks/user.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { Presence } from "discord.js";
+import { Presence } from "better-discord.js"
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
diff --git a/test/test_clientfactory.ts b/test/test_clientfactory.ts
index eb9ab99be53b4b54b3fd03035755dd97347ebe2c..ad2a29dca396bf2bfa26af8f620b88fde2632d03 100644
--- a/test/test_clientfactory.ts
+++ b/test/test_clientfactory.ts
@@ -22,7 +22,7 @@ import { DiscordBridgeConfigAuth } from "../src/config";
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
 
 const DiscordClientFactory = Proxyquire("../src/clientfactory", {
-    "discord.js": { Client: require("./mocks/discordclient").MockDiscordClient },
+    "better-discord.js": { Client: require("./mocks/discordclient").MockDiscordClient },
 }).DiscordClientFactory;
 
 const STORE = {
diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts
index f8e6e181660bf1e61a2364546582aecefb71b005..f2d21ade1ca0467ccfbfbd62dad37953001a05a2 100644
--- a/test/test_discordbot.ts
+++ b/test/test_discordbot.ts
@@ -16,7 +16,7 @@ limitations under the License.
 
 import { expect } from "chai";
 import * as Proxyquire from "proxyquire";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 
 import { MockGuild } from "./mocks/guild";
 import { MockMember } from "./mocks/member";
@@ -25,6 +25,7 @@ import { MockMessage } from "./mocks/message";
 import { Util } from "../src/util";
 import { AppserviceMock } from "./mocks/appservicemock";
 import { MockUser } from "./mocks/user";
+import { MockTextChannel } from "./mocks/channel";
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
@@ -103,7 +104,7 @@ describe("DiscordBot", () => {
         });
     });
     describe("OnMessage()", () => {
-        const channel = new Discord.TextChannel({} as any, {} as any);
+        const channel = new MockTextChannel();
         const msg = new MockMessage(channel);
         const author = new MockUser("11111");
         let HANDLE_COMMAND = false;
@@ -291,7 +292,7 @@ describe("DiscordBot", () => {
 
             const guild: any = new MockGuild("123", []);
             guild._mockAddMember(new MockMember("12345", "TestUsername"));
-            const channel = new Discord.TextChannel(guild, {} as any);
+            const channel = new MockTextChannel(guild);
             const oldMsg = new MockMessage(channel) as any;
             const newMsg = new MockMessage(channel) as any;
             oldMsg.embeds = [];
@@ -318,7 +319,7 @@ describe("DiscordBot", () => {
 
             const guild: any = new MockGuild("123", []);
             guild._mockAddMember(new MockMember("12345", "TestUsername"));
-            const channel = new Discord.TextChannel(guild, {} as any);
+            const channel = new MockTextChannel(guild);
             const oldMsg = new MockMessage(channel) as any;
             const newMsg = new MockMessage(channel) as any;
             oldMsg.embeds = [];
@@ -362,7 +363,7 @@ describe("DiscordBot", () => {
 
             const guild: any = new MockGuild("123", []);
             guild._mockAddMember(new MockMember("12345", "TestUsername"));
-            const channel = new Discord.TextChannel(guild, {} as any);
+            const channel = new MockTextChannel(guild, {} as any);
             const oldMsg = new MockMessage(channel) as any;
             const newMsg = new MockMessage(channel) as any;
             oldMsg.embeds = [];
diff --git a/test/test_discordmessageprocessor.ts b/test/test_discordmessageprocessor.ts
index 88507a34f62e614591a6ab4d9e7d5255e2128d46..b6e793a2e1db35447551f7a26810234d8569f87f 100644
--- a/test/test_discordmessageprocessor.ts
+++ b/test/test_discordmessageprocessor.ts
@@ -15,12 +15,13 @@ limitations under the License.
 */
 
 import * as Chai from "chai"; // TODO: Use expect
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import { DiscordMessageProcessor } from "../src/discordmessageprocessor";
 import { DiscordBot } from "../src/bot";
 import { MockGuild } from "./mocks/guild";
 import { MockMember } from "./mocks/member";
 import { MockMessage } from "./mocks/message";
+import { MockTextChannel } from "./mocks/channel";
 
 // we are a test file and thus need those
 /* tslint:disable:no-unused-expression max-file-line-count no-any */
@@ -128,7 +129,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {});
+            const channel = new MockTextChannel(guild);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
             msg.content = "<@12345>";
@@ -142,7 +143,7 @@ describe("DiscordMessageProcessor", () => {
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
             guild._mockAddMember(new MockMember("12345", "TestUsername"));
-            const channel = new Discord.TextChannel(guild, {});
+            const channel = new MockTextChannel(guild);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
             msg.content = "<@12345>";
@@ -156,7 +157,7 @@ describe("DiscordMessageProcessor", () => {
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
             guild._mockAddMember(new MockMember("12345", "TestUsername", null, "TestNickname"));
-            const channel = new Discord.TextChannel(guild, {});
+            const channel = new MockTextChannel(guild);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
             msg.content = "<@12345>";
@@ -171,7 +172,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"});
+            const channel = new MockTextChannel(guild, {id: "456", name: "TestChannel"});
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
             msg.content = "Hello <:hello:123456789>";
@@ -183,7 +184,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"});
+            const channel = new MockTextChannel(guild, {id: "456", name: "TestChannel"});
             guild.channels.set("456", channel);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
@@ -199,7 +200,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"});
+            const channel = new MockTextChannel(guild, {id: "456", name: "TestChannel"});
             guild.channels.set("456", channel);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
@@ -212,7 +213,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {id: "456", name: "TestChannel"});
+            const channel = new MockTextChannel(guild, {id: "456", name: "TestChannel"});
             guild.channels.set("456", channel);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
@@ -226,7 +227,7 @@ describe("DiscordMessageProcessor", () => {
             const processor = new DiscordMessageProcessor(
                 "localhost", bot as DiscordBot);
             const guild: any = new MockGuild("123", []);
-            const channel = new Discord.TextChannel(guild, {id: "678", name: "TestChannel"});
+            const channel = new MockTextChannel(guild, {id: "678", name: "TestChannel"});
             guild.channels.set("678", channel);
             const msg = new MockMessage(channel) as any;
             msg.embeds = [];
diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts
index b00248099467a3cb3b430ec02566cb5b5d9ef569..7d806eb22b96904c6475a3f89c9fec4747e5bf29 100644
--- a/test/test_matrixeventprocessor.ts
+++ b/test/test_matrixeventprocessor.ts
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import { expect } from "chai";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 import * as Proxyquire from "proxyquire";
 import { MockMember } from "./mocks/member";
 import { MatrixEventProcessor, MatrixEventProcessorOpts } from "../src/matrixeventprocessor";
@@ -265,7 +265,7 @@ describe("MatrixEventProcessor", () => {
             processor.HandleAttachment = async () => "";
             processor.EventToEmbed = async (evt, chan) => {
                 return {
-                    messageEmbed: new Discord.RichEmbed(),
+                    messageEmbed: new Discord.MessageEmbed(),
                 };
             };
             const room = { data: {
@@ -298,7 +298,7 @@ describe("MatrixEventProcessor", () => {
             processor.HandleAttachment = async () => "";
             processor.EventToEmbed = async (evt, chan) => {
                 return {
-                    messageEmbed: new Discord.RichEmbed(),
+                    messageEmbed: new Discord.MessageEmbed(),
                 };
             };
             const room = { data: {
@@ -435,7 +435,7 @@ describe("MatrixEventProcessor", () => {
             } as IMatrixEvent, mockChannel as any);
             const author = embeds.messageEmbed.author;
             expect(author!.name).to.equal("Test User");
-            expect(author!.icon_url).to.equal("https://localhost/avatarurl");
+            expect(author!.iconURL).to.equal("https://localhost/avatarurl");
             expect(author!.url).to.equal("https://matrix.to/#/@test:localhost");
         });
 
@@ -449,7 +449,7 @@ describe("MatrixEventProcessor", () => {
             } as IMatrixEvent, mockChannel as any);
             const author = embeds.messageEmbed.author;
             expect(author!.name).to.equal("Test User");
-            expect(author!.icon_url).to.equal("https://localhost/avatarurl");
+            expect(author!.iconURL).to.equal("https://localhost/avatarurl");
             expect(author!.url).to.equal("https://matrix.to/#/@test:localhost");
         });
 
@@ -463,7 +463,7 @@ describe("MatrixEventProcessor", () => {
             } as IMatrixEvent, mockChannel as any);
             const author = embeds.messageEmbed.author;
             expect(author!.name).to.equal("@test_nonexistant:localhost");
-            expect(author!.icon_url).to.be.undefined;
+            expect(author!.iconURL).to.be.undefined;
             expect(author!.url).to.equal("https://matrix.to/#/@test_nonexistant:localhost");
         });
 
@@ -513,7 +513,7 @@ describe("MatrixEventProcessor", () => {
             } as IMatrixEvent, mockChannel as any);
             const author = embeds.messageEmbed.author;
             expect(author!.name).to.equal("Test User");
-            expect(author!.icon_url).to.equal("https://localhost/avatarurl");
+            expect(author!.iconURL).to.equal("https://localhost/avatarurl");
             expect(author!.url).to.equal("https://matrix.to/#/@test:localhost");
         });
 
@@ -603,7 +603,11 @@ describe("MatrixEventProcessor", () => {
                 },
             } as IMatrixEvent, realBridge.botIntent.underlyingClient)) as Discord.FileOptions;
             expect(attachment.name).to.eq("filename.webm");
-            expect(attachment.attachment.length).to.eq(SMALL_FILE);
+            if (attachment.attachment instanceof Buffer) {
+                expect(attachment.attachment.length).to.eq(SMALL_FILE);
+            } else {
+                throw Error('Expected attachment to be a buffer');
+            }
         });
         it("message without a url", async () => {
             const {processor, realBridge} =  createMatrixEventProcessor();
@@ -645,7 +649,11 @@ describe("MatrixEventProcessor", () => {
                 },
             } as IMatrixEvent, realBridge.botIntent.underlyingClient)) as Discord.FileOptions;
             expect(attachment.name).to.eq("filename.webm");
-            expect(attachment.attachment.length).to.eq(SMALL_FILE);
+            if (attachment.attachment instanceof Buffer) {
+                expect(attachment.attachment.length).to.eq(SMALL_FILE);
+            } else {
+                throw Error('Expected attachment to be a buffer');
+            }
         });
         it("message with a small info.size but a larger file", async () => {
             const {processor, realBridge} =  createMatrixEventProcessor();
@@ -675,7 +683,7 @@ describe("MatrixEventProcessor", () => {
                     url: "mxc://localhost/8000000",
                 },
             } as IMatrixEvent, realBridge.botIntent.underlyingClient, true);
-            expect((ret as Discord.RichEmbed).image!.url).equals("https://localhost/8000000");
+            expect((ret as Discord.MessageEmbed).image!.url).equals("https://localhost/8000000");
         });
         it("Should handle stickers.", async () => {
             const {processor, realBridge} =  createMatrixEventProcessor();
@@ -722,7 +730,7 @@ describe("MatrixEventProcessor", () => {
             } as IMatrixEvent, mockChannel as any);
             expect(result!.description).to.be.equal("Hello!");
             expect(result!.author!.name).to.be.equal("Doggo!");
-            expect(result!.author!.icon_url).to.be.equal("https://fakeurl.com");
+            expect(result!.author!.iconURL).to.be.equal("https://fakeurl.com");
             expect(result!.author!.url).to.be.equal("https://matrix.to/#/@doggo:localhost");
         });
         it("should handle replies with a missing event", async () => {
@@ -744,7 +752,7 @@ This is where the reply goes`,
             } as IMatrixEvent, mockChannel as any);
             expect(result!.description).to.be.equal("Reply with unknown content");
             expect(result!.author!.name).to.be.equal("Unknown");
-            expect(result!.author!.icon_url).to.be.undefined;
+            expect(result!.author!.iconURL).to.be.undefined;
             expect(result!.author!.url).to.be.undefined;
         });
         it("should handle replies with a valid reply event", async () => {
@@ -766,7 +774,7 @@ This is where the reply goes`,
             } as IMatrixEvent, mockChannel as any);
             expect(result!.description).to.be.equal("Hello!");
             expect(result!.author!.name).to.be.equal("Doggo!");
-            expect(result!.author!.icon_url).to.be.equal("https://fakeurl.com");
+            expect(result!.author!.iconURL).to.be.equal("https://fakeurl.com");
             expect(result!.author!.url).to.be.equal("https://matrix.to/#/@doggo:localhost");
         });
         it("should handle replies on top of replies", async () => {
@@ -788,7 +796,7 @@ This is the second reply`,
             } as IMatrixEvent, mockChannel as any);
             expect(result!.description).to.be.equal("This is the first reply");
             expect(result!.author!.name).to.be.equal("Doggo!");
-            expect(result!.author!.icon_url).to.be.equal("https://fakeurl.com");
+            expect(result!.author!.iconURL).to.be.equal("https://fakeurl.com");
             expect(result!.author!.url).to.be.equal("https://matrix.to/#/@doggo:localhost");
         });
         it("should handle replies with non text events", async () => {
@@ -810,7 +818,7 @@ This is the reply`,
             } as IMatrixEvent, mockChannel as any);
             expect(result!.description).to.be.equal("Reply with unknown content");
             expect(result!.author!.name).to.be.equal("Unknown");
-            expect(result!.author!.icon_url).to.be.undefined;
+            expect(result!.author!.iconURL).to.be.undefined;
             expect(result!.author!.url).to.be.undefined;
         });
         it("should add the reply time", async () => {
diff --git a/test/test_presencehandler.ts b/test/test_presencehandler.ts
index b57c9b7dc965aa1fe6fbeca4c7fdab2e762cfc41..f11bae398f92f182c4bd7b8e6dea238048e3aa11 100644
--- a/test/test_presencehandler.ts
+++ b/test/test_presencehandler.ts
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import { expect } from "chai";
-import * as Discord from "discord.js";
+import * as Discord from "better-discord.js"
 
 import { PresenceHandler } from "../src/presencehandler";
 import { DiscordBot } from "../src/bot";
@@ -95,9 +95,10 @@ describe("PresenceHandler", () => {
             lastStatus = null;
             const handler = new PresenceHandler(bot as DiscordBot);
             const member = new MockUser("abc", "def") as any;
-            member.MockSetPresence(new Discord.Presence({
+            member.MockSetPresence(new Discord.Presence({} as any, {
                 status: "online",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "online", undefined);
@@ -106,9 +107,10 @@ describe("PresenceHandler", () => {
             lastStatus = null;
             const handler = new PresenceHandler(bot as DiscordBot);
             const member = new MockUser("abc", "def") as any;
-            member.MockSetPresence(new Discord.Presence({
+            member.MockSetPresence(new Discord.Presence({} as any, {
                 status: "offline",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "offline", undefined);
@@ -117,9 +119,10 @@ describe("PresenceHandler", () => {
             lastStatus = null;
             const handler = new PresenceHandler(bot as DiscordBot);
             const member = new MockUser("abc", "def") as any;
-            member.MockSetPresence(new Discord.Presence({
+            member.MockSetPresence(new Discord.Presence({} as any, {
                 status: "idle",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "unavailable", undefined);
@@ -128,16 +131,18 @@ describe("PresenceHandler", () => {
             lastStatus = null;
             const handler = new PresenceHandler(bot as DiscordBot);
             const member = new MockUser("abc", "def") as any;
-            member.MockSetPresence(new Discord.Presence({
+            member.MockSetPresence(new Discord.Presence({} as any, {
                 status: "dnd",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "online", "Do not disturb");
-            member.MockSetPresence(new Discord.Presence({
-                game: new Discord.Game({name: "Test Game"}, {} as any),
+            member.MockSetPresence(new Discord.Presence({} as any, {
+                activities: ({name: "Test Game", type: 'PLAYING'}),
                 status: "dnd",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "online", "Do not disturb | Playing Test Game");
@@ -146,17 +151,19 @@ describe("PresenceHandler", () => {
             lastStatus = null;
             const handler = new PresenceHandler(bot as DiscordBot);
             const member = new MockUser("abc", "def") as any;
-            member.MockSetPresence(new Discord.Presence({
-                game: new Discord.Game({name: "Test Game"}, {} as any),
+            member.MockSetPresence(new Discord.Presence({} as any, {
+                activities: ({name: "Test Game", type: 'PLAYING'}),
                 status: "online",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "online", "Playing Test Game");
-            member.MockSetPresence(new Discord.Presence({
-                game: new Discord.Game({name: "Test Game", type: 1}, {} as any),
+            member.MockSetPresence(new Discord.Presence({} as any, {
+                activities: ({name: "Test Game", type: 'STREAMING'}),
                 status: "online",
-            }, {} as any));
+                user: member,
+            }));
             await handler.ProcessUser(member);
             appservice.getIntentForSuffix(member.id)
                 .underlyingClient.wasCalled("setPresenceStatus", true, "online", "Streaming Test Game");
diff --git a/tools/chanfix.ts b/tools/chanfix.ts
index 3e86a22ae5df4138be3de7ded4b348edc6fee313..9b293e891b6457d3a08a1adc179abc7530963cf3 100644
--- a/tools/chanfix.ts
+++ b/tools/chanfix.ts
@@ -98,7 +98,7 @@ async function run() {
     const promiseList2: Promise<void>[] = [];
 
     let curDelay = config.limits.roomGhostJoinDelay; // we'll just re-use this
-    client.guilds.forEach((guild) => {
+    client.guilds.cache.forEach((guild) => {
         promiseList2.push((async () => {
             await Util.DelayedPromise(curDelay);
             try {
diff --git a/tools/ghostfix.ts b/tools/ghostfix.ts
index 1d0310b2369ed2d2374326f861321421ba010d0f..88dee4380a129063472c55036eafeaa0d2042251 100644
--- a/tools/ghostfix.ts
+++ b/tools/ghostfix.ts
@@ -87,9 +87,9 @@ async function run() {
     const promiseList: Promise<void>[] = [];
     let curDelay = config.limits.roomGhostJoinDelay;
     try {
-        client.guilds.forEach((guild) => {
-            guild.members.forEach((member) => {
-                if (member.id === client.user.id) {
+        client.guilds.cache.forEach((guild) => {
+            guild.members.cache.forEach((member) => {
+                if (member.id === client.user?.id) {
                     return;
                 }
                 promiseList.push((async () => {