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,23 +1,28 @@ | ||||||
| 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).", | ||||||
|  |     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; |                 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: { |  | ||||||
|                     description: "No description set.", |  | ||||||
|                     color: member!.displayColor |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|                 } else { |                 } else { | ||||||
|             // Alternatively, I could make descriptions last outside of just one stream.
 |  | ||||||
|                     send("You can only use this command when streaming."); |                     send("You can only use this command when streaming."); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|  | @ -29,16 +34,109 @@ export default new NamedCommand({ | ||||||
|                         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 { |                     } else { | ||||||
|                 // Alternatively, I could make descriptions last outside of just one stream.
 |  | ||||||
|                         send("You can only use this command when streaming."); |                         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…
	
	Add table
		Add a link
		
	
		Reference in a new issue