Loading src/bridge.tsdeleted 100644 → 0 +0 −3 Original line number Diff line number Diff line class Bridge_ {} export const Bridge = new Bridge_(); src/discord.ts +35 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import { PartialMessageReaction, PartialUser, Partials, PermissionResolvable, ReactionEmoji, Typing, User, Loading Loading @@ -119,6 +120,32 @@ class Discord_ { throw new Error("Unknown proxy type"); } async checkPermissions(guildId: string, channelId: string): Promise<true> { const guild = await this.client.guilds.fetch(guildId); if (!guild.members.me) throw new Error("Not member of guild"); const channel = await guild.channels.fetch(channelId); if (!channel) throw new Error("Channel not found"); const REQUIRED_PERMISSIONS: PermissionResolvable[] = [ "ManageWebhooks", "ManageMessages", "ManageThreads", "UseExternalSounds", ]; const perms = channel.permissionsFor(guild.members.me); const hasAllPerms = REQUIRED_PERMISSIONS.map((perm) => perms.has(perm) ).every((e) => Boolean(e)); if (!hasAllPerms) { throw new Error("Does not have all permissions"); } return true; } async getAttachmentURL( guild_id: string, channel_id: string, Loading Loading @@ -1214,6 +1241,14 @@ class Discord_ { } } async sendNotice(guild_id: string, channel_id: string, content: string) { const guild = await this.client.guilds.fetch(guild_id); const channel = await guild.channels.fetch(channel_id); if (!channel || !channel.isSendable()) return; return channel.send(content); } async sendMessage( guild_id: string, channel_id: string, Loading src/index.ts +3 −2 Original line number Diff line number Diff line import "./lib/sentry"; import "./types/env"; import { Secrets } from "./lib/secrets"; import "./webserver"; import { Discord } from "./discord"; import { Matrix } from "./lib/matrix"; import { WebServer } from "./routes"; new WebServer().listen(); Discord.login(Secrets.get("DISCORD_TOKEN")); Loading src/lib/matrix.ts +65 −3 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import { MatrixReactionEvent, MatrixRoomMembership, MatrixRoomMessage, MatrixRoomPowerLevels, MatrixRoomPowerLevelsDefaults, SentClientEvent, } from "../types/matrix"; import { FriendlyError } from "./utils"; Loading Loading @@ -169,10 +171,22 @@ export class Matrix { ); } static async isUserInRoom(roomId: string, user: IUsername): Promise<boolean> { const req = await this.fetch<MatrixRoomMembership["content"]>( `/_matrix/client/v3/rooms/${roomId}/state/m.room.member/@${user}:${MATRIX_HOMESERVER}` static async getState<T>(roomId: string, state: string, key: string) { return await this.fetch<T>( `/_matrix/client/v3/rooms/${roomId}/state/${state}/${key}` ); } static async getMemberState(roomId: string, user: IUsername) { return await this.getState<MatrixRoomMembership["content"]>( roomId, `m.room.member`, `@${user}:${MATRIX_HOMESERVER}` ); } static async isUserInRoom(roomId: string, user: IUsername): Promise<boolean> { const req = await this.getMemberState(roomId, user); if ("errcode" in req.data) { // if(req.data.errcode === "M_NOT_FOUND") Loading Loading @@ -569,6 +583,54 @@ export class Matrix { "?via=" + MATRIX_HOMESERVER) as any; } static hasPermission( powerLevels: MatrixRoomPowerLevels["content"], user_id: IUsername, event_type: "invite" | "kick" | "redact" | "ban" | (string & {}) ): boolean { const effectiveLevel = powerLevels.users[`@${user_id}:${MATRIX_HOMESERVER}`] ?? powerLevels.users_default; let requiredLevel = powerLevels.events[event_type] ?? powerLevels.events_default; if (["invite", "kick", "redact", "ban"].indexOf(event_type) > -1) { requiredLevel = powerLevels[event_type as Exclude<typeof event_type, string & {}>] ?? MatrixRoomPowerLevelsDefaults[ event_type as Exclude<typeof event_type, string & {}> ]; } return effectiveLevel >= requiredLevel; } static async checkPermissions(room_id: string): Promise<true> { const member = await this.getMemberState(room_id, `_discord_bot`); if ("errcode" in member.data) throw new Error("Unable to get member"); if (member.data.membership !== "join") throw new Error("Not in room"); const powerLevels = await this.getState<MatrixRoomPowerLevels["content"]>( room_id, "m.room.power_levels", "" ); if ("errcode" in powerLevels.data) throw new Error("Unable to get power levels"); const powerLevelsD: MatrixRoomPowerLevels["content"] = powerLevels.data; const REQUIRED_PERMS = ["invite", "kick", "ban", "redact"]; const hasAll = REQUIRED_PERMS.map((p) => this.hasPermission(powerLevelsD, `_discord_bot`, p) ).every((a) => Boolean(a)); if (!hasAll) throw new Error("Does not have all required permissions"); return true; } } type IUsername = `_discord_${string}`; Loading src/lib/secrets.ts +4 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,8 @@ type SecretName = | "MATRIX_AS_TOKEN" | "MATRIX_HS_TOKEN" | "DISCORD_TOKEN" | "SIGNED_SECRET"; | "SIGNED_SECRET" | "ADMIN_TOKEN"; export class Secrets { private static instance: Secrets; Loading @@ -13,6 +14,7 @@ export class Secrets { MATRIX_HS_TOKEN: "", DISCORD_TOKEN: "", SIGNED_SECRET: "", ADMIN_TOKEN: "", }; constructor() { Loading @@ -21,6 +23,7 @@ export class Secrets { this.load("MATRIX_AS_TOKEN"); this.load("MATRIX_HS_TOKEN"); this.load("SIGNED_SECRET"); this.load("ADMIN_TOKEN"); } private load(secret: SecretName) { Loading Loading
src/bridge.tsdeleted 100644 → 0 +0 −3 Original line number Diff line number Diff line class Bridge_ {} export const Bridge = new Bridge_();
src/discord.ts +35 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import { PartialMessageReaction, PartialUser, Partials, PermissionResolvable, ReactionEmoji, Typing, User, Loading Loading @@ -119,6 +120,32 @@ class Discord_ { throw new Error("Unknown proxy type"); } async checkPermissions(guildId: string, channelId: string): Promise<true> { const guild = await this.client.guilds.fetch(guildId); if (!guild.members.me) throw new Error("Not member of guild"); const channel = await guild.channels.fetch(channelId); if (!channel) throw new Error("Channel not found"); const REQUIRED_PERMISSIONS: PermissionResolvable[] = [ "ManageWebhooks", "ManageMessages", "ManageThreads", "UseExternalSounds", ]; const perms = channel.permissionsFor(guild.members.me); const hasAllPerms = REQUIRED_PERMISSIONS.map((perm) => perms.has(perm) ).every((e) => Boolean(e)); if (!hasAllPerms) { throw new Error("Does not have all permissions"); } return true; } async getAttachmentURL( guild_id: string, channel_id: string, Loading Loading @@ -1214,6 +1241,14 @@ class Discord_ { } } async sendNotice(guild_id: string, channel_id: string, content: string) { const guild = await this.client.guilds.fetch(guild_id); const channel = await guild.channels.fetch(channel_id); if (!channel || !channel.isSendable()) return; return channel.send(content); } async sendMessage( guild_id: string, channel_id: string, Loading
src/index.ts +3 −2 Original line number Diff line number Diff line import "./lib/sentry"; import "./types/env"; import { Secrets } from "./lib/secrets"; import "./webserver"; import { Discord } from "./discord"; import { Matrix } from "./lib/matrix"; import { WebServer } from "./routes"; new WebServer().listen(); Discord.login(Secrets.get("DISCORD_TOKEN")); Loading
src/lib/matrix.ts +65 −3 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ import { MatrixReactionEvent, MatrixRoomMembership, MatrixRoomMessage, MatrixRoomPowerLevels, MatrixRoomPowerLevelsDefaults, SentClientEvent, } from "../types/matrix"; import { FriendlyError } from "./utils"; Loading Loading @@ -169,10 +171,22 @@ export class Matrix { ); } static async isUserInRoom(roomId: string, user: IUsername): Promise<boolean> { const req = await this.fetch<MatrixRoomMembership["content"]>( `/_matrix/client/v3/rooms/${roomId}/state/m.room.member/@${user}:${MATRIX_HOMESERVER}` static async getState<T>(roomId: string, state: string, key: string) { return await this.fetch<T>( `/_matrix/client/v3/rooms/${roomId}/state/${state}/${key}` ); } static async getMemberState(roomId: string, user: IUsername) { return await this.getState<MatrixRoomMembership["content"]>( roomId, `m.room.member`, `@${user}:${MATRIX_HOMESERVER}` ); } static async isUserInRoom(roomId: string, user: IUsername): Promise<boolean> { const req = await this.getMemberState(roomId, user); if ("errcode" in req.data) { // if(req.data.errcode === "M_NOT_FOUND") Loading Loading @@ -569,6 +583,54 @@ export class Matrix { "?via=" + MATRIX_HOMESERVER) as any; } static hasPermission( powerLevels: MatrixRoomPowerLevels["content"], user_id: IUsername, event_type: "invite" | "kick" | "redact" | "ban" | (string & {}) ): boolean { const effectiveLevel = powerLevels.users[`@${user_id}:${MATRIX_HOMESERVER}`] ?? powerLevels.users_default; let requiredLevel = powerLevels.events[event_type] ?? powerLevels.events_default; if (["invite", "kick", "redact", "ban"].indexOf(event_type) > -1) { requiredLevel = powerLevels[event_type as Exclude<typeof event_type, string & {}>] ?? MatrixRoomPowerLevelsDefaults[ event_type as Exclude<typeof event_type, string & {}> ]; } return effectiveLevel >= requiredLevel; } static async checkPermissions(room_id: string): Promise<true> { const member = await this.getMemberState(room_id, `_discord_bot`); if ("errcode" in member.data) throw new Error("Unable to get member"); if (member.data.membership !== "join") throw new Error("Not in room"); const powerLevels = await this.getState<MatrixRoomPowerLevels["content"]>( room_id, "m.room.power_levels", "" ); if ("errcode" in powerLevels.data) throw new Error("Unable to get power levels"); const powerLevelsD: MatrixRoomPowerLevels["content"] = powerLevels.data; const REQUIRED_PERMS = ["invite", "kick", "ban", "redact"]; const hasAll = REQUIRED_PERMS.map((p) => this.hasPermission(powerLevelsD, `_discord_bot`, p) ).every((a) => Boolean(a)); if (!hasAll) throw new Error("Does not have all required permissions"); return true; } } type IUsername = `_discord_${string}`; Loading
src/lib/secrets.ts +4 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,8 @@ type SecretName = | "MATRIX_AS_TOKEN" | "MATRIX_HS_TOKEN" | "DISCORD_TOKEN" | "SIGNED_SECRET"; | "SIGNED_SECRET" | "ADMIN_TOKEN"; export class Secrets { private static instance: Secrets; Loading @@ -13,6 +14,7 @@ export class Secrets { MATRIX_HS_TOKEN: "", DISCORD_TOKEN: "", SIGNED_SECRET: "", ADMIN_TOKEN: "", }; constructor() { Loading @@ -21,6 +23,7 @@ export class Secrets { this.load("MATRIX_AS_TOKEN"); this.load("MATRIX_HS_TOKEN"); this.load("SIGNED_SECRET"); this.load("ADMIN_TOKEN"); } private load(secret: SecretName) { Loading