Add Discord bot, reactivate team-assign

This commit is contained in:
pikiou 2023-03-18 04:08:48 +01:00
parent a906dfff07
commit 71fd770a27
22 changed files with 674 additions and 23 deletions

View File

@ -58,6 +58,7 @@ module.exports = {
__SERVER__: true, __SERVER__: true,
__DEV__: true, __DEV__: true,
__LOCAL__: false, __LOCAL__: false,
__REGISTER_DISCORD_COMMANDS__: false,
__TEST__: false, __TEST__: false,
}, },
} }

View File

@ -22,6 +22,7 @@ module.exports = {
__CLIENT__: true, __CLIENT__: true,
__SERVER__: false, __SERVER__: false,
__LOCAL__: false, __LOCAL__: false,
__REGISTER_DISCORD_COMMANDS__: false,
__TEST__: true, __TEST__: true,
}, },
maxConcurrency: 50, maxConcurrency: 50,

View File

@ -43,6 +43,7 @@
"ser": "yarn dev:build && node ./public/server", "ser": "yarn dev:build && node ./public/server",
"dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts", "dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts",
"local-start": "cross-env LOCAL=true yarn build && node ./public/server", "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", "start": "node ./public/server",
"build": "run-s build:*", "build": "run-s build:*",
"build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts", "build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts",
@ -94,6 +95,7 @@
"core-js": "^3.15.2", "core-js": "^3.15.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"detect-node": "^2.1.0", "detect-node": "^2.1.0",
"discord.js": "^14.7.1",
"express": "^4.17.1", "express": "^4.17.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"google-auth-library": "^7.10.1", "google-auth-library": "^7.10.1",

BIN
public/favicon.ico Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -10,7 +10,7 @@ import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
// import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting" // import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting"
// import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals" // import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals"
// import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo" // import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo"
// import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes" import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
// import { // import {
// AskParticipationDetails, // AskParticipationDetails,
// fetchFor as fetchForParticipationDetails, // fetchFor as fetchForParticipationDetails,
@ -27,7 +27,7 @@ const Asks = (): JSX.Element | null => {
AskDiscord(asks, 5) AskDiscord(asks, 5)
AskDayWishes(asks, 10) AskDayWishes(asks, 10)
// AskTeamWishes(asks, 11) AskTeamWishes(asks, 11)
// AskParticipationDetails(asks, 12) // AskParticipationDetails(asks, 12)
// AskPersonalInfo(asks, 15) // AskPersonalInfo(asks, 15)
// AskHosting(asks, 20) // AskHosting(asks, 20)
@ -72,7 +72,7 @@ export const fetchFor = [
...fetchForDayWishes, ...fetchForDayWishes,
// ...fetchForHosting, // ...fetchForHosting,
// ...fetchForMeals, // ...fetchForMeals,
// ...fetchForTeamWishes, ...fetchForTeamWishes,
// ...fetchForParticipationDetails, // ...fetchForParticipationDetails,
// ...fetchForPersonalInfo, // ...fetchForPersonalInfo,
] ]

View File

