Improved stream notifications
This commit is contained in:
parent
728f115de9
commit
9bf44c160a
|
@ -9,7 +9,7 @@ import {
|
|||
} from "../../core";
|
||||
import {clean} from "../../lib";
|
||||
import {Config, Storage} from "../../structures";
|
||||
import {Permissions, TextChannel, User} from "discord.js";
|
||||
import {Permissions, TextChannel, User, Role} from "discord.js";
|
||||
import {logs} from "../../modules/globals";
|
||||
|
||||
function getLogBuffer(type: string) {
|
||||
|
@ -162,6 +162,46 @@ export default new NamedCommand({
|
|||
send(`Successfully set this server's stream notifications channel to ${result}.`);
|
||||
}
|
||||
})
|
||||
}),
|
||||
streamrole: new NamedCommand({
|
||||
description: "Sets/removes a stream notification role (and the corresponding category name)",
|
||||
usage: "set/remove <...>",
|
||||
run: "You need to enter in a role.",
|
||||
subcommands: {
|
||||
set: new NamedCommand({
|
||||
usage: "<role> <category>",
|
||||
id: "role",
|
||||
role: new Command({
|
||||
run: "You need to enter a category name.",
|
||||
any: new RestCommand({
|
||||
async run({send, guild, args, combined}) {
|
||||
const role = args[0] as Role;
|
||||
Storage.getGuild(guild!.id).streamingRoles[role.id] = combined;
|
||||
Storage.save();
|
||||
send(
|
||||
`Successfully set the category \`${combined}\` to notify \`${role.name}\`.`
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
remove: new NamedCommand({
|
||||
usage: "<role>",
|
||||
id: "role",
|
||||
role: new Command({
|
||||
async run({send, guild, args}) {
|
||||
const role = args[0] as Role;
|
||||
const guildStorage = Storage.getGuild(guild!.id);
|
||||
const category = guildStorage.streamingRoles[role.id];
|
||||
delete guildStorage.streamingRoles[role.id];
|
||||
Storage.save();
|
||||
send(
|
||||
`Successfully removed the category \`${category}\` to notify \`${role.name}\`.`
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
@ -251,7 +291,7 @@ export default new NamedCommand({
|
|||
run: "You have to enter some code to execute first.",
|
||||
any: new RestCommand({
|
||||
// You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed.
|
||||
async run({send, combined}) {
|
||||
async run({send, message, channel, guild, author, member, client, args, combined}) {
|
||||
try {
|
||||
let evaled = eval(combined);
|
||||
if (typeof evaled !== "string") evaled = require("util").inspect(evaled);
|
||||
|
|
|
@ -1,44 +1,142 @@
|
|||
import {NamedCommand, RestCommand} from "../../core";
|
||||
import {streamList} from "../../modules/streamNotifications";
|
||||
import {Storage} from "../../structures";
|
||||
|
||||
// Alternatively, I could make descriptions last outside of just one stream.
|
||||
// But then again, users could just copy paste descriptions. :leaSMUG:
|
||||
// Stream presets (for permanent parts of the description) might come some time in the future.
|
||||
export default new NamedCommand({
|
||||
description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`",
|
||||
async run({send, author, member}) {
|
||||
const userID = author.id;
|
||||
description: "Modifies the current embed for your stream",
|
||||
run: "You need to specify whether to set the description or the image (`desc` and `img` respectively).",
|
||||
subcommands: {
|
||||
description: new NamedCommand({
|
||||
aliases: ["desc"],
|
||||
description:
|
||||
"Sets the description of your stream. You can embed links by writing `[some name](some link)` or remove it",
|
||||
usage: "(<description>)",
|
||||
async run({send, author}) {
|
||||
const userID = author.id;
|
||||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.description = "No description set.";
|
||||
stream.update();
|
||||
send(`Successfully set the stream description to:`, {
|
||||
embed: {
|
||||
description: "No description set.",
|
||||
color: member!.displayColor
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.description = undefined;
|
||||
stream.update();
|
||||
send("Successfully removed the stream description.");
|
||||
} else {
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Alternatively, I could make descriptions last outside of just one stream.
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, author, member, combined}) {
|
||||
const userID = author.id;
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, author, member, combined}) {
|
||||
const userID = author.id;
|
||||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.description = combined;
|
||||
stream.update();
|
||||
send(`Successfully set the stream description to:`, {
|
||||
embed: {
|
||||
description: stream.description,
|
||||
color: member!.displayColor
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.description = combined;
|
||||
stream.update();
|
||||
send("Successfully set the stream description to:", {
|
||||
embed: {
|
||||
description: stream.description,
|
||||
color: member!.displayColor
|
||||
}
|
||||
});
|
||||
} else {
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Alternatively, I could make descriptions last outside of just one stream.
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}),
|
||||
thumbnail: new NamedCommand({
|
||||
aliases: ["img"],
|
||||
description: "Sets a thumbnail to display alongside the embed or remove it",
|
||||
usage: "(<link>)",
|
||||
async run({send, author}) {
|
||||
const userID = author.id;
|
||||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.thumbnail = undefined;
|
||||
stream.update();
|
||||
send("Successfully removed the stream thumbnail.");
|
||||
} else {
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, author, member, combined}) {
|
||||
const userID = author.id;
|
||||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.thumbnail = combined;
|
||||
stream.update();
|
||||
send(`Successfully set the stream thumbnail to: ${combined}`, {
|
||||
embed: {
|
||||
description: stream.description,
|
||||
thumbnail: {url: combined},
|
||||
color: member!.displayColor
|
||||
}
|
||||
});
|
||||
} else {
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
category: new NamedCommand({
|
||||
aliases: ["cat", "group"],
|
||||
description:
|
||||
"Sets the stream category any future streams will be in (as well as notification roles if set)",
|
||||
usage: "(<category>)",
|
||||
async run({send, guild, author}) {
|
||||
const userID = author.id;
|
||||
const memberStorage = Storage.getGuild(guild!.id).getMember(userID);
|
||||
memberStorage.streamCategory = null;
|
||||
Storage.save();
|
||||
send("Successfully removed the category for all your current and future streams.");
|
||||
|
||||
// Then modify the current category if the user is streaming
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.category = "None";
|
||||
stream.update();
|
||||
}
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, guild, author, combined}) {
|
||||
const userID = author.id;
|
||||
const guildStorage = Storage.getGuild(guild!.id);
|
||||
const memberStorage = guildStorage.getMember(userID);
|
||||
let found = false;
|
||||
|
||||
// Check if it's a valid category
|
||||
for (const [roleID, categoryName] of Object.entries(guildStorage.streamingRoles)) {
|
||||
if (combined === categoryName) {
|
||||
found = true;
|
||||
memberStorage.streamCategory = roleID;
|
||||
Storage.save();
|
||||
send(
|
||||
`Successfully set the category for your current and future streams to: \`${categoryName}\``
|
||||
);
|
||||
|
||||
// Then modify the current category if the user is streaming
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.category = categoryName;
|
||||
stream.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
send(
|
||||
`No valid category found by \`${combined}\`! The available categories are: \`${Object.values(
|
||||
guildStorage.streamingRoles
|
||||
).join(", ")}\``
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js";
|
||||
import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Message, Collection} from "discord.js";
|
||||
import {client} from "../index";
|
||||
import {Storage} from "../structures";
|
||||
|
||||
type Stream = {
|
||||
streamer: GuildMember;
|
||||
channel: VoiceChannel;
|
||||
category: string;
|
||||
description?: string;
|
||||
thumbnail?: string;
|
||||
message: Message;
|
||||
streamStart: number;
|
||||
update: () => void;
|
||||
};
|
||||
|
||||
|
@ -14,10 +17,17 @@ type Stream = {
|
|||
export const streamList = new Collection<string, Stream>();
|
||||
|
||||
// Probably find a better, DRY way of doing this.
|
||||
function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed {
|
||||
function getStreamEmbed(
|
||||
streamer: GuildMember,
|
||||
channel: VoiceChannel,
|
||||
streamStart: number,
|
||||
category: string,
|
||||
description?: string,
|
||||
thumbnail?: string
|
||||
): MessageEmbed {
|
||||
const user = streamer.user;
|
||||
const embed = new MessageEmbed()
|
||||
.setTitle(`Stream: \`#${channel.name}\``)
|
||||
.setTitle(channel.name)
|
||||
.setAuthor(
|
||||
streamer.nickname ?? user.username,
|
||||
user.avatarURL({
|
||||
|
@ -25,11 +35,22 @@ function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, descriptio
|
|||
format: "png"
|
||||
}) ?? user.defaultAvatarURL
|
||||
)
|
||||
.setColor(streamer.displayColor);
|
||||
// I decided to not include certain fields:
|
||||
// .addField("Activity", "CrossCode", true) - Probably too much presence data involved, increasing memory usage.
|
||||
// .addField("Viewers", 5, true) - There doesn't seem to currently be a way to track how many viewers there are. Presence data for "WATCHING" doesn't seem to affect it, and listening to raw client events doesn't seem to make it appear either.
|
||||
.addField("Voice Channel", channel, true)
|
||||
.addField("Category", category, true)
|
||||
.setColor(streamer.displayColor)
|
||||
.setFooter(
|
||||
"Stream Started",
|
||||
streamer.guild.iconURL({
|
||||
dynamic: true
|
||||
}) || undefined
|
||||
)
|
||||
.setTimestamp(streamStart);
|
||||
|
||||
if (description) {
|
||||
embed.setDescription(description);
|
||||
}
|
||||
if (description) embed.setDescription(description);
|
||||
if (thumbnail) embed.setThumbnail(thumbnail);
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
@ -40,7 +61,7 @@ client.on("voiceStateUpdate", async (before, after) => {
|
|||
// 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);
|
||||
const {streamingChannel, streamingRoles, members} = Storage.getGuild(after.guild.id);
|
||||
|
||||
if (streamingChannel) {
|
||||
const member = after.member!;
|
||||
|
@ -50,13 +71,42 @@ client.on("voiceStateUpdate", async (before, after) => {
|
|||
// Although checking the bot's permission to send might seem like a good idea, having the error be thrown will cause it to show up in the last channel rather than just show up in the console.
|
||||
if (textChannel instanceof TextChannel) {
|
||||
if (isStartStreamEvent) {
|
||||
const streamStart = Date.now();
|
||||
let streamNotificationPing = "";
|
||||
let category = "None";
|
||||
|
||||
// Check the category if there's one set then ping that role.
|
||||
if (member.id in members) {
|
||||
const roleID = members[member.id].streamCategory;
|
||||
|
||||
// Only continue if they set a valid category.
|
||||
if (roleID && roleID in streamingRoles) {
|
||||
streamNotificationPing = `<@&${roleID}>`;
|
||||
category = streamingRoles[roleID];
|
||||
}
|
||||
}
|
||||
|
||||
streamList.set(member.id, {
|
||||
streamer: member,
|
||||
channel: voiceChannel,
|
||||
message: await textChannel.send(getStreamEmbed(member, voiceChannel)),
|
||||
category,
|
||||
message: await textChannel.send(
|
||||
streamNotificationPing,
|
||||
getStreamEmbed(member, voiceChannel, streamStart, category)
|
||||
),
|
||||
update(this: Stream) {
|
||||
this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description));
|
||||
}
|
||||
this.message.edit(
|
||||
getStreamEmbed(
|
||||
this.streamer,
|
||||
this.channel,
|
||||
streamStart,
|
||||
this.category,
|
||||
this.description,
|
||||
this.thumbnail
|
||||
)
|
||||
);
|
||||
},
|
||||
streamStart
|
||||
});
|
||||
} else if (isStopStreamEvent) {
|
||||
if (streamList.has(member.id)) {
|
||||
|
|
|
@ -57,18 +57,30 @@ class User {
|
|||
}
|
||||
}
|
||||
|
||||
class Member {
|
||||
public streamCategory: string | null;
|
||||
|
||||
constructor(data?: GenericJSON) {
|
||||
this.streamCategory = select(data?.streamCategory, null, String);
|
||||
}
|
||||
}
|
||||
|
||||
class Guild {
|
||||
public prefix: string | null;
|
||||
public welcomeType: "none" | "text" | "graphical";
|
||||
public welcomeChannel: string | null;
|
||||
public welcomeMessage: string | null;
|
||||
public streamingChannel: string | null;
|
||||
public streamingRoles: {[role: string]: string}; // Role ID: Category Name
|
||||
public members: {[id: string]: Member};
|
||||
|
||||
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);
|
||||
this.streamingRoles = {};
|
||||
this.members = {};
|
||||
|
||||
switch (data?.welcomeType) {
|
||||
case "text":
|
||||
|
@ -81,6 +93,37 @@ class Guild {
|
|||
this.welcomeType = "none";
|
||||
break;
|
||||
}
|
||||
|
||||
if (data?.streamingRoles) {
|
||||
for (const id in data.streamingRoles) {
|
||||
const category = data.streamingRoles[id];
|
||||
|
||||
if (/\d{17,}/g.test(id) && typeof category === "string") {
|
||||
this.streamingRoles[id] = category;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data?.members) {
|
||||
for (let id in data.members) {
|
||||
if (/\d{17,}/g.test(id)) {
|
||||
this.members[id] = new Member(data.members[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a member's profile if they exist and generate one if not. */
|
||||
public getMember(id: string): Member {
|
||||
if (!/\d{17,}/g.test(id))
|
||||
console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`);
|
||||
|
||||
if (id in this.members) return this.members[id];
|
||||
else {
|
||||
const member = new Member();
|
||||
this.members[id] = member;
|
||||
return member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue