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: | ||||
|         branches: | ||||
|             - typescript | ||||
|             - docker | ||||
| 
 | ||||
| jobs: | ||||
|     analyze: | ||||
|  | @ -16,10 +15,13 @@ jobs: | |||
|                   fetch-depth: 2 | ||||
| 
 | ||||
|             - name: Setup Node.JS | ||||
|               uses: actions/setup-node@v2-beta | ||||
|               uses: actions/setup-node@v2 | ||||
|               with: | ||||
|                   node-version: "12" | ||||
|             - run: npm ci | ||||
|                   node-version: "14" | ||||
|             # 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 | ||||
|               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 . . | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,6 +43,9 @@ | |||
|         "tsc-watch": "^4.2.9", | ||||
|         "typescript": "^3.9.7" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "fsevents": "^2.1.2" | ||||
|     }, | ||||
|     "author": "Keanu Timmermans", | ||||
|     "license": "MIT", | ||||
|     "keywords": [ | ||||
|  |  | |||
|  | @ -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,5 +1,5 @@ | |||
| import { | ||||
|     Command, | ||||
|     RestCommand, | ||||
|     NamedCommand, | ||||
|     CHANNEL_TYPE, | ||||
|     getPermissionName, | ||||
|  | @ -51,7 +51,7 @@ export default new NamedCommand({ | |||
|                 .setColor(EMBED_COLOR); | ||||
|         }); | ||||
|     }, | ||||
|     any: new Command({ | ||||
|     any: new RestCommand({ | ||||
|         async run({send, args}) { | ||||
|             const resultingBlob = await getCommandInfo(args); | ||||
|             if (typeof resultingBlob === "string") return send(resultingBlob); | ||||
|  |  | |||
|  | @ -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(", ")}\`` | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
| }); | ||||
|  |  | |||
|  | @ -139,12 +139,16 @@ export interface GenericJSON { | |||
|     [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 { | ||||
|     private __meta__ = "generic"; | ||||
|     private __meta__!: string; | ||||
| 
 | ||||
|     constructor(tag?: string) { | ||||
|         this.__meta__ = tag || this.__meta__; | ||||
|         Object.defineProperty(this, "__meta__", { | ||||
|             value: tag || "generic", | ||||
|             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 {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…
	
	Add table
		Add a link
		
	
		Reference in a new issue