mirror of
https://github.com/keanuplayz/TravBot-v3.git
synced 2024-08-15 02:33:12 +00:00
Merge branch 'typescript' into HEAD
This commit is contained in:
commit
dd572e637d
9 changed files with 362 additions and 57 deletions
10
.github/workflows/image.yml
vendored
10
.github/workflows/image.yml
vendored
|
@ -3,7 +3,6 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- typescript
|
- typescript
|
||||||
- docker
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
@ -16,10 +15,13 @@ jobs:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Setup Node.JS
|
- name: Setup Node.JS
|
||||||
uses: actions/setup-node@v2-beta
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "12"
|
node-version: "14"
|
||||||
- run: npm ci
|
# https://github.com/npm/cli/issues/558#issuecomment-580018468
|
||||||
|
# Error: "npm ERR! fsevents not accessible from jest-haste-map"
|
||||||
|
# (supposed to just be a warning b/c optional dependency, but CI environment causes it to fail)
|
||||||
|
- run: npm i
|
||||||
|
|
||||||
- name: Build codebase
|
- name: Build codebase
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
67
Dockerfile
67
Dockerfile
|
@ -1,4 +1,69 @@
|
||||||
FROM node:current-alpine
|
###############
|
||||||
|
# Solution #1 #
|
||||||
|
###############
|
||||||
|
# https://github.com/geekduck/docker-node-canvas
|
||||||
|
# Took 20m 55s
|
||||||
|
|
||||||
|
#FROM node:12
|
||||||
|
#
|
||||||
|
#RUN apt-get update \
|
||||||
|
# && apt-get install -qq build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
|
||||||
|
#
|
||||||
|
#RUN mkdir -p /opt/node/js \
|
||||||
|
# && cd /opt/node \
|
||||||
|
# && npm i canvas
|
||||||
|
#
|
||||||
|
#WORKDIR /opt/node/js
|
||||||
|
#
|
||||||
|
#ENTRYPOINT ["node"]
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Solution #2 #
|
||||||
|
###############
|
||||||
|
# https://github.com/Automattic/node-canvas/issues/729#issuecomment-352991456
|
||||||
|
# Took 22m 50s
|
||||||
|
|
||||||
|
#FROM ubuntu:xenial
|
||||||
|
#
|
||||||
|
#RUN apt-get update && apt-get install -y \
|
||||||
|
# curl \
|
||||||
|
# git
|
||||||
|
#
|
||||||
|
#RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \
|
||||||
|
# && curl -sL https://deb.nodesource.com/setup_8.x | bash - \
|
||||||
|
# && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
|
||||||
|
# && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
#
|
||||||
|
#RUN apt-get update && apt-get install -y \
|
||||||
|
# nodejs \
|
||||||
|
# yarn \
|
||||||
|
# libcairo2-dev \
|
||||||
|
# libjpeg-dev \
|
||||||
|
# libpango1.0-dev \
|
||||||
|
# libgif-dev \
|
||||||
|
# libpng-dev \
|
||||||
|
# build-essential \
|
||||||
|
# g++
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Solution #3 #
|
||||||
|
###############
|
||||||
|
# https://github.com/Automattic/node-canvas/issues/866#issuecomment-330001221
|
||||||
|
# Took 7m 29s
|
||||||
|
|
||||||
|
FROM node:10.16.0-alpine
|
||||||
|
FROM mhart/alpine-node:8.5.0
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
g++ \
|
||||||
|
cairo-dev \
|
||||||
|
jpeg-dev \
|
||||||
|
pango-dev \
|
||||||
|
bash \
|
||||||
|
imagemagick
|
||||||
|
|
||||||
|
# The rest of the commands to execute
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"tsc-watch": "^4.2.9",
|
"tsc-watch": "^4.2.9",
|
||||||
"typescript": "^3.9.7"
|
"typescript": "^3.9.7"
|
||||||
},
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "^2.1.2"
|
||||||
|
},
|
||||||
"author": "Keanu Timmermans",
|
"author": "Keanu Timmermans",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "../../core";
|
} from "../../core";
|
||||||
import {clean} from "../../lib";
|
import {clean} from "../../lib";
|
||||||
import {Config, Storage} from "../../structures";
|
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";
|
import {logs} from "../../modules/globals";
|
||||||
|
|
||||||
function getLogBuffer(type: string) {
|
function getLogBuffer(type: string) {
|
||||||
|
@ -162,6 +162,46 @@ export default new NamedCommand({
|
||||||
send(`Successfully set this server's stream notifications channel to ${result}.`);
|
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.",
|
run: "You have to enter some code to execute first.",
|
||||||
any: new RestCommand({
|
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.
|
// 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 {
|
try {
|
||||||
let evaled = eval(combined);
|
let evaled = eval(combined);
|
||||||
if (typeof evaled !== "string") evaled = require("util").inspect(evaled);
|
if (typeof evaled !== "string") evaled = require("util").inspect(evaled);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Command,
|
RestCommand,
|
||||||
NamedCommand,
|
NamedCommand,
|
||||||
CHANNEL_TYPE,
|
CHANNEL_TYPE,
|
||||||
getPermissionName,
|
getPermissionName,
|
||||||
|
@ -51,7 +51,7 @@ export default new NamedCommand({
|
||||||
.setColor(EMBED_COLOR);
|
.setColor(EMBED_COLOR);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
any: new Command({
|
any: new RestCommand({
|
||||||
async run({send, args}) {
|
async run({send, args}) {
|
||||||
const resultingBlob = await getCommandInfo(args);
|
const resultingBlob = await getCommandInfo(args);
|
||||||
if (typeof resultingBlob === "string") return send(resultingBlob);
|
if (typeof resultingBlob === "string") return send(resultingBlob);
|
||||||
|
|
|
@ -1,44 +1,142 @@
|
||||||
import {NamedCommand, RestCommand} from "../../core";
|
import {NamedCommand, RestCommand} from "../../core";
|
||||||
import {streamList} from "../../modules/streamNotifications";
|
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({
|
export default new NamedCommand({
|
||||||
description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`",
|
description: "Modifies the current embed for your stream",
|
||||||
async run({send, author, member}) {
|
run: "You need to specify whether to set the description or the image (`desc` and `img` respectively).",
|
||||||
const userID = author.id;
|
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)) {
|
if (streamList.has(userID)) {
|
||||||
const stream = streamList.get(userID)!;
|
const stream = streamList.get(userID)!;
|
||||||
stream.description = "No description set.";
|
stream.description = undefined;
|
||||||
stream.update();
|
stream.update();
|
||||||
send(`Successfully set the stream description to:`, {
|
send("Successfully removed the stream description.");
|
||||||
embed: {
|
} else {
|
||||||
description: "No description set.",
|
send("You can only use this command when streaming.");
|
||||||
color: member!.displayColor
|
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
} else {
|
any: new RestCommand({
|
||||||
// Alternatively, I could make descriptions last outside of just one stream.
|
async run({send, author, member, combined}) {
|
||||||
send("You can only use this command when streaming.");
|
const userID = author.id;
|
||||||
}
|
|
||||||
},
|
|
||||||
any: new RestCommand({
|
|
||||||
async run({send, author, member, combined}) {
|
|
||||||
const userID = author.id;
|
|
||||||
|
|
||||||
if (streamList.has(userID)) {
|
if (streamList.has(userID)) {
|
||||||
const stream = streamList.get(userID)!;
|
const stream = streamList.get(userID)!;
|
||||||
stream.description = combined;
|
stream.description = combined;
|
||||||
stream.update();
|
stream.update();
|
||||||
send(`Successfully set the stream description to:`, {
|
send("Successfully set the stream description to:", {
|
||||||
embed: {
|
embed: {
|
||||||
description: stream.description,
|
description: stream.description,
|
||||||
color: member!.displayColor
|
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(", ")}\``
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -139,12 +139,16 @@ export interface GenericJSON {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In order to define a file to write to while also not:
|
||||||
|
// - Using the delete operator (which doesn't work on properties which cannot be undefined)
|
||||||
|
// - Assigning it first then using Object.defineProperty (which raises a flag on CodeQL)
|
||||||
|
// A non-null assertion is used on the class property to say that it'll definitely be assigned.
|
||||||
export abstract class GenericStructure {
|
export abstract class GenericStructure {
|
||||||
private __meta__ = "generic";
|
private __meta__!: string;
|
||||||
|
|
||||||
constructor(tag?: string) {
|
constructor(tag?: string) {
|
||||||
this.__meta__ = tag || this.__meta__;
|
|
||||||
Object.defineProperty(this, "__meta__", {
|
Object.defineProperty(this, "__meta__", {
|
||||||
|
value: tag || "generic",
|
||||||
enumerable: false
|
enumerable: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {client} from "../index";
|
||||||
import {Storage} from "../structures";
|
import {Storage} from "../structures";
|
||||||
|
|
||||||
type Stream = {
|
type Stream = {
|
||||||
streamer: GuildMember;
|
streamer: GuildMember;
|
||||||
channel: VoiceChannel;
|
channel: VoiceChannel;
|
||||||
|
category: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
thumbnail?: string;
|
||||||
message: Message;
|
message: Message;
|
||||||
|
streamStart: number;
|
||||||
update: () => void;
|
update: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,10 +17,17 @@ type Stream = {
|
||||||
export const streamList = new Collection<string, Stream>();
|
export const streamList = new Collection<string, Stream>();
|
||||||
|
|
||||||
// Probably find a better, DRY way of doing this.
|
// 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 user = streamer.user;
|
||||||
const embed = new MessageEmbed()
|
const embed = new MessageEmbed()
|
||||||
.setTitle(`Stream: \`#${channel.name}\``)
|
.setTitle(channel.name)
|
||||||
.setAuthor(
|
.setAuthor(
|
||||||
streamer.nickname ?? user.username,
|
streamer.nickname ?? user.username,
|
||||||
user.avatarURL({
|
user.avatarURL({
|
||||||
|
@ -25,11 +35,22 @@ function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, descriptio
|
||||||
format: "png"
|
format: "png"
|
||||||
}) ?? user.defaultAvatarURL
|
}) ?? 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) {
|
if (description) embed.setDescription(description);
|
||||||
embed.setDescription(description);
|
if (thumbnail) embed.setThumbnail(thumbnail);
|
||||||
}
|
|
||||||
|
|
||||||
return embed;
|
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.
|
// 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) {
|
if (isStartStreamEvent || isStopStreamEvent) {
|
||||||
const {streamingChannel} = Storage.getGuild(after.guild.id);
|
const {streamingChannel, streamingRoles, members} = Storage.getGuild(after.guild.id);
|
||||||
|
|
||||||
if (streamingChannel) {
|
if (streamingChannel) {
|
||||||
const member = after.member!;
|
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.
|
// 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 (textChannel instanceof TextChannel) {
|
||||||
if (isStartStreamEvent) {
|
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, {
|
streamList.set(member.id, {
|
||||||
streamer: member,
|
streamer: member,
|
||||||
channel: voiceChannel,
|
channel: voiceChannel,
|
||||||
message: await textChannel.send(getStreamEmbed(member, voiceChannel)),
|
category,
|
||||||
|
message: await textChannel.send(
|
||||||
|
streamNotificationPing,
|
||||||
|
getStreamEmbed(member, voiceChannel, streamStart, category)
|
||||||
|
),
|
||||||
update(this: Stream) {
|
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) {
|
} else if (isStopStreamEvent) {
|
||||||
if (streamList.has(member.id)) {
|
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 {
|
class Guild {
|
||||||
public prefix: string | null;
|
public prefix: string | null;
|
||||||
public welcomeType: "none" | "text" | "graphical";
|
public welcomeType: "none" | "text" | "graphical";
|
||||||
public welcomeChannel: string | null;
|
public welcomeChannel: string | null;
|
||||||
public welcomeMessage: string | null;
|
public welcomeMessage: string | null;
|
||||||
public streamingChannel: string | null;
|
public streamingChannel: string | null;
|
||||||
|
public streamingRoles: {[role: string]: string}; // Role ID: Category Name
|
||||||
|
public members: {[id: string]: Member};
|
||||||
|
|
||||||
constructor(data?: GenericJSON) {
|
constructor(data?: GenericJSON) {
|
||||||
this.prefix = select(data?.prefix, null, String);
|
this.prefix = select(data?.prefix, null, String);
|
||||||
this.welcomeChannel = select(data?.welcomeChannel, null, String);
|
this.welcomeChannel = select(data?.welcomeChannel, null, String);
|
||||||
this.welcomeMessage = select(data?.welcomeMessage, null, String);
|
this.welcomeMessage = select(data?.welcomeMessage, null, String);
|
||||||
this.streamingChannel = select(data?.streamingChannel, null, String);
|
this.streamingChannel = select(data?.streamingChannel, null, String);
|
||||||
|
this.streamingRoles = {};
|
||||||
|
this.members = {};
|
||||||
|
|
||||||
switch (data?.welcomeType) {
|
switch (data?.welcomeType) {
|
||||||
case "text":
|
case "text":
|
||||||
|
@ -81,6 +93,37 @@ class Guild {
|
||||||
this.welcomeType = "none";
|
this.welcomeType = "none";
|
||||||
break;
|
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 a new issue