diff --git a/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts new file mode 100644 index 00000000..1a59e170 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/CompanionChannelsPlugin.ts @@ -0,0 +1,18 @@ +import { zeppelinPlugin } from "../ZeppelinPluginBlueprint"; +import { CompanionChannelsPluginType, ConfigSchema, TCompanionChannelOpts } from "./types"; +import { VoiceChannelJoinEvt } from "./events/VoiceChannelJoinEvt"; +import { VoiceChannelSwitchEvt } from "./events/VoiceChannelSwitchEvt"; +import { VoiceChannelLeaveEvt } from "./events/VoiceChannelLeaveEvt"; + +const defaultOptions = { + config: { + entries: {}, + }, +}; + +export const CompanionChannelsPlugin = zeppelinPlugin()("companion_channels", { + configSchema: ConfigSchema, + defaultOptions, + + events: [VoiceChannelJoinEvt, VoiceChannelSwitchEvt, VoiceChannelLeaveEvt], +}); diff --git a/backend/src/plugins/CompanionChannels/events/VoiceChannelJoinEvt.ts b/backend/src/plugins/CompanionChannels/events/VoiceChannelJoinEvt.ts new file mode 100644 index 00000000..042a6101 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/events/VoiceChannelJoinEvt.ts @@ -0,0 +1,10 @@ +import { eventListener } from "knub"; +import { CompanionChannelsPluginType } from "../types"; +import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; + +export const VoiceChannelJoinEvt = eventListener()( + "voiceChannelJoin", + ({ pluginData, args: { member, newChannel } }) => { + handleCompanionPermissions(pluginData, member.id, newChannel.id); + }, +); diff --git a/backend/src/plugins/CompanionChannels/events/VoiceChannelLeaveEvt.ts b/backend/src/plugins/CompanionChannels/events/VoiceChannelLeaveEvt.ts new file mode 100644 index 00000000..cf584d7e --- /dev/null +++ b/backend/src/plugins/CompanionChannels/events/VoiceChannelLeaveEvt.ts @@ -0,0 +1,10 @@ +import { eventListener } from "knub"; +import { CompanionChannelsPluginType } from "../types"; +import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; + +export const VoiceChannelLeaveEvt = eventListener()( + "voiceChannelLeave", + ({ pluginData, args: { member, oldChannel } }) => { + handleCompanionPermissions(pluginData, member.id, null, oldChannel.id); + }, +); diff --git a/backend/src/plugins/CompanionChannels/events/VoiceChannelSwitchEvt.ts b/backend/src/plugins/CompanionChannels/events/VoiceChannelSwitchEvt.ts new file mode 100644 index 00000000..fe382578 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/events/VoiceChannelSwitchEvt.ts @@ -0,0 +1,10 @@ +import { eventListener } from "knub"; +import { CompanionChannelsPluginType } from "../types"; +import { handleCompanionPermissions } from "../functions/handleCompanionPermissions"; + +export const VoiceChannelSwitchEvt = eventListener()( + "voiceChannelSwitch", + ({ pluginData, args: { member, oldChannel, newChannel } }) => { + handleCompanionPermissions(pluginData, member.id, newChannel.id, oldChannel.id); + }, +); diff --git a/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts b/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts new file mode 100644 index 00000000..fc9693f4 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/functions/getCompanionChannelOptsForVoiceChannelId.ts @@ -0,0 +1,17 @@ +import { PluginData } from "knub"; +import { CompanionChannelsPluginType, TCompanionChannelOpts } from "../types"; + +const defaultCompanionChannelOpts: Partial = { + enabled: true, +}; + +export function getCompanionChannelOptsForVoiceChannelId( + pluginData: PluginData, + userId: string, + voiceChannelId: string, +): TCompanionChannelOpts[] { + const config = pluginData.config.getMatchingConfig({ userId, channelId: voiceChannelId }); + return Object.values(config.entries) + .filter(opts => opts.voice_channel_ids.includes(voiceChannelId)) + .map(opts => Object.assign({}, defaultCompanionChannelOpts, opts)); +} diff --git a/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts new file mode 100644 index 00000000..528e0546 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/functions/handleCompanionPermissions.ts @@ -0,0 +1,65 @@ +import { CompanionChannelsPluginType, TCompanionChannelOpts } from "../types"; +import { getCompanionChannelOptsForVoiceChannelId } from "./getCompanionChannelOptsForVoiceChannelId"; +import { PluginData } from "knub"; +import { TextChannel } from "eris"; + +export function handleCompanionPermissions( + pluginData: PluginData, + userId: string, + voiceChannelId: string, + oldChannelId?: string, +); +export function handleCompanionPermissions( + pluginData: PluginData, + userId: string, + voiceChannelId: null, + oldChannelId: string, +); +export function handleCompanionPermissions( + pluginData: PluginData, + userId: string, + voiceChannelId?: string, + oldChannelId?: string, +) { + const permsToDelete: Set = new Set(); // channelId[] + const oldPerms: Map = new Map(); // channelId => permissions + const permsToSet: Map = new Map(); // channelId => permissions + + const oldChannelOptsArr: TCompanionChannelOpts[] = oldChannelId + ? getCompanionChannelOptsForVoiceChannelId(pluginData, userId, oldChannelId) + : []; + const newChannelOptsArr: TCompanionChannelOpts[] = voiceChannelId + ? getCompanionChannelOptsForVoiceChannelId(pluginData, userId, voiceChannelId) + : []; + + for (const oldChannelOpts of oldChannelOptsArr) { + for (const channelId of oldChannelOpts.text_channel_ids) { + oldPerms.set(channelId, oldChannelOpts.permissions); + permsToDelete.add(channelId); + } + } + + for (const newChannelOpts of newChannelOptsArr) { + for (const channelId of newChannelOpts.text_channel_ids) { + if (oldPerms.get(channelId) !== newChannelOpts.permissions) { + // Update text channel perms if the channel we transitioned from didn't already have the same text channel perms + permsToSet.set(channelId, newChannelOpts.permissions); + } + if (permsToDelete.has(channelId)) { + permsToDelete.delete(channelId); + } + } + } + + for (const channelId of permsToDelete) { + const channel = pluginData.guild.channels.get(channelId); + if (!channel || !(channel instanceof TextChannel)) continue; + channel.deletePermission(userId, `Companion Channel for ${oldChannelId} | User Left`); + } + + for (const [channelId, permissions] of permsToSet) { + const channel = pluginData.guild.channels.get(channelId); + if (!channel || !(channel instanceof TextChannel)) continue; + channel.editPermission(userId, permissions, 0, "member", `Companion Channel for ${voiceChannelId} | User Joined`); + } +} diff --git a/backend/src/plugins/CompanionChannels/types.ts b/backend/src/plugins/CompanionChannels/types.ts new file mode 100644 index 00000000..e27277f3 --- /dev/null +++ b/backend/src/plugins/CompanionChannels/types.ts @@ -0,0 +1,28 @@ +import * as t from "io-ts"; +import { tNullable } from "../../utils"; +import { BasePluginType } from "knub"; +import { GuildLogs } from "../../data/GuildLogs"; +import { GuildSavedMessages } from "../../data/GuildSavedMessages"; + +// Permissions using these numbers: https://abal.moe/Eris/docs/reference (add all allowed/denied ones up) +export const CompanionChannelOpts = t.type({ + voice_channel_ids: t.array(t.string), + text_channel_ids: t.array(t.string), + permissions: t.number, + enabled: tNullable(t.boolean), +}); +export type TCompanionChannelOpts = t.TypeOf; + +export const ConfigSchema = t.type({ + entries: t.record(t.string, CompanionChannelOpts), +}); +export type TConfigSchema = t.TypeOf; + +export interface ICompanionChannelMap { + [channelId: string]: TCompanionChannelOpts; +} + +export interface CompanionChannelsPluginType extends BasePluginType { + config: TConfigSchema; + state: {}; +} diff --git a/backend/src/plugins/availablePlugins.ts b/backend/src/plugins/availablePlugins.ts index 2b7cc9ed..a77e18d5 100644 --- a/backend/src/plugins/availablePlugins.ts +++ b/backend/src/plugins/availablePlugins.ts @@ -27,6 +27,7 @@ import { SelfGrantableRolesPlugin } from "./SelfGrantableRoles/SelfGrantableRole import { SpamPlugin } from "./Spam/SpamPlugin"; import { ReactionRolesPlugin } from "./ReactionRoles/ReactionRolesPlugin"; import { AutomodPlugin } from "./Automod/AutomodPlugin"; +import { CompanionChannelsPlugin } from "./CompanionChannels/CompanionChannelsPlugin"; // prettier-ignore export const guildPlugins: Array> = [ @@ -57,6 +58,7 @@ export const guildPlugins: Array> = [ CasesPlugin, MutesPlugin, AutomodPlugin, + CompanionChannelsPlugin, ]; // prettier-ignore