From 3362f9fbbe0adac111e2e608d8b66d419f076a48 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 3 Apr 2021 21:35:55 -0500 Subject: [PATCH 1/2] Prototyped stream notifications like CheeseBot --- src/commands/admin.ts | 20 ++++++++ src/commands/utilities/streaminfo.ts | 18 +++++++ src/core/structures.ts | 2 + src/events/channelUpdate.ts | 14 ++++++ src/events/voiceStateUpdate.ts | 74 ++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 src/commands/utilities/streaminfo.ts create mode 100644 src/events/channelUpdate.ts create mode 100644 src/events/voiceStateUpdate.ts diff --git a/src/commands/admin.ts b/src/commands/admin.ts index 2622087..e78068f 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -164,6 +164,26 @@ export default new Command({ }) }) } + }), + stream: new Command({ + description: "Set a channel to send stream notifications.", + async run($) { + if ($.guild) { + const guild = Storage.getGuild($.guild.id); + + if (guild.streamingChannel) { + guild.streamingChannel = null; + $.channel.send("Removed your server's stream notifications channel."); + } else { + guild.streamingChannel = $.channel.id; + $.channel.send(`Set your server's stream notifications channel to ${$.channel}.`); + } + + Storage.save(); + } else { + $.channel.send("You must use this command in a server."); + } + } }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts new file mode 100644 index 0000000..e4ea9aa --- /dev/null +++ b/src/commands/utilities/streaminfo.ts @@ -0,0 +1,18 @@ +import Command from "../../core/command"; +import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; + +export default new Command({ + description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", + async run($) { + const userID = $.author.id; + + if (streamList.has(userID)) { + const stream = streamList.get(userID)!; + stream.description = $.args.join(" ") || undefined; + stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + } else { + // Alternatively, I could make descriptions last outside of just one stream. + $.channel.send("You can only use this command when streaming."); + } + } +}); diff --git a/src/core/structures.ts b/src/core/structures.ts index 13bdc9f..0e77494 100644 --- a/src/core/structures.ts +++ b/src/core/structures.ts @@ -58,11 +58,13 @@ class Guild { public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; public welcomeMessage: string | null; + public streamingChannel: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); this.welcomeMessage = select(data?.welcomeMessage, null, String); + this.streamingChannel = select(data?.streamingChannel, null, String); switch (data?.welcomeType) { case "text": diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts new file mode 100644 index 0000000..0b325c9 --- /dev/null +++ b/src/events/channelUpdate.ts @@ -0,0 +1,14 @@ +import Event from "../core/event"; +import {streamList, getStreamEmbed} from "./voiceStateUpdate"; + +export default new Event<"channelUpdate">({ + async on(before, after) { + if (before.type === "voice" && after.type === "voice") { + for (const {streamer, channel, description, message} of streamList.values()) { + if (after.id === channel.id) { + message.edit(getStreamEmbed(streamer, channel, description)); + } + } + } + } +}); diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts new file mode 100644 index 0000000..ded1a5a --- /dev/null +++ b/src/events/voiceStateUpdate.ts @@ -0,0 +1,74 @@ +import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js"; +import Event from "../core/event"; +import $ from "../core/lib"; +import {Storage} from "../core/structures"; +import {client} from "../index"; + +type Stream = { + streamer: GuildMember; + channel: VoiceChannel; + description?: string; + message: Message; +}; + +// A list of user IDs and message embeds. +export const streamList = new Collection(); + +// Probably find a better, DRY way of doing this. +export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { + const user = streamer.user; + const embed = new MessageEmbed() + .setTitle(`Stream: \`#${channel.name}\``) + .setAuthor( + streamer.nickname ?? user.username, + user.avatarURL({ + dynamic: true, + format: "png" + }) ?? user.defaultAvatarURL + ) + .setColor(streamer.displayColor); + + if (description) { + embed.setDescription(description); + } + + return embed; +} + +export default new Event<"voiceStateUpdate">({ + async on(before, after) { + const isStartStreamEvent = !before.streaming && after.streaming; + const isStopStreamEvent = before.streaming && (!after.streaming || !after.channel); // If you were streaming before but now are either not streaming or have left the channel. + // Note: isStopStreamEvent can be called twice in a row - If Discord crashes/quits while you're streaming, it'll call once with a null channel and a second time with a channel. + + if (isStartStreamEvent || isStopStreamEvent) { + const {streamingChannel} = Storage.getGuild(after.guild.id); + + if (streamingChannel) { + const member = after.member!; + const voiceChannel = after.channel!; + const textChannel = client.channels.cache.get(streamingChannel); + + if (textChannel && textChannel instanceof TextChannel) { + if (isStartStreamEvent) { + streamList.set(member.id, { + streamer: member, + channel: voiceChannel, + message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + }); + } else if (isStopStreamEvent) { + if (streamList.has(member.id)) { + const {message} = streamList.get(member.id)!; + message.delete(); + streamList.delete(member.id); + } + } + } else { + $.error( + `The streaming notifications channel ${streamingChannel} for guild ${after.guild.id} either doesn't exist or isn't a text channel.` + ); + } + } + } + } +}); From 678485160e3f42052a5d2ba7c6c9daf94b6fc5a4 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Mon, 5 Apr 2021 20:55:21 -0500 Subject: [PATCH 2/2] Added optional channel target for setting channel --- src/commands/admin.ts | 25 +++++++++++++++++++++++-- src/commands/utilities/streaminfo.ts | 6 +++--- src/events/channelUpdate.ts | 8 ++++---- src/events/voiceStateUpdate.ts | 10 +++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/commands/admin.ts b/src/commands/admin.ts index e78068f..40c2242 100644 --- a/src/commands/admin.ts +++ b/src/commands/admin.ts @@ -166,7 +166,8 @@ export default new Command({ } }), stream: new Command({ - description: "Set a channel to send stream notifications.", + description: "Set a channel to send stream notifications. Type `#` to reference the channel.", + usage: "()", async run($) { if ($.guild) { const guild = Storage.getGuild($.guild.id); @@ -183,7 +184,27 @@ export default new Command({ } else { $.channel.send("You must use this command in a server."); } - } + }, + // If/when channel types come out, this will be the perfect candidate to test it. + any: new Command({ + async run($) { + if ($.guild) { + const match = $.args[0].match(/^<#(\d{17,19})>$/); + + if (match) { + Storage.getGuild($.guild.id).streamingChannel = match[1]; + Storage.save(); + $.channel.send(`Successfully set this server's welcome channel to ${match[0]}.`); + } else { + $.channel.send( + "You must provide a reference channel. You can do this by typing `#` then searching for the proper channel." + ); + } + } else { + $.channel.send("You must use this command in a server."); + } + } + }) }) } }), diff --git a/src/commands/utilities/streaminfo.ts b/src/commands/utilities/streaminfo.ts index e4ea9aa..7d90785 100644 --- a/src/commands/utilities/streaminfo.ts +++ b/src/commands/utilities/streaminfo.ts @@ -1,5 +1,5 @@ import Command from "../../core/command"; -import {streamList, getStreamEmbed} from "../../events/voiceStateUpdate"; +import {streamList} from "../../events/voiceStateUpdate"; export default new Command({ description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", @@ -8,8 +8,8 @@ export default new Command({ if (streamList.has(userID)) { const stream = streamList.get(userID)!; - stream.description = $.args.join(" ") || undefined; - stream.message.edit(getStreamEmbed(stream.streamer, stream.channel, stream.description)); + stream.description = $.args.join(" ") || "No description set."; + stream.update(); } else { // Alternatively, I could make descriptions last outside of just one stream. $.channel.send("You can only use this command when streaming."); diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index 0b325c9..7d49789 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -1,12 +1,12 @@ import Event from "../core/event"; -import {streamList, getStreamEmbed} from "./voiceStateUpdate"; +import {streamList} from "./voiceStateUpdate"; export default new Event<"channelUpdate">({ async on(before, after) { if (before.type === "voice" && after.type === "voice") { - for (const {streamer, channel, description, message} of streamList.values()) { - if (after.id === channel.id) { - message.edit(getStreamEmbed(streamer, channel, description)); + for (const stream of streamList.values()) { + if (after.id === stream.channel.id) { + stream.update(); } } } diff --git a/src/events/voiceStateUpdate.ts b/src/events/voiceStateUpdate.ts index ded1a5a..54b90d4 100644 --- a/src/events/voiceStateUpdate.ts +++ b/src/events/voiceStateUpdate.ts @@ -9,13 +9,14 @@ type Stream = { channel: VoiceChannel; description?: string; message: Message; + update: () => void; }; // A list of user IDs and message embeds. export const streamList = new Collection(); // Probably find a better, DRY way of doing this. -export function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { +function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { const user = streamer.user; const embed = new MessageEmbed() .setTitle(`Stream: \`#${channel.name}\``) @@ -49,12 +50,15 @@ export default new Event<"voiceStateUpdate">({ const voiceChannel = after.channel!; const textChannel = client.channels.cache.get(streamingChannel); - if (textChannel && textChannel instanceof TextChannel) { + if (textChannel instanceof TextChannel) { if (isStartStreamEvent) { streamList.set(member.id, { streamer: member, channel: voiceChannel, - message: await textChannel.send(getStreamEmbed(member, voiceChannel)) + message: await textChannel.send(getStreamEmbed(member, voiceChannel)), + update(this: Stream) { + this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description)); + } }); } else if (isStopStreamEvent) { if (streamList.has(member.id)) {