commit e22c9bc87ed1ca63a8a7543157bb228777e7542d Author: jasonfoknxu Date: Sat Apr 1 19:51:57 2023 +0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47a4f6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# config +.env + +# Compiled script +/run + +# logs +/logs + +# dependencies +/node_modules +/.pnp +.pnp.js + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/.readme/discord.png b/.readme/discord.png new file mode 100644 index 0000000..e4875f8 Binary files /dev/null and b/.readme/discord.png differ diff --git a/.readme/discord_400.png b/.readme/discord_400.png new file mode 100644 index 0000000..7751ded Binary files /dev/null and b/.readme/discord_400.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1507737 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 nxu@nxu.biz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7767ee8 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# FreeCityHK Minecraft Discord Bot + +專門為 FreeCityHK Minecraft 制作的 Discord Bot + + +``` +✨ FreeCityHK 係一個以 RP [RPG] 的 生存 Minecraft 伺服器 ✨ + +💫 仲記唔記得你曾經喺 Minecraft 起過嘅偉大建築,面對宏大嘅城堡有冇諗過真真正正成為一方霸主!?💫 + +🪙 面對霸權,你有冇一種革命情意結 ?! 或者,相比建築,更加響往冷兵器、傭兵、國戰、荒島生活!? 🪙 + +🏵️ 玩慣咗 GTA RP 又有冇諗過換個方式,以 Minecraft 嚟過一個另類人生RP !? 🏵️ + +🌅 FreeCityHK 可能就係你可以嘗試嘅選擇,你可以建立你嘅國家、城邦。🌅 + +🍻 你可以喺依度招兵買馬造就你嘅軍團,廣交結社成立你嘅暗部。 🍻 + +☀️ 每個人都有你嘅過往,而唔知喺依個世界你嘅選擇,又會造就點嘅全新篇章呢!?☀️ + +🥠 你嘅 RP 故事正在由你編寫中,我地期待著... 🥠 +``` + +[FreeCityHK Minecraft 3.0介紹影片](https://www.youtube.com/watch?v=gmT4nW-8rFk) + +## 功能 & 指令 +- 頻道名顯示在線人數,每10分鐘更新一次 +- 查看玩家ID和skin `/player {玩家名}` +- 查看在線玩家 `/online` +- 查看伺服器狀態 `/server` +- 加白名單 `/whitelist {玩家名} ` +- 遊戲畫面公告 `/noti {公告内容}` +- 聊天訊息 `/say {訊息内容}` + +## 設定 (.env) +- `TOKEN` —— Discord Bot 的 Token +- `CHANNEL_ID` —— 可以使用 Bot 指令的頻道ID(如多個頻道可用`,`分隔) +- `UPDATE_CHANNEL` —— 顯示在線人數的頻道ID(如多個頻道可用`,`分隔) +- `SERVER` —— Minecraft Server 地址 +- `QUERY_PORT` —— Minecraft Server Query Port +- `RCON_PORT` —— Minecraft Server Rcon Port +- `RCON_PW` —— Minecraft Server Rcon 密碼 +- `PLAYER_API` —— 以玩家名取得玩家ID的API,使用`{{NAME}}`取代玩家名 +- `PROFILE_API` —— 以玩家ID取得玩家資料的API,使用`{{PLAYER_ID}}`取代玩家ID + +## 使用方法 + +> 有關建立和設定 Discord Bot 的方法,請自行了解。 + +1. `yarn install` 或 `npm install` 安裝所需的 dependencies +2. 把 `sample.env` 重新命名為 `.env` +3. 填寫設定 +4. `yarn run build` 或 `npm run build` 進行編譯 +5. `yarn start` 或 `npm start` 使用 PM2 運行 +6. 如果沒有/不想用PM2,可以 `yarn run test` 或 `npm run test` 使用 Node.js 直接運行 + + +## 歡迎加入 FreeCityHK Minecraft Discord +[![FreeCityHK Minecraft Discord](.readme/discord_400.png 'Discord')](https://discord.freecityhk.city) + + +## Credits +- [axios](https://github.com/axios/axios) +- [Crafatar](https://github.com/crafatar/crafatar/) (Minecraft Skin render 方法) +- [node-canvas](https://github.com/Automattic/node-canvas) +- [discord.js](https://github.com/discordjs/discord.js) +- [dotenv](https://github.com/motdotla/dotenv) +- [minecraft-server-util](https://github.com/PassTheMayo/minecraft-server-util) +- [node-schedule](https://github.com/node-schedule/node-schedule) diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..c542b6f --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,15 @@ +module.exports = { + apps : [{ + name : "freecityhk-mc-dc-bot", + script: 'run/app.js', + watch: false, + watch_delay: 1000, + ignore_watch : ["node_modules"], + //exec_mode: "cluster", + //instances: 2, + max_memory_restart: "2G", + log_date_format: "YYYY-MM-DD HH:mm:ss", + error_file: "./logs/error.log", + out_file: "./logs/out.log" + }], +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..0af2dd7 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "freecityhk-minecraft-discord-bot", + "version": "1.0.0", + "main": "run/app.js", + "license": "MIT", + "author": "jasonfoknxu (https://nxuweb.net)", + "repository": "github:jasonfoknxu/freecityhk-minecraft-discord-bot", + "scripts": { + "start": "pm2 start ecosystem.config.js --no-daemon", + "build": "tsc -p tsconfig.json", + "test": "node run/app.js" + }, + "dependencies": { + "axios": "^1.3.4", + "canvas": "^2.11.0", + "discord.js": "^14.8.0", + "dotenv": "^16.0.3", + "minecraft-server-util": "^5.4.0", + "node-schedule": "^2.1.1" + }, + "devDependencies": { + "@types/node-schedule": "^2.1.0", + "typescript": "^5.0.2" + } +} diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..8179ebb --- /dev/null +++ b/sample.env @@ -0,0 +1,12 @@ +# Discord +TOKEN= +CHANNEL_ID= +UPDATE_CHANNEL= + +# Minecraft +SERVER= +QUERY_PORT= +RCON_PORT= +RCON_PW= +PLAYER_API=https://api.mojang.com/users/profiles/minecraft/{{NAME}} +PROFILE_API=https://sessionserver.mojang.com/session/minecraft/profile/{{PLAYER_ID}} \ No newline at end of file diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..b766163 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,144 @@ +import { Client, GatewayIntentBits, VoiceChannel } from 'discord.js'; +import { config } from './config'; +import { getServerInfo, getOnlinePlayers, onlineCommand, serverCommand } from './commands/ServerInfo'; +import { playerCommand, getPlayer } from './commands/playerInfo'; +import { add2Whitelist, notiPlayer, notiPlayerCommand, say, sayCommand, whitelistCommand } from './commands/minecraft'; +import { render } from './render'; +import * as schedule from "node-schedule"; + +const client = new Client({ intents: [GatewayIntentBits.Guilds ] }); + +client.on('ready', async () => { + if (client.user) { + console.log(`已登入 Discord: ${client.user.tag}`); + const allCommands = [playerCommand, onlineCommand, serverCommand, whitelistCommand, notiPlayerCommand, sayCommand]; + allCommands.forEach(async (command) => { + await client.application?.commands.create(command.toJSON()); + }); + } +}); + +client.on('interactionCreate', async (interaction) => { + if (!interaction.isChatInputCommand()) return; + + if (config.CHANNEL_ID.includes(interaction.channelId)) { + await interaction.deferReply(); + switch (interaction.commandName) { + case 'player': + const playerName = interaction.options.getString('玩家名'); + if (!playerName) { + await interaction.editReply('請輸入玩家名稱。'); + } else { + const getPlayerInfo = await getPlayer(playerName); + if (!getPlayerInfo) { + await interaction.editReply('\:warning: 出錯了,請稍後再試一次。'); + } else { + let model; + if (getPlayerInfo.textures.SKIN.url) { + model = await render(getPlayerInfo.textures.SKIN.url); + } + let replyMessage: { files?: any; content: string } = { + content: `玩家名:\`${getPlayerInfo.profileName}\`\n玩家ID:\`${getPlayerInfo.profileId}\`\n`, + }; + if (model) { + replyMessage = { ...replyMessage, files: [{ attachment: model }] }; + } + await interaction.editReply(replyMessage); + } + } + break; + case 'online': + const onlinePlayers = await getOnlinePlayers(); + if (!onlinePlayers?.list) { + await interaction.editReply('伺服器目前為離線狀態,或無法取得在線玩家數,請稍後再試一次。'); + } else { + let reply = '目前有 `' + onlinePlayers.online + '` 名玩家在線'; + if (onlinePlayers.online > 0) { + reply += ':\n'; + } + onlinePlayers.list.forEach((player) => { + reply += '\n\:bust_in_silhouette: ' + player; + }); + await interaction.editReply(reply); + } + break; + case 'server': + const serverInfo = await getServerInfo(); + if (!serverInfo) { + await interaction.editReply('伺服器目前為離線狀態。'); + } else { + let result = `【FreeCityHK Minecraft】\n${serverInfo.motd.clean}\n版本:\`${serverInfo.version}\`\n玩家人數:\`${serverInfo.players.online}/${serverInfo.players.max}\``; + await interaction.editReply(result); + } + break; + case 'whitelist': + const newPlayerName = interaction.options.getString('玩家名'); + if (!newPlayerName) { + await interaction.editReply('請輸入玩家名稱。'); + } else { + const updateWhitelist = await add2Whitelist(newPlayerName); + if (!updateWhitelist) { + await interaction.editReply('\:warning: 出錯了,請稍後再試一次。'); + } else { + await interaction.editReply(updateWhitelist); + } + } + break; + case 'noti': + const notiMessage = interaction.options.getString('公告訊息'); + if (!notiMessage) { + await interaction.editReply('請輸入要發放的公告通知。'); + } else { + const notiResult = await notiPlayer(notiMessage); + if (!notiResult) { + await interaction.editReply('\:warning: 出錯了,請稍後再試一次。'); + } else { + await interaction.editReply(notiResult); + } + } + break; + case 'say': + const sayMessage = interaction.options.getString('訊息'); + if (!sayMessage) { + await interaction.editReply('請輸入要發送的訊息。'); + } else { + await say(sayMessage); + await interaction.editReply(`已發送訊息:${sayMessage}`); + } + break; + } + } +}); + + +const updateChannelName = async () => { + if (config.UPDATE_CHANNEL) { + const serverInfo = await getServerInfo(); + const onlinePlayers = serverInfo?.players.online ?? '🔴'; + let title = `🎲|在線人數:${onlinePlayers}`; // 𝐅𝐫𝐞𝐞𝐂𝐢𝐭𝐲𝐇𝐊 + + config.UPDATE_CHANNEL.forEach(async (channelId) => { + const channel = await client.channels.fetch(channelId) as VoiceChannel; + + if (channel.name !== title) { + channel.setName(title); + } + }); + } +} + +const start = () => { + try { + client.login(config.TOKEN); + } catch (err) { + console.error(err); + process.exit(1); + } +}; + +start(); + + +const job = schedule.scheduleJob('*/10 * * * *', async () => { + await updateChannelName(); +}); \ No newline at end of file diff --git a/src/commands/minecraft.ts b/src/commands/minecraft.ts new file mode 100644 index 0000000..996e5a2 --- /dev/null +++ b/src/commands/minecraft.ts @@ -0,0 +1,67 @@ +import { config } from '../config'; +import * as util from 'minecraft-server-util'; +import { SlashCommandBuilder } from 'discord.js'; + +const add2Whitelist = async (playerName: string) => { + try { + const client = new util.RCON(); + await client.connect(config.SERVER, config.RCON_PORT /* , options */); + await client.login(config.RCON_PW); + const message = await client.execute(`whitelist add ${playerName}`); + return message; + } catch (e) { + return null; + } +}; + +const notiPlayer = async (message: string, color: string = 'light_purple') => { + try { + const client = new util.RCON(); + await client.connect(config.SERVER, config.RCON_PORT /* , options */); + await client.login(config.RCON_PW); + const result = await client.execute( + `/title @a title {"text":"${string2Unicode(message)}","bold":true,"color":"${color}"}` + ); + return result; + } catch (e) { + return null; + } +}; + +const say = async (message: string) => { + try { + const client = new util.RCON(); + await client.connect(config.SERVER, config.RCON_PORT /* , options */); + await client.login(config.RCON_PW); + const result = await client.execute(`say ${message}`); + return result; + } catch (e) { + return null; + } +}; + +const string2Unicode = (str: string) => { + let result = ''; + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i).toString(16).toUpperCase(); + result += '\\u' + '0'.repeat(4 - code.length) + code; + } + return result; +}; + +const whitelistCommand = new SlashCommandBuilder() + .setName('whitelist') + .setDescription('將玩家加入白名單') + .addStringOption((option) => option.setName('玩家名').setDescription('Minecraft 玩家的名稱').setRequired(true)); + +const notiPlayerCommand = new SlashCommandBuilder() + .setName('noti') + .setDescription('發放公告通知,將會在玩家畫面中間顯示') + .addStringOption((option) => option.setName('公告訊息').setDescription('公告内容').setRequired(true)); + +const sayCommand = new SlashCommandBuilder() + .setName('say') + .setDescription('透過聊天向多個玩家發送訊息') + .addStringOption((option) => option.setName('訊息').setDescription('訊息内容').setRequired(true)); + +export { add2Whitelist, notiPlayer, say, whitelistCommand, notiPlayerCommand, sayCommand }; diff --git a/src/commands/playerInfo.ts b/src/commands/playerInfo.ts new file mode 100644 index 0000000..66a0248 --- /dev/null +++ b/src/commands/playerInfo.ts @@ -0,0 +1,54 @@ +import { SlashCommandBuilder } from 'discord.js'; +import { config } from '../config'; +import axios, { AxiosResponse } from 'axios'; +import { Player } from '../interface/Player'; +import { PlayerProfile } from '../interface/PlayerProfile'; + +const getPlayerId = async (name: string): Promise => { + const name2IdApi = config.PLAYER_API.replace('{{NAME}}', name); + const player: AxiosResponse | null = await axios.get(name2IdApi).catch(() => { + return null; + }); + if (!player?.data) { + return null; + } + return player.data; +}; + +const getPlayerProfile = async (playerId: string): Promise => { + const profileApi = config.PROFILE_API.replace('{{PLAYER_ID}}', playerId); + const playerProfile = await axios.get(profileApi).catch(() => { + return null; + }); + if (!playerProfile?.data) { + return null; + } + + if (playerProfile.data.properties[0]?.value) { + const playerData: PlayerProfile = JSON.parse( + Buffer.from(playerProfile.data.properties[0].value, 'base64').toString('binary') + ); + return playerData; + } + + return null; +}; + +const getPlayer = async (name: string) => { + const playerInfo = await getPlayerId(name); + if (!playerInfo) { + return null; + } + const profile = await getPlayerProfile(playerInfo.id); + if (!profile) { + return { profileId: playerInfo.id, profileName: playerInfo.name }; + } + return profile; +}; + +const playerCommand = new SlashCommandBuilder() + .setName('player') + .setDescription('查看 Minecraft 玩家資料') + .addStringOption((option) => option.setName('玩家名').setDescription('Minecraft 玩家的名稱').setRequired(true)); + +export { playerCommand, getPlayerId, getPlayerProfile, getPlayer }; diff --git a/src/commands/serverInfo.ts b/src/commands/serverInfo.ts new file mode 100644 index 0000000..2bf6b7d --- /dev/null +++ b/src/commands/serverInfo.ts @@ -0,0 +1,30 @@ +import { SlashCommandBuilder, Client } from 'discord.js'; +import * as util from 'minecraft-server-util'; +import { config } from '../config'; +import { ServerInfo } from '../interface/ServerInfo'; + +const getServerInfo = async (): Promise => { + try { + const query: ServerInfo = await util.queryFull(config.SERVER, config.QUERY_PORT /* , options */); + if (!query) { + return null; + } + return query; + } catch (e) { + return null; + } +}; + +const getOnlinePlayers = async () => { + const serverInfo = await getServerInfo(); + if (!serverInfo?.players) { + return null; + } + return serverInfo.players; +}; + +const onlineCommand = new SlashCommandBuilder().setName('online').setDescription('查看 FreeCityHK 在線玩家'); + +const serverCommand = new SlashCommandBuilder().setName('server').setDescription('FreeCityHK Minecraft 伺服器'); + +export { getServerInfo, getOnlinePlayers, onlineCommand, serverCommand }; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..b823869 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,40 @@ +import * as dotenv from 'dotenv'; + +const loadConfig = dotenv.config(); +if (loadConfig.error) { + console.error('[ERROR] Cannot load config'); + throw loadConfig.error; +} + +const parsedConfig = loadConfig.parsed; + +if (!parsedConfig) { + console.error('[ERROR] Cannot parse config'); + throw new Error(); +} + +interface Config { + TOKEN: string; + CHANNEL_ID: string[]; + UPDATE_CHANNEL: string[]; + SERVER: string; + QUERY_PORT: number; + RCON_PORT: number; + RCON_PW: string; + PLAYER_API: string; + PROFILE_API: string; +} + +const config: Config = { + TOKEN: parsedConfig.TOKEN, + CHANNEL_ID: parsedConfig.CHANNEL_ID.split(','), + UPDATE_CHANNEL: parsedConfig.UPDATE_CHANNEL.split(','), + SERVER: parsedConfig.SERVER, + QUERY_PORT: parseInt(parsedConfig.QUERY_PORT ?? '25565'), + RCON_PORT: parseInt(parsedConfig.RCON_PORT ?? '25575'), + RCON_PW: parsedConfig.RCON_PW, + PLAYER_API: parsedConfig.PLAYER_API, + PROFILE_API: parsedConfig.PROFILE_API, +}; + +export { config }; diff --git a/src/interface/Player.ts b/src/interface/Player.ts new file mode 100644 index 0000000..f026259 --- /dev/null +++ b/src/interface/Player.ts @@ -0,0 +1,7 @@ +interface Player { + id: string, + name: string, + properties?: any +} + +export {Player}; \ No newline at end of file diff --git a/src/interface/PlayerProfile.ts b/src/interface/PlayerProfile.ts new file mode 100644 index 0000000..67888d2 --- /dev/null +++ b/src/interface/PlayerProfile.ts @@ -0,0 +1,20 @@ +interface PlayerProfile { + timestamp?: number | string, + profileId: string, + profileName: string, + textures?: PlayerTextures | any, + [key: string]: any +} + +interface PlayerTextures { + SKIN: PlayerSkin, + [key: string]: any +} + +interface PlayerSkin { + url: string, + metadata: any, + [key: string]: any +} + +export {PlayerProfile, PlayerTextures, PlayerSkin} \ No newline at end of file diff --git a/src/interface/ServerInfo.ts b/src/interface/ServerInfo.ts new file mode 100644 index 0000000..b3d0930 --- /dev/null +++ b/src/interface/ServerInfo.ts @@ -0,0 +1,25 @@ +interface ServerInfo { + motd: motd, + version: string, + software?: string, + plugins?: any, + map: string, + players: playerInfo, + hostIP?: string, + hostPort?: number, + [key: string]: any +} + +interface motd { + raw: string, + clean: string, + html: string +} + +interface playerInfo { + online: number, + max: number, + list: string[] +} + +export {ServerInfo} diff --git a/src/render.ts b/src/render.ts new file mode 100644 index 0000000..dbe824a --- /dev/null +++ b/src/render.ts @@ -0,0 +1,205 @@ +// Reference: https://github.com/crafatar/crafatar/ + +import * as cvs from 'canvas'; + +const removeOpacity = (canvas: cvs.Canvas) => { + let ctx = canvas.getContext('2d'); + let imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height); + let data = imagedata.data; + for (let i = 0; i < data.length; i += 4) { + data[i + 3] = 255; + } + ctx.putImageData(imagedata, 0, 0); + return canvas; +}; + +const containsOpacity = (canvas: cvs.Canvas) => { + let ctx = canvas.getContext('2d'); + let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + for (let i = 3; i < imageData.length; i += 4) { + if (imageData[i] < 255) { + return true; + } + } + return false; +}; + +const resize = (src: cvs.Canvas, scale: number) => { + let dst = cvs.createCanvas(scale * src.width, scale * src.height); + let context = dst.getContext('2d'); + context.patternQuality = 'fast'; + context.drawImage(src, 0, 0, src.width * scale, src.height * scale); + return dst; +}; + +function getPart(src: cvs.Image, x: number, y: number, width: number, height: number, scale: number) { + let dst = cvs.createCanvas(scale * width, scale * height); + let context = dst.getContext('2d'); + context.patternQuality = 'fast'; + context.drawImage(src, x, y, width, height, 0, 0, width * scale, height * scale); + return dst; +} + +function flip(src: cvs.Canvas) { + let dst = cvs.createCanvas(src.width, src.height); + let context = dst.getContext('2d'); + context.scale(-1, 1); + context.drawImage(src, -src.width, 0); + return dst; +} + +const skewA = 26 / 45; // 0.57777777 +const skewB = skewA * 2; // 1.15555555 + +const render = async ( + img: string, + scale: number = 6, + overlay: boolean = true, + renderBody: boolean = true, + slim: boolean = false +) => { + let canvas = cvs.createCanvas(scale * 20, scale * (renderBody ? 45.1 : 18.5)); + let ctx = canvas.getContext('2d'); + const skin = await cvs.loadImage(img); + let oldSkin = skin.height === 32; + let armWidth = slim ? 3 : 4; + + let headTop = resize(removeOpacity(getPart(skin, 8, 0, 8, 8, 1)), scale); + let headFront = resize(removeOpacity(getPart(skin, 8, 8, 8, 8, 1)), scale); + let headRight = resize(removeOpacity(getPart(skin, 0, 8, 8, 8, 1)), scale); + + let armRightTop = resize(removeOpacity(getPart(skin, 44, 16, armWidth, 4, 1)), scale); + let armRightFront = resize(removeOpacity(getPart(skin, 44, 20, armWidth, 12, 1)), scale); + let armRightSide = resize(removeOpacity(getPart(skin, 40, 20, 4, 12, 1)), scale); + + let armLeftTop = oldSkin ? flip(armRightTop) : resize(removeOpacity(getPart(skin, 36, 48, armWidth, 4, 1)), scale); + let armLeftFront = oldSkin + ? flip(armRightFront) + : resize(removeOpacity(getPart(skin, 36, 52, armWidth, 12, 1)), scale); + + let legRightFront = resize(removeOpacity(getPart(skin, 4, 20, 4, 12, 1)), scale); + let legRightSide = resize(removeOpacity(getPart(skin, 0, 20, 4, 12, 1)), scale); + + let legLeftFront = oldSkin ? flip(legRightFront) : resize(removeOpacity(getPart(skin, 20, 52, 4, 12, 1)), scale); + + let bodyFront = resize(removeOpacity(getPart(skin, 20, 20, 8, 12, 1)), scale); + + if (overlay) { + if (containsOpacity(getPart(skin, 32, 0, 32, 32, 1))) { + // render head overlay + headTop.getContext('2d').drawImage(getPart(skin, 40, 0, 8, 8, scale), 0, 0); + headFront.getContext('2d').drawImage(getPart(skin, 40, 8, 8, 8, scale), 0, 0); + headRight.getContext('2d').drawImage(getPart(skin, 32, 8, 8, 8, scale), 0, 0); + } + + if (!oldSkin) { + let bodyArea = getPart(skin, 16, 32, 32, 16, 1); + let rightArm = getPart(skin, 48, 48, 16, 16, 1); + let leftArm = getPart(skin, 40, 32, 16, 16, 1); + let rightLeg = getPart(skin, 0, 32, 16, 16, 1); + let leftLeg = getPart(skin, 0, 48, 16, 16, 1); + + if (containsOpacity(bodyArea)) { + // render body overlay + bodyFront.getContext('2d').drawImage(getPart(skin, 20, 36, 8, 12, scale), 0, 0); + } + + if (containsOpacity(rightArm)) { + // render right arm overlay + armRightTop.getContext('2d').drawImage(getPart(skin, 44, 32, armWidth, 4, scale), 0, 0); + armRightFront.getContext('2d').drawImage(getPart(skin, 44, 36, armWidth, 12, scale), 0, 0); + armRightSide.getContext('2d').drawImage(getPart(skin, 40, 36, 4, 12, scale), 0, 0); + } + + if (containsOpacity(leftArm)) { + // render left arm overlay + armLeftTop.getContext('2d').drawImage(getPart(skin, 36 + 16, 48, armWidth, 4, scale), 0, 0); + armLeftFront.getContext('2d').drawImage(getPart(skin, 36 + 16, 52, armWidth, 12, scale), 0, 0); + } + + if (containsOpacity(rightLeg)) { + // render right leg overlay + legRightFront.getContext('2d').drawImage(getPart(skin, 4, 36, 4, 12, scale), 0, 0); + legRightSide.getContext('2d').drawImage(getPart(skin, 0, 36, 4, 12, scale), 0, 0); + } + + if (containsOpacity(leftLeg)) { + // render left leg overlay + legLeftFront.getContext('2d').drawImage(getPart(skin, 4, 52, 4, 12, scale), 0, 0); + } + } + } + + let x = 0; + let y = 0; + let z = 0; + + let zOffset = scale * 3; + let xOffset = scale * 2; + + if (renderBody) { + // pre-render front onto separate canvas + let front = cvs.createCanvas(scale * 16, scale * 24); + let frontCtx = front.getContext('2d'); + frontCtx.patternQuality = 'fast'; + + frontCtx.drawImage(armRightFront, (4 - armWidth) * scale, 0 * scale, armWidth * scale, 12 * scale); + frontCtx.drawImage(armLeftFront, 12 * scale, 0 * scale, armWidth * scale, 12 * scale); + frontCtx.drawImage(bodyFront, 4 * scale, 0 * scale, 8 * scale, 12 * scale); + frontCtx.drawImage(legRightFront, 4 * scale, 12 * scale, 4 * scale, 12 * scale); + frontCtx.drawImage(legLeftFront, 8 * scale, 12 * scale, 4 * scale, 12 * scale); + + // top + x = xOffset + scale * 2; + y = scale * -armWidth; + z = zOffset + scale * 8; + ctx.setTransform(new cvs.DOMMatrix([1, -skewA, 1, skewA, 0, 0])); + ctx.drawImage(armRightTop, y - z - 0.5, x + z, armRightTop.width + 1, armRightTop.height + 1); + + y = scale * 8; + ctx.drawImage(armLeftTop, y - z, x + z, armLeftTop.width, armLeftTop.height + 1); + + // right side + ctx.setTransform(new cvs.DOMMatrix([1, skewA, 0, skewB, 0, 0])); + x = xOffset + scale * 2; + y = 0; + z = zOffset + scale * 20; + ctx.drawImage(legRightSide, x + y, z - y, legRightSide.width, legRightSide.height); + + x = xOffset + scale * 2; + y = scale * -armWidth; + z = zOffset + scale * 8; + ctx.drawImage(armRightSide, x + y, z - y - 0.5, armRightSide.width, armRightSide.height + 1); + + // front + z = zOffset + scale * 12; + y = 0; + ctx.setTransform(new cvs.DOMMatrix([1, -skewA, 0, skewB, 0, skewA])); + ctx.drawImage(front, y + x, x + z - 0.5, front.width, front.height); + } + + // head top + x = xOffset; + y = -0.5; + z = zOffset; + ctx.setTransform(new cvs.DOMMatrix([1, -skewA, 1, skewA, 0, 0])); + ctx.drawImage(headTop, y - z, x + z, headTop.width, headTop.height + 1); + + // head front + x = xOffset + 8 * scale; + y = 0; + z = zOffset - 0.5; + ctx.setTransform(new cvs.DOMMatrix([1, -skewA, 0, skewB, 0, skewA])); + ctx.drawImage(headFront, y + x, x + z, headFront.width, headFront.height); + + // head right + x = xOffset; + y = 0; + z = zOffset; + ctx.setTransform(new cvs.DOMMatrix([1, skewA, 0, skewB, 0, 0])); + ctx.drawImage(headRight, x + y, z - y - 0.5, headRight.width + 0.5, headRight.height + 1); + + return canvas.toBuffer(); +}; + +export { render }; diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..b41ad69 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,8 @@ +const number2Digit = (num: number | string) => { + const chars = num.toString().split(''); + const digit = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + const convert = chars.map((char) => digit[parseInt(char)]); + return convert.join(''); +}; + +export { number2Digit }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b49fd91 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": ["es6", "dom", "es2017"], + "strict": true, + "outDir": "run", + "rootDir": "src", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "src/*" + ], +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..1df78cb --- /dev/null +++ b/yarn.lock @@ -0,0 +1,734 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@discordjs/builders@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@discordjs/builders/-/builders-1.5.0.tgz" + integrity sha512-7XxT78mnNBPigHn2y6KAXkicxIBFtZREGWaRZ249EC1l6gBUEP8IyVY5JTciIjJArxkF+tg675aZvsTNTKBpmA== + dependencies: + "@discordjs/formatters" "^0.2.0" + "@discordjs/util" "^0.2.0" + "@sapphire/shapeshift" "^3.8.1" + discord-api-types "^0.37.35" + fast-deep-equal "^3.1.3" + ts-mixer "^6.0.3" + tslib "^2.5.0" + +"@discordjs/collection@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@discordjs/collection/-/collection-1.4.0.tgz" + integrity sha512-hiOJyk2CPFf1+FL3a4VKCuu1f448LlROVuu8nLz1+jCOAPokUcdFAV+l4pd3B3h6uJlJQSASoZzrdyNdjdtfzQ== + +"@discordjs/formatters@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.2.0.tgz" + integrity sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA== + dependencies: + discord-api-types "^0.37.35" + +"@discordjs/rest@^1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@discordjs/rest/-/rest-1.6.0.tgz" + integrity sha512-HGvqNCZ5Z5j0tQHjmT1lFvE5ETO4hvomJ1r0cbnpC1zM23XhCpZ9wgTCiEmaxKz05cyf2CI9p39+9LL+6Yz1bA== + dependencies: + "@discordjs/collection" "^1.4.0" + "@discordjs/util" "^0.2.0" + "@sapphire/async-queue" "^1.5.0" + "@sapphire/snowflake" "^3.4.0" + discord-api-types "^0.37.35" + file-type "^18.2.1" + tslib "^2.5.0" + undici "^5.20.0" + +"@discordjs/util@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@discordjs/util/-/util-0.2.0.tgz" + integrity sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg== + +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.10" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@sapphire/async-queue@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz" + integrity sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA== + +"@sapphire/shapeshift@^3.8.1": + version "3.8.1" + resolved "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.8.1.tgz" + integrity sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw== + dependencies: + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + +"@sapphire/snowflake@^3.4.0": + version "3.4.0" + resolved "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.0.tgz" + integrity sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw== + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + +"@types/node-schedule@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/node-schedule/-/node-schedule-2.1.0.tgz#60375640c0509bab963573def9d1f417f438c290" + integrity sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "18.15.3" + resolved "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz" + integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== + +"@types/ws@^8.5.4": + version "8.5.4" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + +abbrev@1: + version "1.1.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.3.4: + version "1.3.4" + resolved "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz" + integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +canvas@^2.11.0: + version "2.11.0" + resolved "https://registry.npmjs.org/canvas/-/canvas-2.11.0.tgz" + integrity sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +cron-parser@^4.2.0: + version "4.8.1" + resolved "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz" + integrity sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ== + dependencies: + luxon "^3.2.1" + +debug@4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + +discord-api-types@^0.37.35: + version "0.37.36" + resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.36.tgz" + integrity sha512-Nlxmp10UpVr/utgZ9uODQvG2Or+5w7LFrvFMswyeKC9l/+UaqGT6H0OVgEFhu9GEO4U6K7NNO5W8Carv7irnCA== + +discord.js@^14.8.0: + version "14.8.0" + resolved "https://registry.npmjs.org/discord.js/-/discord.js-14.8.0.tgz" + integrity sha512-UOxYtc/YnV7jAJ2gISluJyYeBw4e+j8gWn+IoqG8unaHAVuvZ13DdYN0M1f9fbUgUvSarV798inIrYFtDNDjwQ== + dependencies: + "@discordjs/builders" "^1.5.0" + "@discordjs/collection" "^1.4.0" + "@discordjs/formatters" "^0.2.0" + "@discordjs/rest" "^1.6.0" + "@discordjs/util" "^0.2.0" + "@sapphire/snowflake" "^3.4.0" + "@types/ws" "^8.5.4" + discord-api-types "^0.37.35" + fast-deep-equal "^3.1.3" + lodash.snakecase "^4.1.1" + tslib "^2.5.0" + undici "^5.20.0" + ws "^8.12.1" + +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +file-type@^18.2.1: + version "18.2.1" + resolved "https://registry.npmjs.org/file-type/-/file-type-18.2.1.tgz" + integrity sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg== + dependencies: + readable-web-to-node-stream "^3.0.2" + strtok3 "^7.0.0" + token-types "^5.0.1" + +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +lodash.snakecase@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz" + integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long-timeout@0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz" + integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +luxon@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz" + integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg== + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +minecraft-motd-util@^1.1.9: + version "1.1.12" + resolved "https://registry.npmjs.org/minecraft-motd-util/-/minecraft-motd-util-1.1.12.tgz" + integrity sha512-5TuTRjrRupSTruea0nRC37r0FdhkS1O4wIJKAYfwJRCQd/X4Zyl/dVIs96h9UVW6N8jhIuz9pNkrDsqyN7VBdA== + +minecraft-server-util@^5.4.0: + version "5.4.0" + resolved "https://registry.npmjs.org/minecraft-server-util/-/minecraft-server-util-5.4.0.tgz" + integrity sha512-KvwS125IxSK5vWvYp2mMuotRCZkHPkEL/iuHcfeDdBtn+GvvSdIgln/Ucy/+yyE9WN64Y64HluSHjd4W8Mlz1Q== + dependencies: + minecraft-motd-util "^1.1.9" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.2.5" + resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz" + integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nan@^2.17.0: + version "2.17.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + +node-fetch@^2.6.7: + version "2.6.9" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz" + integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + dependencies: + whatwg-url "^5.0.0" + +node-schedule@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-2.1.1.tgz#6958b2c5af8834954f69bb0a7a97c62b97185de3" + integrity sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ== + dependencies: + cron-parser "^4.2.0" + long-timeout "0.1.1" + sorted-array-functions "^1.3.0" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0, once@^1.3.1: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +peek-readable@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz" + integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + 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.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5: + version "7.3.8" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +sorted-array-functions@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz" + integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strtok3@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz" + integrity sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^5.0.0" + +tar@^6.1.11: + version "6.1.13" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +token-types@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz" + integrity sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-mixer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz" + integrity sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ== + +tslib@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +typescript@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz" + integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== + +undici@^5.20.0: + version "5.21.0" + resolved "https://registry.npmjs.org/undici/-/undici-5.21.0.tgz" + integrity sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA== + dependencies: + busboy "^1.6.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.12.1: + version "8.13.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==