From 71fd770a2715202fe37891e5a6f8b3cb5441ce6a Mon Sep 17 00:00:00 2001 From: pikiou Date: Sat, 18 Mar 2023 04:08:48 +0100 Subject: [PATCH] Add Discord bot, reactivate team-assign --- .eslintrc.js | 1 + jest/config.js | 1 + package.json | 2 + public/favicon.ico | Bin 22382 -> 6782 bytes src/components/Asks/index.tsx | 6 +- src/components/LoginForm/index.tsx | 2 +- src/components/Teams/TeamList.tsx | 4 +- src/components/VolunteerBoard/Board.tsx | 12 +- .../VolunteerBoard/DayWishes/DayWishes.tsx | 6 + src/config/default.ts | 1 + src/routes/index.ts | 12 +- src/server/discordBot.ts | 405 ++++++++++++++++++ src/server/gsheets/accessors.ts | 10 +- src/server/gsheets/discordRoles.ts | 14 + src/server/gsheets/localDb.ts | 2 + src/server/index.ts | 12 + src/server/notifications.ts | 4 +- src/services/discordRoles.ts | 28 ++ src/store/teamList.ts | 4 + src/types/index.d.ts | 2 + webpack/base.config.ts | 2 + yarn.lock | 167 +++++++- 22 files changed, 674 insertions(+), 23 deletions(-) mode change 100644 => 100755 public/favicon.ico create mode 100644 src/server/discordBot.ts create mode 100644 src/server/gsheets/discordRoles.ts create mode 100644 src/services/discordRoles.ts diff --git a/.eslintrc.js b/.eslintrc.js index 6dabd63..88bb4b1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,6 +58,7 @@ module.exports = { __SERVER__: true, __DEV__: true, __LOCAL__: false, + __REGISTER_DISCORD_COMMANDS__: false, __TEST__: false, }, } diff --git a/jest/config.js b/jest/config.js index f907412..23e308f 100644 --- a/jest/config.js +++ b/jest/config.js @@ -22,6 +22,7 @@ module.exports = { __CLIENT__: true, __SERVER__: false, __LOCAL__: false, + __REGISTER_DISCORD_COMMANDS__: false, __TEST__: true, }, maxConcurrency: 50, diff --git a/package.json b/package.json index 5b6c268..82a34bb 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "ser": "yarn dev:build && node ./public/server", "dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts", "local-start": "cross-env LOCAL=true yarn build && node ./public/server", + "discord-register": "cross-env REGISTER_DISCORD_COMMANDS=true yarn build && node ./public/server", "start": "node ./public/server", "build": "run-s build:*", "build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts", @@ -94,6 +95,7 @@ "core-js": "^3.15.2", "cross-env": "^7.0.3", "detect-node": "^2.1.0", + "discord.js": "^14.7.1", "express": "^4.17.1", "fs": "^0.0.1-security", "google-auth-library": "^7.10.1", diff --git a/public/favicon.ico b/public/favicon.ico old mode 100644 new mode 100755 index c2c86b859eaa20639adf92ff979c2be8d580433e..3f747cbedbdd7142d4d1a311f882f228bc36d4d1 GIT binary patch literal 6782 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 literal 22382 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 QPNaAH_W=L@JAozeZ|l@fegFUf diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx index e343c08..f1f7f39 100644 --- a/src/components/Asks/index.tsx +++ b/src/components/Asks/index.tsx @@ -10,7 +10,7 @@ import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes" // import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting" // import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals" // import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo" -// import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes" +import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes" // import { // AskParticipationDetails, // fetchFor as fetchForParticipationDetails, @@ -27,7 +27,7 @@ const Asks = (): JSX.Element | null => { AskDiscord(asks, 5) AskDayWishes(asks, 10) - // AskTeamWishes(asks, 11) + AskTeamWishes(asks, 11) // AskParticipationDetails(asks, 12) // AskPersonalInfo(asks, 15) // AskHosting(asks, 20) @@ -72,7 +72,7 @@ export const fetchFor = [ ...fetchForDayWishes, // ...fetchForHosting, // ...fetchForMeals, - // ...fetchForTeamWishes, + ...fetchForTeamWishes, // ...fetchForParticipationDetails, // ...fetchForPersonalInfo, ] diff --git a/src/components/LoginForm/index.tsx b/src/components/LoginForm/index.tsx index 8b53bbf..36f8597 100644 --- a/src/components/LoginForm/index.tsx +++ b/src/components/LoginForm/index.tsx @@ -29,7 +29,7 @@ const LoginForm = (): JSX.Element => { return (
- Si tu es bénévole, connecte-toi pour accéder à ton espace. + Si tu es bénévole ou que tu l'as déjà été, connecte-toi pour accéder à ton espace.
diff --git a/src/components/Teams/TeamList.tsx b/src/components/Teams/TeamList.tsx index cf4ab3a..790d037 100644 --- a/src/components/Teams/TeamList.tsx +++ b/src/components/Teams/TeamList.tsx @@ -2,10 +2,10 @@ import React, { memo } from "react" import { useSelector } from "react-redux" import styles from "./styles.module.scss" import TeamItem from "./TeamItem" -import { selectSortedActiveTeams } from "../../store/teamList" +import { selectSortedTeams } from "../../store/teamList" const TeamList: React.FC = (): JSX.Element | null => { - const teams = useSelector(selectSortedActiveTeams) + const teams = useSelector(selectSortedTeams) if (!teams || teams.length === 0) return null return ( diff --git a/src/components/VolunteerBoard/Board.tsx b/src/components/VolunteerBoard/Board.tsx index c9103f4..da4bd87 100644 --- a/src/components/VolunteerBoard/Board.tsx +++ b/src/components/VolunteerBoard/Board.tsx @@ -7,8 +7,8 @@ import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal" // import MealsFormModal from "./MealsForm/MealsFormModal" // import ParticipationDetails from "./ParticipationDetails/ParticipationDetails" // import ParticipationDetailsFormModal from "./ParticipationDetailsForm/ParticipationDetailsFormModal" -// import TeamWishes from "./TeamWishes/TeamWishes" -// import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal" +import TeamWishes from "./TeamWishes/TeamWishes" +import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal" // import VolunteerTeam from "./VolunteerTeam/VolunteerTeam" import withUserConnected from "../../utils/withUserConnected" import ContentTitle from "../ui/Content/ContentTitle" @@ -16,7 +16,7 @@ import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm // import { fetchFor as fetchForHostingForm } from "./HostingForm/HostingForm" // import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm" // import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm" -// import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm" +import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm" import { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm" import PersonalInfo from "./PersonalInfo/PersonalInfo" import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal" @@ -43,10 +43,10 @@ const Board: FC = (): JSX.Element => ( {/* - + */} - + {/* @@ -66,5 +66,5 @@ export const fetchFor = [ // ...fetchForHostingForm, // ...fetchForMealsForm, // ...fetchForParticipationDetailsForm, - // ...fetchForTeamWishesForm, + ...fetchForTeamWishesForm, ] diff --git a/src/components/VolunteerBoard/DayWishes/DayWishes.tsx b/src/components/VolunteerBoard/DayWishes/DayWishes.tsx index 041beec..eeeb082 100644 --- a/src/components/VolunteerBoard/DayWishes/DayWishes.tsx +++ b/src/components/VolunteerBoard/DayWishes/DayWishes.tsx @@ -38,6 +38,12 @@ const DayWishes: FC = (): JSX.Element | null => { Je ne sais pas encore si je participerai à PeL 2023
)} + {participation === "à distance" && ( +
+ Je participerai à PeL 2023 ! Sans y + être pendant le weekend. +
+ )} {participation === "inconnu" && (
Participation à PeL 2023{" "} diff --git a/src/config/default.ts b/src/config/default.ts index 5dc2c59..3b4d494 100755 --- a/src/config/default.ts +++ b/src/config/default.ts @@ -16,4 +16,5 @@ export default { ], }, DEV_JWT_SECRET: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!", + DEV_DISCORD_TOKEN: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!", } diff --git a/src/routes/index.ts b/src/routes/index.ts index 13395e2..309a7b2 100755 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -11,7 +11,7 @@ import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../page import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register" import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge" // import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans" -// import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning" +import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning" import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" @@ -51,11 +51,11 @@ export default [ // component: AsyncLoans, // loadData: loadLoansData, // }, - // { - // path: "/emprunter", - // component: AsyncLoaning, - // loadData: loadLoaningData, - // }, + { + path: "/emprunter", + component: AsyncLoaning, + loadData: loadLoaningData, + }, { path: "/fiches", component: AsyncKnowledgeCards, diff --git a/src/server/discordBot.ts b/src/server/discordBot.ts new file mode 100644 index 0000000..d57f7a3 --- /dev/null +++ b/src/server/discordBot.ts @@ -0,0 +1,405 @@ +import _ from "lodash" +import { + Client, + GatewayIntentBits, + Collection, + Events, + SlashCommandBuilder, + CommandInteraction, + /* REST, Routes, */ Partials, + MessageReaction, + PartialMessageReaction, + Guild, + User, + PartialUser, +} from "discord.js" +import { promises as fs, constants } from "fs" +import path from "path" + +import { translationVolunteer, Volunteer, VolunteerWithoutId } from "../services/volunteers" +import { + translationDiscordRoles, + DiscordRole, + DiscordRoleWithoutId, +} from "../services/discordRoles" +import { getSheet } from "./gsheets/accessors" +import config from "../config" + +let cachedToken: string +// let cachedClientId: string +let cachedGuildId: string +const CREDS_PATH = path.resolve(process.cwd(), "access/discordToken.json") +getCreds() // Necessary until we can make async express middleware + +type Command = { + data: SlashCommandBuilder + execute: (interaction: CommandInteraction) => Promise +} +const commands: Collection = new Collection() + +// const userCommand: Command = { +// data: new SlashCommandBuilder() +// .setName('user') +// .setDescription('Provides information about the user.'), +// async execute(interaction: CommandInteraction) { + +// const { commandName } = interaction + +// if (commandName === 'user') { +// const message = await interaction.reply({ content: 'You can react with Unicode emojis!', fetchReply: true }) +// message.react('😄') +// } +// }, +// } + +let hasDiscordAccessReturn: boolean | undefined +export async function hasDiscordAccess(): Promise { + if (hasDiscordAccessReturn !== undefined) { + return hasDiscordAccessReturn + } + try { + // eslint-disable-next-line no-bitwise + await fs.access(CREDS_PATH, constants.R_OK | constants.W_OK) + hasDiscordAccessReturn = true + } catch { + hasDiscordAccessReturn = false + } + return hasDiscordAccessReturn +} + +// export async function discordRegisterCommands(): Promise { +// if (!__REGISTER_DISCORD_COMMANDS__) { +// return +// } + +// if (!(await hasDiscordAccess())) { +// console.error(`Discord bot: no creds found, not running bot`) +// return +// } + +// await getCreds() + +// const commandsToRegister = [] +// commandsToRegister.push(userCommand.data.toJSON()) + +// const rest = new REST({ version: '10' }).setToken(cachedToken) + +// try { +// await rest.put( +// Routes.applicationGuildCommands(cachedClientId, cachedGuildId), +// { body: commandsToRegister }, +// ) +// } catch (error) { +// console.error(error) +// } + +// process.exit() +// } + +export async function discordBot(): Promise { + try { + if (__REGISTER_DISCORD_COMMANDS__) { + return + } + + if (!(await hasDiscordAccess())) { + console.error(`Discord bot: no creds found, not running bot`) + return + } + await getCreds() + + const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.MessageContent, + ], + partials: [Partials.Message, Partials.Channel, Partials.Reaction], + }) + + // commands.set(userCommand.data.name, userCommand) + + client.once(Events.ClientReady, (localClient) => { + setInterval(() => setBotReactions(localClient), 5 * 60 * 1000) + setTimeout(() => setBotReactions(localClient), 20 * 1000) + + setInterval(() => setAllRoles(localClient), 5 * 60 * 1000) + setTimeout(() => setAllRoles(localClient), 5 * 1000) + }) + + client.on(Events.InteractionCreate, async (interaction) => { + if (!interaction.isChatInputCommand()) return + + const command = commands.get(interaction.commandName) + + if (!command) { + console.error(`No command matching ${interaction.commandName} was found.`) + return + } + + try { + await command.execute(interaction) + } catch (error) { + console.error(error) + } + }) + + client.on(Events.MessageReactionAdd, async (reaction, user) => { + await fetchPartial(reaction) + + await setRolesFromEmoji(client, user, reaction, "add") + }) + + client.on(Events.MessageReactionRemove, async (reaction, user) => { + await fetchPartial(reaction) + + await setRolesFromEmoji(client, user, reaction, "remove") + }) + + client.login(cachedToken) + } catch (error) { + console.error("Discord error", error) + } +} + +async function setBotReactions(client: Client) { + try { + const discordRolesSheet = await getSheet( + "DiscordRoles", + new DiscordRole(), + translationDiscordRoles + ) + const discordRolesList = await discordRolesSheet.getList() + if (!discordRolesList) { + return + } + + client.channels.cache.each(async (channel) => { + if (!channel.isTextBased()) { + return + } + + discordRolesList.forEach(async (discordRole: DiscordRole) => { + let message + try { + message = await channel.messages.fetch(discordRole.messageId) + } catch (error) { + return + } + + const reaction = message.reactions.cache.find( + (r) => r.emoji.name === discordRole.emoji + ) + if (reaction) { + return + } + await message.react(discordRole.emoji) + }) + }) + } catch (error) { + console.error("Error in setBotReactions", error) + } +} + +async function setAllRoles(client: Client) { + try { + const volunteerSheet = await getSheet( + "Volunteers", + new Volunteer(), + translationVolunteer + ) + const volunteerList = await volunteerSheet.getList() + if (!volunteerList) { + return + } + + const volunteerByDiscordId = _.mapKeys(volunteerList, (v) => v.discordId.toString()) + + const guild = await client.guilds.fetch(cachedGuildId) + + if (!guild || !guild.members.cache) { + return + } + + // Set (maybe) volunteer roles + + const volunteerRoleIds: { [key: string]: string } = _.mapValues( + { + oui: "Bénévole", + "peut-etre": "Bénévole incertain", + "à distance": "Bénévole à distance", + non: "", + }, + (v) => (_.isEmpty(v) ? "" : guild.roles.cache.find((role) => role.name === v)?.id || "") + ) + + await setVolunteersRoles( + guild, + volunteerByDiscordId, + volunteerRoleIds, + (volunteer: Volunteer) => volunteer.active + ) + + // Set Team- and Référent- roles + + const teamIds = { + "1": "accueil", + "5": "paillante", + "21": "photo", + "4": "tournois", + // "19": "exposants-asso", ignored because it's mixed with volunteers of last edition + "2": "jav", + "18": "jeux-xl", + "27": "ring", + "8": "coindespetitsjoueurs", + "23": "jeuxdelires", + "3": "jdd", + "9": "figurines", + "10": "jdr", + "28": "jeuxhistoire", + "6": "évènements", + "29": "presse", + "30": "initiation-18xx", + "0": "", + } + + const teamRoleIds: { [key: string]: string } = _.mapValues(teamIds, (v) => + _.isEmpty(v) + ? "" + : guild.roles.cache.find((role) => role.name === `Team-${v}`)?.id || "" + ) + + await setVolunteersRoles(guild, volunteerByDiscordId, teamRoleIds, (volunteer: Volunteer) => + _.includes(["oui", "peut-etre", "à distance"], volunteer.active) + ? `${volunteer.team}` + : "" + ) + + const referentRoleIds: { [key: string]: string } = _.mapValues(teamIds, (v) => + _.isEmpty(v) + ? "" + : guild.roles.cache.find((role) => role.name === `Référent-${v}`)?.id || "" + ) + + await setVolunteersRoles( + guild, + volunteerByDiscordId, + referentRoleIds, + (volunteer: Volunteer) => + _.includes(["oui", "peut-etre", "à distance"], volunteer.active) && + volunteer.roles.includes("référent") + ? `${volunteer.team}` + : "0" + ) + } catch (error) { + console.error("Error in setAllRoles", error) + } +} + +async function setVolunteersRoles( + guild: Guild, + volunteerByDiscordId: { [key: string]: Volunteer }, + volunteerRoleIds: { [key: string]: string }, + funcKey: (volunteer: Volunteer) => string +) { + const members = await guild.members.fetch() + members.each(async (member) => { + const volunteer = volunteerByDiscordId[member.id] + if (!volunteer) { + return + } + + _.forOwn(volunteerRoleIds, (roleId, active) => { + if (!roleId) { + return + } + + const shouldHaveRole = active === funcKey(volunteer) + const hasRole = member.roles.cache.has(roleId) + + if (hasRole && !shouldHaveRole) { + member.roles.remove(roleId) + } else if (!hasRole && shouldHaveRole) { + member.roles.add(roleId) + } + }) + }) +} + +async function setRolesFromEmoji( + client: Client, + user: User | PartialUser, + reaction: MessageReaction | PartialMessageReaction, + action: "add" | "remove" +) { + const discordRolesSheet = await getSheet( + "DiscordRoles", + new DiscordRole(), + translationDiscordRoles + ) + const discordRolesList = await discordRolesSheet.getList() + if (!discordRolesList) { + return + } + + await client.guilds.fetch() + const guild = client.guilds.resolve(cachedGuildId) + + if (!guild || !guild.members.cache) { + return + } + + discordRolesList.forEach(async (discordRole: DiscordRole) => { + if ( + reaction.message.id === discordRole.messageId && + reaction.emoji.name === discordRole.emoji + ) { + const roleId = guild.roles.cache.find((role) => role.name === discordRole.role) + if (!roleId) { + return + } + + const member = guild.members.cache.find((m) => m.id === user.id) + if (!member) { + return + } + await member.fetch() + if (action === "add") { + member.roles.add(roleId) + } else if (action === "remove") { + member.roles.remove(roleId) + } + } + }) +} + +async function fetchPartial(reaction: MessageReaction | PartialMessageReaction): Promise { + if (reaction.partial) { + try { + await reaction.fetch() + } catch (error) { + console.error("Something went wrong when fetching the message", error) + return false + } + } + return true +} + +async function getCreds(): Promise { + if (!cachedToken) { + try { + const credsContent = await fs.readFile(CREDS_PATH) + const parsedCreds = credsContent && JSON.parse(credsContent.toString()) + if (!parsedCreds) { + return + } + cachedToken = parsedCreds.token + // cachedClientId = parsedCreds.clientId + cachedGuildId = parsedCreds.guildId + } catch (e: any) { + cachedToken = config.DEV_DISCORD_TOKEN + } + } +} diff --git a/src/server/gsheets/accessors.ts b/src/server/gsheets/accessors.ts index dcda453..5449cd1 100644 --- a/src/server/gsheets/accessors.ts +++ b/src/server/gsheets/accessors.ts @@ -11,8 +11,8 @@ export { SheetNames } from "./localDb" const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") -const REMOTE_UPDATE_DELAY = 40000 -const DELAY_BETWEEN_ATTEMPTS = 10000 +const REMOTE_UPDATE_DELAY = 120000 +const DELAY_BETWEEN_ATTEMPTS = 30000 const DELAY_BETWEEN_FIRST_LOAD = 1500 let creds: string | undefined | null @@ -59,7 +59,7 @@ export async function getSheet< sheet = new Sheet(sheetName, specimen, translation) await sheet.waitForFirstLoad() sheetList[sheetName] = sheet - setInterval(() => sheet.dbUpdate(), REMOTE_UPDATE_DELAY) + setInterval(() => sheet.dbUpdate(), REMOTE_UPDATE_DELAY * (1 + Math.random() / 10)) } else { sheet = sheetList[sheetName] as Sheet } @@ -265,6 +265,7 @@ export class Sheet< } await tryNTimesVoidReturn(async () => { + console.log(`dbSaveAsync on ${this.name} at ${new Date()}`) // Load sheet into an array of objects const rows = await sheet.getRows() if (!rows[0]) { @@ -317,6 +318,7 @@ export class Sheet< await rows[rowToDelete].delete() } } + console.log(`dbSaveAsync successful on ${this.name} at ${new Date()}`) }) } @@ -330,6 +332,7 @@ export class Sheet< await tryNTimesVoidReturn(async () => { // Load sheet into an array of objects + console.log(`dbLoadAsync on ${this.name} at ${new Date()}`) const rows = (await sheet.getRows()) as StringifiedElement[] const elements: Element[] = [] if (!rows[0]) { @@ -357,6 +360,7 @@ export class Sheet< }) this._state = elements + console.log(`dbLoadAsync successful on ${this.name} at ${new Date()}`) }) } diff --git a/src/server/gsheets/discordRoles.ts b/src/server/gsheets/discordRoles.ts new file mode 100644 index 0000000..658937e --- /dev/null +++ b/src/server/gsheets/discordRoles.ts @@ -0,0 +1,14 @@ +import ExpressAccessors from "./expressAccessors" +import { + DiscordRole, + DiscordRoleWithoutId, + translationDiscordRoles, +} from "../../services/discordRoles" + +const expressAccessor = new ExpressAccessors( + "DiscordRoles", + new DiscordRole(), + translationDiscordRoles +) + +export const discordRolesListGet = expressAccessor.listGet() diff --git a/src/server/gsheets/localDb.ts b/src/server/gsheets/localDb.ts index ebf3dff..9a2a1f8 100644 --- a/src/server/gsheets/localDb.ts +++ b/src/server/gsheets/localDb.ts @@ -16,6 +16,8 @@ export class SheetNames { Boxes = "Boîtes" + DiscordRoles = "Rôles Discord" + Games = "Jeux" Miscs = "Divers" diff --git a/src/server/index.ts b/src/server/index.ts index d8e93b5..3210c9f 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -50,6 +50,7 @@ import { import { wishListGet, wishAdd } from "./gsheets/wishes" import config from "../config" import { notificationsSubscribe, notificationMain } from "./notifications" +import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot" import checkAccess from "./checkAccess" import { hasGSheetsAccess } from "./gsheets/accessors" import { addStatus, showStatusAt } from "./status" @@ -64,6 +65,9 @@ checkAccess() notificationMain() +// discordRegisterCommands() +discordBot() + const app = express() // Allow receiving big images @@ -243,6 +247,14 @@ if (hasPushNotifAccess) { addStatus("Push notif:", chalk.blue(`🚧 offline, simulated`)) } +hasDiscordAccess().then((hasApiAccess: boolean) => { + if (hasApiAccess) { + addStatus("Discord bot:", chalk.green(`✅ online through discord.js`)) + } else { + addStatus("Discord bot:", chalk.blue(`🚧 no creds, disabled`)) + } +}) + hasSecret().then((has: boolean) => { if (has) { addStatus("JWT secret:", chalk.green(`✅ prod private one from file`)) diff --git a/src/server/notifications.ts b/src/server/notifications.ts index 393bba3..854811a 100644 --- a/src/server/notifications.ts +++ b/src/server/notifications.ts @@ -108,7 +108,9 @@ async function notifyAboutAnnouncement(): Promise { } const audience = volunteerList.filter( - (v) => v.acceptsNotifs === "oui" && (v.active === "oui" || v.active === "peut-etre") + (v) => + v.acceptsNotifs === "oui" && + (v.active === "oui" || v.active === "peut-etre" || v.active === "à distance") ) console.error( diff --git a/src/services/discordRoles.ts b/src/services/discordRoles.ts new file mode 100644 index 0000000..07859ed --- /dev/null +++ b/src/services/discordRoles.ts @@ -0,0 +1,28 @@ +/* eslint-disable max-classes-per-file */ +export class DiscordRole { + id = 0 + + messageId = "" + + emoji = "" + + role = "" +} + +export const translationDiscordRoles: { [k in keyof DiscordRole]: string } = { + id: "id", + messageId: "messageId", + emoji: "emoji", + role: "rôle", +} + +export const elementName = "DiscordRoles" + +export type DiscordRoleWithoutId = Omit + +export interface DiscordRole { + id: DiscordRole["id"] + messageId: DiscordRole["messageId"] + emoji: DiscordRole["emoji"] + role: DiscordRole["role"] +} diff --git a/src/store/teamList.ts b/src/store/teamList.ts index 17b096a..e975c99 100644 --- a/src/store/teamList.ts +++ b/src/store/teamList.ts @@ -59,6 +59,10 @@ export const selectTeamList = createSelector( } ) +export const selectSortedTeams = createSelector(selectTeamList, (teams) => + [...teams].sort((a, b) => get(a, "order", 0) - get(b, "order", 0)) +) + export const selectSortedActiveTeams = createSelector(selectTeamList, (teams) => [...teams.filter((team) => get(team, "status") === "active")].sort( (a, b) => get(a, "order", 0) - get(b, "order", 0) diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 48d8021..74fc31b 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -2,6 +2,7 @@ declare const __CLIENT__: boolean declare const __SERVER__: boolean declare const __DEV__: boolean declare const __LOCAL__: boolean +declare const __REGISTER_DISCORD_COMMANDS__: boolean declare const __TEST__: boolean declare module "*.svg" @@ -19,6 +20,7 @@ declare namespace NodeJS { __SERVER__: boolean __DEV__: boolean __LOCAL__: boolean + __REGISTER_DISCORD_COMMANDS__: boolean __TEST__: boolean $RefreshReg$: () => void $RefreshSig$$: () => void diff --git a/webpack/base.config.ts b/webpack/base.config.ts index a86a6e8..1750fad 100644 --- a/webpack/base.config.ts +++ b/webpack/base.config.ts @@ -9,6 +9,7 @@ import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer" export const isDev = process.env.NODE_ENV === "development" const isLocal = process.env.LOCAL === "true" +const isRegisterDiscordCommands = process.env.REGISTER_DISCORD_COMMANDS === "true" const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => { let loaders: RuleSetUseItem[] = [ { @@ -49,6 +50,7 @@ const getPlugins = (isWeb: boolean) => { __SERVER__: !isWeb, __DEV__: isDev, __LOCAL__: isLocal, + __REGISTER_DISCORD_COMMANDS__: isRegisterDiscordCommands, }), ] diff --git a/yarn.lock b/yarn.lock index 20f1f08..9b2a16f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1037,6 +1037,42 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@discordjs/builders@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.4.0.tgz#b951b5e6ce4e459cd06174ce50dbd51c254c1d47" + integrity sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA== + dependencies: + "@discordjs/util" "^0.1.0" + "@sapphire/shapeshift" "^3.7.1" + discord-api-types "^0.37.20" + fast-deep-equal "^3.1.3" + ts-mixer "^6.0.2" + tslib "^2.4.1" + +"@discordjs/collection@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3" + integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg== + +"@discordjs/rest@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.5.0.tgz#dc15474ab98cf6f31291bf61bbc72bcf4f30cea2" + integrity sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA== + dependencies: + "@discordjs/collection" "^1.3.0" + "@discordjs/util" "^0.1.0" + "@sapphire/async-queue" "^1.5.0" + "@sapphire/snowflake" "^3.2.2" + discord-api-types "^0.37.23" + file-type "^18.0.0" + tslib "^2.4.1" + undici "^5.13.0" + +"@discordjs/util@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-0.1.0.tgz#e42ca1bf407bc6d9adf252877d1b206e32ba369a" + integrity sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ== + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -1442,6 +1478,24 @@ redux-thunk "^2.4.1" reselect "^4.1.5" +"@sapphire/async-queue@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8" + integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== + +"@sapphire/shapeshift@^3.7.1": + version "3.8.1" + resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz#b98dc6a7180f9b38219267917b2e6fa33f9ec656" + integrity sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw== + dependencies: + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + +"@sapphire/snowflake@^3.2.2": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.4.0.tgz#25c012158a9feea2256c718985dbd6c1859a5022" + integrity sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw== + "@sendgrid/client@^7.7.0": version "7.7.0" resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.7.0.tgz#f8f67abd604205a0d0b1af091b61517ef465fdbf" @@ -1556,6 +1610,11 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2164,6 +2223,13 @@ anymatch "^3.0.0" source-map "^0.6.0" +"@types/ws@^8.5.3": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + "@types/xml2js@^0.4.11": version "0.4.11" resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.11.tgz#bf46a84ecc12c41159a7bd9cf51ae84129af0e79" @@ -3102,6 +3168,13 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -4006,6 +4079,29 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discord-api-types@^0.37.20, discord-api-types@^0.37.23: + version "0.37.31" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.31.tgz#128d33d641fd9a92fba97a47d7052e1f5694ec27" + integrity sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ== + +discord.js@^14.7.1: + version "14.7.1" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.7.1.tgz#26079d0ff4d27daf02480a403c456121f0682bd9" + integrity sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA== + dependencies: + "@discordjs/builders" "^1.4.0" + "@discordjs/collection" "^1.3.0" + "@discordjs/rest" "^1.4.0" + "@discordjs/util" "^0.1.0" + "@sapphire/snowflake" "^3.2.2" + "@types/ws" "^8.5.3" + discord-api-types "^0.37.20" + fast-deep-equal "^3.1.3" + lodash.snakecase "^4.1.1" + tslib "^2.4.1" + undici "^5.13.0" + ws "^8.11.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -4834,6 +4930,15 @@ file-type@^12.0.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9" integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== +file-type@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-18.2.0.tgz#c2abec00d1af0f09151e1549e3588aab0bac5001" + integrity sha512-M3RQMWY3F2ykyWZ+IHwNCjpnUmukYhtdkGGC1ZVEUb0ve5REGF7NNJ4Q9ehCUabtQKtSVFOMbFTXgJlFb0DQIg== + dependencies: + readable-web-to-node-stream "^3.0.2" + strtok3 "^7.0.0" + token-types "^5.0.1" + file-type@^3.8.0: version "3.9.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" @@ -5648,7 +5753,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7014,6 +7119,11 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== +lodash.snakecase@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -7990,6 +8100,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +peek-readable@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" + integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -8787,6 +8902,13 @@ readable-stream@^3.1.1, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-web-to-node-stream@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -9495,6 +9617,11 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -9665,6 +9792,14 @@ strnum@^1.0.4: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +strtok3@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-7.0.0.tgz#868c428b4ade64a8fd8fee7364256001c1a4cbe5" + integrity sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^5.0.0" + style-search@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" @@ -10000,6 +10135,14 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +token-types@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-5.0.1.tgz#aa9d9e6b23c420a675e55413b180635b86a093b4" + integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + totalist@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" @@ -10070,6 +10213,11 @@ ts-jest@^27.0.3: semver "7.x" yargs-parser "20.x" +ts-mixer@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.2.tgz#3e4e4bb8daffb24435f6980b15204cb5b287e016" + integrity sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A== + ts-node@^10.0.0: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -10109,6 +10257,11 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -10215,6 +10368,13 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici@^5.13.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.16.0.tgz#6b64f9b890de85489ac6332bd45ca67e4f7d9943" + integrity sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ== + dependencies: + busboy "^1.6.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -10705,6 +10865,11 @@ ws@^7.3.1, ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.11.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" + integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"