@ -29,7 +29,7 @@ const LoginForm = (): JSX.Element => {
return ( return (
<form> <form>
<div className={styles.loginIntro} key="login-intro"> <div className={styles.loginIntro} key="login-intro">
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à é, connecte-toi pour accéder à ton espace.
</div> </div>
<div className={styles.formLine} key="line-email"> <div className={styles.formLine} key="line-email">
<label htmlFor="email">Email</label> <label htmlFor="email">Email</label>

View File

@ -2,10 +2,10 @@ import React, { memo } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import TeamItem from "./TeamItem" import TeamItem from "./TeamItem"
import { selectSortedActiveTeams } from "../../store/teamList" import { selectSortedTeams } from "../../store/teamList"
const TeamList: React.FC = (): JSX.Element | null => { const TeamList: React.FC = (): JSX.Element | null => {
const teams = useSelector(selectSortedActiveTeams) const teams = useSelector(selectSortedTeams)
if (!teams || teams.length === 0) return null if (!teams || teams.length === 0) return null
return ( return (

View File

@ -7,8 +7,8 @@ import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal"
// import MealsFormModal from "./MealsForm/MealsFormModal" // import MealsFormModal from "./MealsForm/MealsFormModal"
// import ParticipationDetails from "./ParticipationDetails/ParticipationDetails" // import ParticipationDetails from "./ParticipationDetails/ParticipationDetails"
// import ParticipationDetailsFormModal from "./ParticipationDetailsForm/ParticipationDetailsFormModal" // import ParticipationDetailsFormModal from "./ParticipationDetailsForm/ParticipationDetailsFormModal"
// import TeamWishes from "./TeamWishes/TeamWishes" import TeamWishes from "./TeamWishes/TeamWishes"
// import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal" import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal"
// import VolunteerTeam from "./VolunteerTeam/VolunteerTeam" // import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
import withUserConnected from "../../utils/withUserConnected" import withUserConnected from "../../utils/withUserConnected"
import ContentTitle from "../ui/Content/ContentTitle" 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 fetchForHostingForm } from "./HostingForm/HostingForm"
// import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm" // import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm"
// import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm" // 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 { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm"
import PersonalInfo from "./PersonalInfo/PersonalInfo" import PersonalInfo from "./PersonalInfo/PersonalInfo"
import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal" import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal"
@ -43,10 +43,10 @@ const Board: FC = (): JSX.Element => (
<DayWishes /> <DayWishes />
<DayWishesFormModal /> <DayWishesFormModal />
{/* <ParticipationDetails /> {/* <ParticipationDetails />
<ParticipationDetailsFormModal /> <ParticipationDetailsFormModal /> */}
<TeamWishes /> <TeamWishes />
<TeamWishesFormModal /> <TeamWishesFormModal />
<VolunteerTeam /> {/* <VolunteerTeam />
<Hosting /> <Hosting />
<HostingFormModal /> <HostingFormModal />
<Meals /> <Meals />
@ -66,5 +66,5 @@ export const fetchFor = [
// ...fetchForHostingForm, // ...fetchForHostingForm,
// ...fetchForMealsForm, // ...fetchForMealsForm,
// ...fetchForParticipationDetailsForm, // ...fetchForParticipationDetailsForm,
// ...fetchForTeamWishesForm, ...fetchForTeamWishesForm,
] ]

View File

@ -38,6 +38,12 @@ const DayWishes: FC = (): JSX.Element | null => {
Je <b>ne sais pas encore</b> si je participerai à PeL 2023 Je <b>ne sais pas encore</b> si je participerai à PeL 2023
</div> </div>
)} )}
{participation === "à distance" && (
<div className={styles.participationLabel}>
Je <b className={styles.yesParticipation}>participerai</b> à PeL 2023 ! Sans y
être pendant le weekend.
</div>
)}
{participation === "inconnu" && ( {participation === "inconnu" && (
<div className={styles.lineEmpty}> <div className={styles.lineEmpty}>
Participation à PeL 2023{" "} Participation à PeL 2023{" "}

View File

@ -16,4 +16,5 @@ export default {
], ],
}, },
DEV_JWT_SECRET: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!", DEV_JWT_SECRET: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!",
DEV_DISCORD_TOKEN: "fakeqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!",
} }

View File

@ -11,7 +11,7 @@ import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../page
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register" import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge" import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge"
// import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans" // 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 AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards"
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
@ -51,11 +51,11 @@ export default [
// component: AsyncLoans, // component: AsyncLoans,
// loadData: loadLoansData, // loadData: loadLoansData,
// }, // },
// { {
// path: "/emprunter", path: "/emprunter",
// component: AsyncLoaning, component: AsyncLoaning,
// loadData: loadLoaningData, loadData: loadLoaningData,
// }, },
{ {
path: "/fiches", path: "/fiches",
component: AsyncKnowledgeCards, component: AsyncKnowledgeCards,

405
src/server/discordBot.ts Normal file
View File

@ -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<void>
}
const commands: Collection<string, Command> = 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<boolean> {
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<void> {
// 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<void> {
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<DiscordRoleWithoutId, DiscordRole>(
"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<VolunteerWithoutId, Volunteer>(
"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<DiscordRoleWithoutId, DiscordRole>(
"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<boolean> {
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<void> {
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
}
}
}

View File

@ -11,8 +11,8 @@ export { SheetNames } from "./localDb"
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json") const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
const REMOTE_UPDATE_DELAY = 40000 const REMOTE_UPDATE_DELAY = 120000
const DELAY_BETWEEN_ATTEMPTS = 10000 const DELAY_BETWEEN_ATTEMPTS = 30000
const DELAY_BETWEEN_FIRST_LOAD = 1500 const DELAY_BETWEEN_FIRST_LOAD = 1500
let creds: string | undefined | null let creds: string | undefined | null
@ -59,7 +59,7 @@ export async function getSheet<
sheet = new Sheet<ElementNoId, Element>(sheetName, specimen, translation) sheet = new Sheet<ElementNoId, Element>(sheetName, specimen, translation)
await sheet.waitForFirstLoad() await sheet.waitForFirstLoad()
sheetList[sheetName] = sheet sheetList[sheetName] = sheet
setInterval(() => sheet.dbUpdate(), REMOTE_UPDATE_DELAY) setInterval(() => sheet.dbUpdate(), REMOTE_UPDATE_DELAY * (1 + Math.random() / 10))
} else { } else {
sheet = sheetList[sheetName] as Sheet<ElementNoId, Element> sheet = sheetList[sheetName] as Sheet<ElementNoId, Element>
} }
@ -265,6 +265,7 @@ export class Sheet<
} }
await tryNTimesVoidReturn(async () => { await tryNTimesVoidReturn(async () => {
console.log(`dbSaveAsync on ${this.name} at ${new Date()}`)
// Load sheet into an array of objects // Load sheet into an array of objects
const rows = await sheet.getRows() const rows = await sheet.getRows()
if (!rows[0]) { if (!rows[0]) {
@ -317,6 +318,7 @@ export class Sheet<
await rows[rowToDelete].delete() await rows[rowToDelete].delete()
} }
} }
console.log(`dbSaveAsync successful on ${this.name} at ${new Date()}`)
}) })
} }
@ -330,6 +332,7 @@ export class Sheet<
await tryNTimesVoidReturn(async () => { await tryNTimesVoidReturn(async () => {
// Load sheet into an array of objects // Load sheet into an array of objects
console.log(`dbLoadAsync on ${this.name} at ${new Date()}`)
const rows = (await sheet.getRows()) as StringifiedElement[] const rows = (await sheet.getRows()) as StringifiedElement[]
const elements: Element[] = [] const elements: Element[] = []
if (!rows[0]) { if (!rows[0]) {
@ -357,6 +360,7 @@ export class Sheet<
}) })
this._state = elements this._state = elements
console.log(`dbLoadAsync successful on ${this.name} at ${new Date()}`)
}) })
} }

View File

@ -0,0 +1,14 @@
import ExpressAccessors from "./expressAccessors"
import {
DiscordRole,
DiscordRoleWithoutId,
translationDiscordRoles,
} from "../../services/discordRoles"
const expressAccessor = new ExpressAccessors<DiscordRoleWithoutId, DiscordRole>(
"DiscordRoles",
new DiscordRole(),
translationDiscordRoles
)
export const discordRolesListGet = expressAccessor.listGet()

View File

@ -16,6 +16,8 @@ export class SheetNames {
Boxes = "Boîtes" Boxes = "Boîtes"
DiscordRoles = "Rôles Discord"
Games = "Jeux" Games = "Jeux"
Miscs = "Divers" Miscs = "Divers"

View File

@ -50,6 +50,7 @@ import {
import { wishListGet, wishAdd } from "./gsheets/wishes" import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config" import config from "../config"
import { notificationsSubscribe, notificationMain } from "./notifications" import { notificationsSubscribe, notificationMain } from "./notifications"
import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot"
import checkAccess from "./checkAccess" import checkAccess from "./checkAccess"
import { hasGSheetsAccess } from "./gsheets/accessors" import { hasGSheetsAccess } from "./gsheets/accessors"
import { addStatus, showStatusAt } from "./status" import { addStatus, showStatusAt } from "./status"
@ -64,6 +65,9 @@ checkAccess()
notificationMain() notificationMain()
// discordRegisterCommands()
discordBot()
const app = express() const app = express()
// Allow receiving big images // Allow receiving big images
@ -243,6 +247,14 @@ if (hasPushNotifAccess) {
addStatus("Push notif:", chalk.blue(`🚧 offline, simulated`)) 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) => { hasSecret().then((has: boolean) => {
if (has) { if (has) {
addStatus("JWT secret:", chalk.green(`✅ prod private one from file`)) addStatus("JWT secret:", chalk.green(`✅ prod private one from file`))

View File

@ -108,7 +108,9 @@ async function notifyAboutAnnouncement(): Promise<void> {
} }
const audience = volunteerList.filter( 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( console.error(

View File

@ -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<DiscordRole, "id">
export interface DiscordRole {
id: DiscordRole["id"]
messageId: DiscordRole["messageId"]
emoji: DiscordRole["emoji"]
role: DiscordRole["role"]
}

View File

@ -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) => export const selectSortedActiveTeams = createSelector(selectTeamList, (teams) =>
[...teams.filter((team) => get(team, "status") === "active")].sort( [...teams.filter((team) => get(team, "status") === "active")].sort(
(a, b) => get(a, "order", 0) - get(b, "order", 0) (a, b) => get(a, "order", 0) - get(b, "order", 0)

View File

@ -2,6 +2,7 @@ declare const __CLIENT__: boolean
declare const __SERVER__: boolean declare const __SERVER__: boolean
declare const __DEV__: boolean declare const __DEV__: boolean
declare const __LOCAL__: boolean declare const __LOCAL__: boolean
declare const __REGISTER_DISCORD_COMMANDS__: boolean
declare const __TEST__: boolean declare const __TEST__: boolean
declare module "*.svg" declare module "*.svg"
@ -19,6 +20,7 @@ declare namespace NodeJS {
__SERVER__: boolean __SERVER__: boolean
__DEV__: boolean __DEV__: boolean
__LOCAL__: boolean __LOCAL__: boolean
__REGISTER_DISCORD_COMMANDS__: boolean
__TEST__: boolean __TEST__: boolean
$RefreshReg$: () => void $RefreshReg$: () => void
$RefreshSig$$: () => void $RefreshSig$$: () => void

View File

@ -9,6 +9,7 @@ import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"
export const isDev = process.env.NODE_ENV === "development" export const isDev = process.env.NODE_ENV === "development"
const isLocal = process.env.LOCAL === "true" const isLocal = process.env.LOCAL === "true"
const isRegisterDiscordCommands = process.env.REGISTER_DISCORD_COMMANDS === "true"
const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => { const getStyleLoaders = (isWeb: boolean, isSass?: boolean) => {
let loaders: RuleSetUseItem[] = [ let loaders: RuleSetUseItem[] = [
{ {
@ -49,6 +50,7 @@ const getPlugins = (isWeb: boolean) => {
__SERVER__: !isWeb, __SERVER__: !isWeb,
__DEV__: isDev, __DEV__: isDev,
__LOCAL__: isLocal, __LOCAL__: isLocal,
__REGISTER_DISCORD_COMMANDS__: isRegisterDiscordCommands,
}), }),
] ]

167
yarn.lock
View File

@ -1037,6 +1037,42 @@
dependencies: dependencies:
"@jridgewell/trace-mapping" "0.3.9" "@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": "@discoveryjs/json-ext@^0.5.0":
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
@ -1442,6 +1478,24 @@
redux-thunk "^2.4.1" redux-thunk "^2.4.1"
reselect "^4.1.5" 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": "@sendgrid/client@^7.7.0":
version "7.7.0" version "7.7.0"
resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.7.0.tgz#f8f67abd604205a0d0b1af091b61517ef465fdbf" resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.7.0.tgz#f8f67abd604205a0d0b1af091b61517ef465fdbf"
@ -1556,6 +1610,11 @@
"@testing-library/dom" "^8.0.0" "@testing-library/dom" "^8.0.0"
"@types/react-dom" "<18.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": "@tootallnate/once@1":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@ -2164,6 +2223,13 @@
anymatch "^3.0.0" anymatch "^3.0.0"
source-map "^0.6.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": "@types/xml2js@^0.4.11":
version "0.4.11" version "0.4.11"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.11.tgz#bf46a84ecc12c41159a7bd9cf51ae84129af0e79" 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" base64-js "^1.3.1"
ieee754 "^1.1.13" 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: bytes@3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@ -4006,6 +4079,29 @@ dir-glob@^3.0.1:
dependencies: dependencies:
path-type "^4.0.0" 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: doctrine@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" 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" resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9"
integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== 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: file-type@^3.8.0:
version "3.9.0" version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" 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: dependencies:
harmony-reflect "^1.4.6" harmony-reflect "^1.4.6"
ieee754@^1.1.13: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 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" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== 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: lodash.truncate@^4.4.2:
version "4.4.2" version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" 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" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 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: pend@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 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" string_decoder "^1.1.1"
util-deprecate "^1.0.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: readdirp@~3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 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" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 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: strict-uri-encode@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" 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" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== 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: style-search@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" 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" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 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: totalist@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
@ -10070,6 +10213,11 @@ ts-jest@^27.0.3:
semver "7.x" semver "7.x"
yargs-parser "20.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: ts-node@^10.0.0:
version "10.9.1" version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" 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" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== 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: tsutils@^3.21.0:
version "3.21.0" version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 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" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 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: unicode-canonical-property-names-ecmascript@^2.0.0:
version "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" 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" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== 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: xml-name-validator@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"