Merge pull request #31 from TeamNewGuys/user-can-view-profile

Added Profile view
This commit is contained in:
Noah
2021-09-26 10:35:51 +10:00
committed by GitHub
14 changed files with 347 additions and 57 deletions
+4 -1
View File
@@ -18,7 +18,10 @@
"rules": {
"indent": [
"error",
"tab"
"tab",
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
+1
View File
@@ -3,3 +3,4 @@ node_modules
.vscode
yarn-error.log
.env
conf.json5
+3 -1
View File
@@ -12,6 +12,7 @@
"@types/express-serve-static-core": "^4.17.24",
"@types/jest": "^27.0.1",
"@types/json5": "^2.2.0",
"@types/lodash": "^4.14.172",
"@types/node": "^16.7.10",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1",
@@ -24,12 +25,13 @@
"dependencies": {
"@discordjs/builders": "^0.5.0",
"@discordjs/rest": "^0.1.0-canary.0",
"axios": "^0.21.4",
"deezer-api-ts": "^1.2.1",
"discord.js": "^13.1.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"json5": "^2.2.0",
"node-fetch": "^3.0.0",
"lodash": "^4.17.21",
"prudence": "^0.9.6",
"songlink-api": "^1.0.0",
"tachi-common": "^0.2.1",
+4 -2
View File
@@ -1,5 +1,4 @@
/** @TODO Potentially re-work this to only run on PROD env, handy for DEV env! **/
/** @deprecated replace with database call & cache */
export const allowedChannels = ["876321394005254214"];
export enum LoggerLayers {
@@ -8,5 +7,8 @@ export enum LoggerLayers {
server = "server",
serverAuth = "serverAuth",
botConfigSetup = "botConfigSetup",
tachiFetch = "tachiFetch"
tachiFetch = "tachiFetch",
profile = "profile",
selectInteractionHandler = "selectInteractionHandler",
buildProfileEmbed = "buildProfileEmbed"
}
+11 -11
View File
@@ -1,10 +1,11 @@
import { Client, Intents } from "discord.js";
import { handleIsCommand } from "./interactionHandlers/command/handleIsCommand";
import { handleIsSelectMenu } from "./interactionHandlers/selectMenu/handleIsSelectMenu";
import { ProcessEnv } from "./setup";
import { LoggerLayers } from "./config";
import { registerSlashCommands, slashCommands, SlashCommand } from "./slashCommands/register";
import { registerSlashCommands } from "./slashCommands/register";
import { createLayeredLogger } from "./utils/logger";
const logger = createLayeredLogger(LoggerLayers.client);
const client = new Client({
@@ -12,16 +13,13 @@ const client = new Client({
});
client.on("interactionCreate", async (interaction) => {
if (!interaction.isCommand()) return;
try {
const command = slashCommands.find((command: SlashCommand) => {
return command.info.name === interaction.commandName;
});
if (interaction.isSelectMenu()) {
return await handleIsSelectMenu(interaction);
}
if (command && command.exec) {
logger.info(`Running ${command.info.name} interaction`);
command.exec(interaction);
if (interaction.isCommand()) {
return await handleIsCommand(interaction);
}
} catch (e) {
logger.error("Failed to run interaction");
@@ -38,7 +36,9 @@ client.on("interactionCreate", async (interaction) => {
await client.login(ProcessEnv.DISCORD_TOKEN);
logger.info(`Logged in successfully to ${client.guilds.cache.size} guilds`);
logger.info(
`Invite URL: https://discord.com/api/oauth2/authorize?client_id=${client.application!.id}&permissions=8&scope=applications.commands%20bot`
`Invite URL: https://discord.com/api/oauth2/authorize?client_id=${
client.application!.id
}&permissions=8&scope=applications.commands%20bot`
);
await registerSlashCommands(client);
@@ -0,0 +1,24 @@
import { Interaction } from "discord.js";
import { LoggerLayers } from "../../config";
import { SlashCommand, slashCommands } from "../../slashCommands/register";
import { createLayeredLogger } from "../../utils/logger";
const logger = createLayeredLogger(LoggerLayers.selectInteractionHandler);
export const handleIsCommand = async (interaction: Interaction): Promise<void> => {
try {
/** Rechecking required to enforce types */
if (interaction.isCommand()) {
const command = slashCommands.find((command: SlashCommand) => {
return command.info.name === interaction.commandName;
});
if (command && command.exec) {
logger.info(`Running ${command.info.name} interaction`);
command.exec(interaction);
}
}
} catch (e) {
logger.error("Failed to handle isCommand interaction");
}
};
@@ -0,0 +1,23 @@
import { Interaction } from "discord.js";
import { LoggerLayers } from "../../config";
import { buildProfileIntractable } from "../../profile/buildProfileEmbed";
import { createLayeredLogger } from "../../utils/logger";
import { stringToSimpleGameType } from "../../utils/utils";
const logger = createLayeredLogger(LoggerLayers.selectInteractionHandler);
export const handleIsSelectMenu = async (interaction: Interaction): Promise<void> => {
try {
/** Rechecking required to enforce types */
if (interaction.isSelectMenu()) {
/** @TODO Split this out when we add more dropdowns */
const userId = interaction.customId.split(":")[1];
await interaction.update(
await buildProfileIntractable(userId, stringToSimpleGameType(interaction.values[0]))
);
}
return;
} catch (e) {
logger.error("Failed to handle isSelectMenu interaction");
}
};
+120
View File
@@ -0,0 +1,120 @@
import { InteractionReplyOptions, MessageActionRow, MessageEmbed, MessagePayload, MessageSelectMenu } from "discord.js";
import { Game, UserGameStats, IDStrings, PublicUserDocument, UGSRatingsLookup } from "tachi-common";
import { find } from "lodash";
import { LoggerLayers } from "../config";
import { TachiServerV1Get } from "../utils/fetch-tachi";
import { createLayeredLogger } from "../utils/logger";
import {
formatGameScoreRating,
formatGameWrapper,
getPfpUrl,
prettyRatingString,
SimpleGameType,
simpleGameTypeToString
} from "../utils/utils";
const logger = createLayeredLogger(LoggerLayers.buildProfileEmbed);
const pullRatings = <I extends IDStrings = IDStrings>(
gpt: SimpleGameType<Game>,
ratings: Partial<Record<UGSRatingsLookup[I], number>>
): string[] => {
const allRatings: string[] = [];
const RatingKeys = <UGSRatingsLookup[I][]>Object.keys(ratings);
RatingKeys.forEach((rating) => {
const ratingValue: string = formatGameScoreRating(gpt, rating, ratings[rating] || 0);
allRatings.push(`${prettyRatingString(rating)}: ${ratingValue}`);
});
return allRatings;
};
export const fetchUserDetails = async (userId: number): Promise<PublicUserDocument> => {
try {
logger.info(`Fetching public user document for ${userId}`);
const data = (await TachiServerV1Get<PublicUserDocument>(`/users/${userId}`)).body;
if (data) {
return data;
} else {
throw new Error(`Could not find public user document for ${userId}`);
}
} catch (e) {
logger.error("Unable to fetch user details");
throw new Error("Unable to fetch user details");
}
};
export const buildProfileEmbed = async (data: UserGameStats[], game?: SimpleGameType<Game>): Promise<MessageEmbed> => {
try {
logger.info(`Building profile embed${game ? `for ${formatGameWrapper(game)}` : ""}`);
const embed = new MessageEmbed().setColor("#cc527a");
const userId = data[0].userID;
const userDetails = await fetchUserDetails(userId);
const pfp = userDetails.customPfp
? getPfpUrl(userId)
: "https://cdn.mos.cms.futurecdn.net/mrArzwHcNuQbRwbEmuiwdJ.jpg";
embed.setTitle(`${userDetails.username}'s Profile`);
embed.setThumbnail(pfp);
embed.setAuthor(`@${userDetails.username}`, pfp);
logger.info(`Embed is for ${userDetails.username}`);
if (game) {
const specificData: UserGameStats | undefined = find(
data,
(item) => simpleGameTypeToString(game) === `${item.game}:${item.playtype}`
);
if (specificData) {
embed.addField(
`Stats for: ${formatGameWrapper(game)}`,
`${pullRatings(game, specificData.ratings).join("\n")}`
);
} else {
embed.addField("Select a game to see stats", `No stats for ${formatGameWrapper(game)}`);
}
} else {
embed.addField("Select a game to see stats", "\u200B");
}
return embed;
} catch (e) {
logger.error(e);
throw new Error("Unable to build profile embed");
}
};
export const buildProfileIntractable = async (
userId: string,
game?: SimpleGameType<Game>
): Promise<InteractionReplyOptions | MessagePayload> => {
try {
const data = (await TachiServerV1Get<UserGameStats[]>(`/users/${userId}/game-stats`))?.body;
logger.verbose(data);
/** @TODO Verify we got valid data here! */
if (data) {
const dropdown = new MessageActionRow().addComponents(
new MessageSelectMenu()
.setCustomId(`SelectGameForProfile:${userId}`)
.setPlaceholder("Browse By Game")
.addOptions(
data.map((_game) => {
return {
label: `${formatGameWrapper(_game)}`,
value: `${_game.game}:${_game.playtype}`
};
})
)
);
return { embeds: [await buildProfileEmbed(data, game)], components: [dropdown] };
}
throw new Error(`No data found for user ${userId}`);
} catch (e) {
logger.error("Unable to build profile interactable");
throw new Error("Unable to build profile interactable");
}
};
+29
View File
@@ -0,0 +1,29 @@
import { CommandInteraction } from "discord.js";
import { IDStrings } from "tachi-common";
import { LoggerLayers } from "../config";
import { BotConfig } from "../setup";
import { createLayeredLogger } from "../utils/logger";
import { stringToSimpleGameType } from "../utils/utils";
import { buildProfileIntractable } from "./buildProfileEmbed";
const logger = createLayeredLogger(LoggerLayers.profile);
export const getProfileByName = async (interaction: CommandInteraction): Promise<void> => {
try {
/** @TODO this can be an @User, userId or userName!!! Handle this at some point */
const userId = interaction.options.getString("user", false) || "TODO FALLBACK TO SELF";
logger.info(`Fetching user ${userId}`);
const optionalGameOption = <IDStrings | undefined>interaction.options.getString("game", false);
const game = optionalGameOption ? stringToSimpleGameType(optionalGameOption) : undefined;
if (userId) {
await interaction.reply(await buildProfileIntractable(userId, game));
} else {
await interaction.reply("No user data found for user");
}
} catch (e) {
logger.error(e);
await interaction.reply(BotConfig.GENERIC_ERROR_MESSAGE);
}
};
+13 -7
View File
@@ -18,6 +18,7 @@ export interface BotConfig {
SERVER_PORT: number;
TACHI_SERVER_LOCATION: string;
OUR_URL: string;
GENERIC_ERROR_MESSAGE: string;
}
function ParseBotConfig(fileLoc = "conf.json5"): BotConfig {
@@ -36,6 +37,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig {
SERVER_PORT: Prudence.isPositiveNonZeroInteger,
TACHI_SERVER_LOCATION: "string",
OUR_URL: "string",
GENERIC_ERROR_MESSAGE: "string"
});
if (err) {
@@ -55,12 +57,17 @@ export interface ProcessEnvironment {
}
function ParseEnvVars() {
const err = Prudence(process.env, {
ENV: "string",
DISCORD_TOKEN: "string",
BOT_CLIENT_SECRET: "string",
BOT_CLIENT_ID: "string"
}, {}, { allowExcessKeys: true });
const err = Prudence(
process.env,
{
ENV: "string",
DISCORD_TOKEN: "string",
BOT_CLIENT_SECRET: "string",
BOT_CLIENT_ID: "string"
},
{},
{ allowExcessKeys: true }
);
if (err) {
logger.error(FormatPrError(err, "Invalid environment. Cannot safely boot."));
@@ -73,5 +80,4 @@ function ParseEnvVars() {
export const BotConfig: BotConfig = ParseBotConfig(process.env.CONF_JSON5_LOCATION);
export const ProcessEnv = ParseEnvVars();
+14
View File
@@ -3,10 +3,12 @@ import { REST } from "@discordjs/rest";
import { APIApplicationCommandOption } from "discord-api-types";
import { Routes } from "discord-api-types/v9";
import { Client, CommandInteraction } from "discord.js";
import { getProfileByName } from "../profile/fetch";
import { ProcessEnv } from "../setup";
import { LoggerLayers } from "../config";
import { help } from "../commands/help/help";
import { createLayeredLogger } from "../utils/logger";
import { gamesToChoicesObject } from "../utils/utils";
const logger = createLayeredLogger(LoggerLayers.slashCommands);
@@ -23,6 +25,18 @@ export const slashCommands: SlashCommand[] = [
{
info: new SlashCommandBuilder().setName("help").setDescription("Shows information about this bot").toJSON(),
exec: async (interaction: CommandInteraction) => await help(interaction)
},
{
info: new SlashCommandBuilder()
.setName("profile")
.setDescription("Displays a Kamaitachi Profile")
/** @TODO Make this optional once we have a fallback */
.addStringOption((option) => option.setName("user").setDescription("The users id").setRequired(true))
.addStringOption((option) =>
option.setName("game").setDescription("The Game").setRequired(false).addChoices(gamesToChoicesObject())
)
.toJSON(),
exec: async (interaction: CommandInteraction) => await getProfileByName(interaction)
}
];
+19 -9
View File
@@ -1,3 +1,4 @@
import axios from "axios";
import { LoggerLayers } from "../config";
import { BotConfig } from "../setup";
import { integer, SuccessfulAPIResponse, UnsuccessfulAPIResponse } from "tachi-common";
@@ -5,14 +6,16 @@ import { createLayeredLogger } from "./logger";
const logger = createLayeredLogger(LoggerLayers.tachiFetch);
export type APIResponse<T> = (SuccessfulAPIResponse<T> | UnsuccessfulAPIResponse) & { statusCode: integer };
export type APIResponse<T> = (SuccessfulAPIResponse<T> | (UnsuccessfulAPIResponse & { body: null })) & {
statusCode: integer;
};
export enum RequestTypes {
GET = "GET",
POST = "POST",
PATCH = "PATCH",
PUT = "PUT",
DELETE = "DELETE",
DELETE = "DELETE"
// HEAD, OPTIONS not used by tachi-server anywhere.
}
@@ -24,22 +27,26 @@ export enum RequestTypes {
* @param body - Optionally, provide some content for the request body.
* @param T - A generic that asserts the type of the response contents. Defaults to unknown.
*/
export async function TachiServerV1Request<T>(method: Exclude<RequestTypes, RequestTypes.GET>, url: string, body: unknown = {}): Promise<APIResponse<T>> {
export async function TachiServerV1Request<T>(
method: Exclude<RequestTypes, RequestTypes.GET>,
url: string,
body: unknown = {}
): Promise<APIResponse<T>> {
const realUrl = PrependTachiUrl(url, "1");
const loggerUrl = `${method} ${realUrl}`;
logger.debug(`Making a request to ${loggerUrl}.`);
try {
const res = await fetch(realUrl, {
const res = await axios(realUrl, {
method,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
data: JSON.stringify(body)
});
const json = await res.json();
const json = (await res.data) as APIResponse<T>;
const contents = { ...json, statusCode: res.status };
LogRequestResult(loggerUrl, contents);
@@ -60,14 +67,17 @@ export async function TachiServerV1Request<T>(method: Exclude<RequestTypes, Requ
* @param params - Any URL params for this request.
* @param T - A generic that asserts the type of the response contents. Defaults to unknown.
*/
export async function TachiServerV1Get<T = unknown>(url: string, params: Record<string, string> = {}): Promise<APIResponse<T>> {
export async function TachiServerV1Get<T = unknown>(
url: string,
params: Record<string, string> = {}
): Promise<APIResponse<T>> {
try {
const urlParams = new URLSearchParams(params);
const realUrl = `${PrependTachiUrl(url, "1")}?${urlParams.toString()}`;
const res = await fetch(realUrl);
const json = await res.json();
const res = await axios(realUrl, { method: RequestTypes.GET });
const json = (await res.data) as APIResponse<T>;
const contents = { ...json, statusCode: res.status };
LogRequestResult(`GET ${realUrl}`, contents);
+64
View File
@@ -0,0 +1,64 @@
import { FormatGame, Game, Playtypes, IDStrings, UGSRatingsLookup, GetGamePTConfig } from "tachi-common";
import { ProcessEnv } from "../setup";
import { PrependTachiUrl } from "./fetch-tachi";
export interface SimpleGameType<T extends Game> {
game: T;
playtype: Playtypes[T];
}
export const simpleGameTypeToString = <T extends Game>(gpt: SimpleGameType<T>): IDStrings =>
<IDStrings>`${gpt.game}:${gpt.playtype}`;
export const stringToSimpleGameType = (gptString: string): SimpleGameType<Game> => {
return { game: <Game>gptString.split(":")[0], playtype: <Playtypes[never]>gptString.split(":")[1] };
};
export const formatGameWrapper = (gpt: SimpleGameType<Game>): string => FormatGame(gpt.game, gpt.playtype);
export const capitalise = (s: string): string => (s && s[0].toUpperCase() + s.slice(1)) || "";
export const prettyRatingString = <I extends IDStrings = IDStrings>(rating: UGSRatingsLookup[I]): string => {
return capitalise(rating);
};
export const getPfpUrl = (userId: number): string => {
if (ProcessEnv.ENV !== "prod") {
return `https://kamaitachi.xyz/static/images/users/${userId}-pfp.png`;
}
return PrependTachiUrl(`/users/${userId}/pfp`);
};
export const gameIdentifierStrings = [
"iidx:SP",
"iidx:DP",
"sdvx:Single",
"usc:Single",
"ddr:SP",
"ddr:DP",
"maimai:Single",
"museca:Single",
"bms:7K",
"bms:14K",
"chunithm:Single",
"gitadora:Gita",
"gitadora:Dora"
];
export const gamesToChoicesObject = (): [name: string, value: string][] => {
return gameIdentifierStrings.map((identifier) => {
return [formatGameWrapper(stringToSimpleGameType(identifier)), identifier];
});
};
export const formatGameScoreRating = <I extends IDStrings = IDStrings>(
gpt: SimpleGameType<Game>,
ratingName: UGSRatingsLookup[I],
value: number
): string => {
const gptConfig = GetGamePTConfig(gpt.game, gpt.playtype);
const val = gptConfig.profileRatingAlgFormatters[ratingName];
if (val) {
return val(value);
}
return value.toFixed(2);
};
+18 -26
View File
@@ -862,6 +862,11 @@
dependencies:
"@types/node" "*"
"@types/lodash@^4.14.172":
version "4.14.172"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@@ -1282,6 +1287,13 @@ axios@^0.19.0:
dependencies:
follow-redirects "1.5.10"
axios@^0.21.4:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
babel-jest@^27.1.0:
version "27.1.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.1.0.tgz#e96ca04554fd32274439869e2b6d24de9d91bc4e"
@@ -1846,11 +1858,6 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
data-uri-to-buffer@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
data-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@@ -2397,13 +2404,6 @@ fecha@^4.2.0:
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz"
integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==
fetch-blob@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.2.tgz#6bc438675f3851ecea51758ac91f6a1cd1bacabd"
integrity sha512-hunJbvy/6OLjCD0uuhLdp0mMPzP/yd2ssd1t2FCJsaA7wkWhpbp9xfuNVpv7Ll4jFhzp6T4LAupSiV9uOeg0VQ==
dependencies:
web-streams-polyfill "^3.0.3"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
@@ -2478,6 +2478,11 @@ follow-redirects@1.5.10:
dependencies:
debug "=3.1.0"
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
foreground-child@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
@@ -3692,7 +3697,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@^4.7.0:
lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3953,14 +3958,6 @@ node-fetch@^2.6.1:
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0.tgz#79da7146a520036f2c5f644e4a26095f17e411ea"
integrity sha512-bKMI+C7/T/SPU1lKnbQbwxptpCrG9ashG+VkytmXCPZyuM9jB6VU+hY0oi4lC8LxTtAeWdckNCTa3nrGsAdA3Q==
dependencies:
data-uri-to-buffer "^3.0.1"
fetch-blob "^3.1.2"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -5365,11 +5362,6 @@ walker@^1.0.7:
dependencies:
makeerror "1.0.x"
web-streams-polyfill@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.1.0.tgz#86f983b4f44745502b0d8563d9ef3afc609d4465"
integrity sha512-wO9r1YnYe7kFBLHyyVEhV1H8VRWoNiNnuP+v/HUUmSTaRF8F93Kmd3JMrETx0f11GXxRek6OcL2QtjFIdc5WYw==
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"