diff --git a/src/plugins/CompanionChannels.ts b/src/plugins/CompanionChannels.ts index 8269deef..ffa27008 100644 --- a/src/plugins/CompanionChannels.ts +++ b/src/plugins/CompanionChannels.ts @@ -1,68 +1,95 @@ import { decorators as d, IPluginOptions, logger } from "knub"; import { ZeppelinPlugin } from "./ZeppelinPlugin"; -import { Member, Channel, GuildChannel, PermissionOverwrite, Permission, Message } from "eris"; +import { Member, Channel, GuildChannel, PermissionOverwrite, Permission, Message, TextChannel } from "eris"; import * as t from "io-ts"; +import { tNullable } from "../utils"; // Permissions using these numbers: https://abal.moe/Eris/docs/reference (add all allowed/denied ones up) -const CompanionChannel = t.type({ - channelIds: t.array(t.string), +const CompanionChannelOpts = t.type({ + voiceChannelIds: t.array(t.string), + textChannelIds: t.array(t.string), permissions: t.number, + enabled: tNullable(t.boolean), }); -type TCompanionChannel = t.TypeOf; +type TCompanionChannelOpts = t.TypeOf; const ConfigSchema = t.type({ - channels: t.record(t.string, CompanionChannel), + companions: t.record(t.string, CompanionChannelOpts), }); type TConfigSchema = t.TypeOf; interface ICompanionChannelMap { - [channelId: string]: TCompanionChannel; + [channelId: string]: TCompanionChannelOpts; } +const defaultCompanionChannelOpts: Partial = { + enabled: true, +}; + export class CompanionChannelPlugin extends ZeppelinPlugin { public static pluginName = "companion_channels"; protected static configSchema = ConfigSchema; - companionChannels: Map = new Map(); - protected static getStaticDefaultOptions(): IPluginOptions { return { config: { - channels: {}, + companions: {}, }, }; } - onLoad() { - const tempCompanionChannels: ICompanionChannelMap = this.getConfig().channels; + /** + * Returns an array of companion channel opts that match the given userId and voiceChannelId, + * with default companion channel opts applied as well + */ + protected getCompanionChannelOptsForVoiceChannelId(userId, voiceChannelId): TCompanionChannelOpts[] { + const config = this.getConfigForMemberIdAndChannelId(userId, voiceChannelId); + return Object.values(config.companions) + .filter(opts => opts.voiceChannelIds.includes(voiceChannelId)) + .map(opts => Object.assign({}, defaultCompanionChannelOpts, opts)); + } - for (const [channelId, opts] of Object.entries(tempCompanionChannels)) { - this.companionChannels.set(channelId, opts); + async handleCompanionPermissions(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 + ? this.getCompanionChannelOptsForVoiceChannelId(userId, oldChannelId) + : []; + const newChannelOptsArr: TCompanionChannelOpts[] = voiceChannelId + ? this.getCompanionChannelOptsForVoiceChannelId(userId, voiceChannelId) + : []; + + for (const oldChannelOpts of oldChannelOptsArr) { + for (const channelId of oldChannelOpts.textChannelIds) { + oldPerms.set(channelId, oldChannelOpts.permissions); + permsToDelete.add(channelId); + } } - } - onUnload() { - this.companionChannels.clear(); - } - - async handleCompanionPermissions(userID: string, voiceChannelId: string, remove?: boolean) { - if (this.companionChannels.has(voiceChannelId)) { - const compChannels = this.companionChannels.get(voiceChannelId); - compChannels.channelIds.forEach(textChannelId => { - const textChannel = this.bot.getChannel(textChannelId); - - if (remove) { - textChannel.deletePermission(userID, `Companion Channel for ${voiceChannelId} | User Left`); - } else { - textChannel.editPermission( - userID, - compChannels.permissions, - 0, - "member", - `Companion Channel for ${voiceChannelId} | User Joined`, - ); + for (const newChannelOpts of newChannelOptsArr) { + for (const channelId of newChannelOpts.textChannelIds) { + 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 = this.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 = this.guild.channels.get(channelId); + if (!channel || !(channel instanceof TextChannel)) continue; + channel.editPermission(userId, permissions, 0, "member", `Companion Channel for ${voiceChannelId} | User Joined`); } } @@ -73,12 +100,11 @@ export class CompanionChannelPlugin extends ZeppelinPlugin { @d.event("voiceChannelSwitch") onVoiceChannelSwitch(member: Member, newChannel: Channel, oldChannel: Channel) { - this.handleCompanionPermissions(member.id, oldChannel.id, true); - this.handleCompanionPermissions(member.id, newChannel.id); + this.handleCompanionPermissions(member.id, newChannel.id, oldChannel.id); } @d.event("voiceChannelLeave") onVoiceChannelLeave(member: Member, voiceChannel: Channel) { - this.handleCompanionPermissions(member.id, voiceChannel.id, true); + this.handleCompanionPermissions(member.id, null, voiceChannel.id); } }