From 8a0f8f1137f956a4d0e39efdd1013adaed39f895 Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Sat, 4 Jan 2025 05:26:57 -0600 Subject: [PATCH 1/4] feature team search --- deploy-commands.js | 25 ++++++++++++ src/commands/CommandHandler.js | 52 +++++++++++++++++++++++++ src/services/PlayerService.js | 32 ++++++++++++++++ src/utils/embedBuilders.js | 70 ++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) diff --git a/deploy-commands.js b/deploy-commands.js index 687ff38..f7cb6c3 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -86,6 +86,31 @@ const commands = [ .setName("list_subscriptions") .setDescription("List all game subscriptions for this server") .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + new SlashCommandBuilder() + .setName("findteam") + .setDescription("Find a team by name") + .addStringOption((option) => + option + .setName("teamname") + .setDescription("The team name to search for") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("game") + .setDescription("Filter by game") + .setRequired(false) + .addChoices( + { name: "Big Ballers VR", value: "Big Ballers VR" }, + { name: "Blacktop Hoops", value: "Blacktop Hoops" }, + { name: "Breachers", value: "Breachers" }, + { name: "Echo Arena", value: "Echo Arena" }, + { name: "Echo Combat", value: "Echo Combat" }, + { name: "Gun Raiders", value: "Gun Raiders" }, + { name: "Nock", value: "Nock" }, + { name: "VAIL", value: "VAIL" } + ) + ), ]; const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN); diff --git a/src/commands/CommandHandler.js b/src/commands/CommandHandler.js index 756f4a1..198b720 100644 --- a/src/commands/CommandHandler.js +++ b/src/commands/CommandHandler.js @@ -34,6 +34,9 @@ class CommandHandler { case 'list_subscriptions': await this.subscriptionCommands.handleListSubscriptions(interaction); break; + case 'findteam': + await this.handleFindTeam(interaction); + break; default: await interaction.editReply({ content: 'โŒ Unknown command', @@ -217,6 +220,55 @@ class CommandHandler { } } + async handleFindTeam(interaction) { + try { + const teamName = interaction.options.getString('teamname'); + const gameFilter = interaction.options.getString('game'); + + const teamData = await this.playerService.findTeamByName(teamName, gameFilter); + if (!teamData || !teamData.success || !teamData.teams || teamData.teams.length === 0) { + await interaction.editReply({ + content: 'โŒ No teams found matching your search criteria.', + }); + return; + } + + const embed = EmbedBuilders.createTeamEmbed(teamData.teams); + + // Create buttons for each team + const rows = teamData.teams.slice(0, 5).map(team => { + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel(`๐Ÿ”ต View ${team.name} (${team.game_mode})`) + .setStyle(ButtonStyle.Link) + .setURL(`${this.playerService.baseUrl}/teams/${team.id}`), + ); + }); + + // Add Discord join button in a separate row + rows.push( + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('๐ŸŸก Join Discord') + .setStyle(ButtonStyle.Link) + .setURL('https://discord.gg/j3DKVATPGQ') + ) + ); + + await interaction.editReply({ + embeds: [embed], + components: rows, + }); + } catch (error) { + this.logger.error('Error in handleFindTeam:', { + error: error.message, + teamname: interaction.options.getString('teamname'), + timestamp: new Date().toISOString() + }); + throw error; + } + } + createActionRow(username) { return new ActionRowBuilder().addComponents( new ButtonBuilder() diff --git a/src/services/PlayerService.js b/src/services/PlayerService.js index 98e6ffd..82e7ce9 100644 --- a/src/services/PlayerService.js +++ b/src/services/PlayerService.js @@ -69,6 +69,38 @@ class PlayerService { getStatsUrl(username) { return `${this.baseUrl}/profile/${username}/stats`; } + + async findTeamByName(teamName, gameFilter = null) { + try { + console.log(`Fetching team data for: ${teamName}${gameFilter ? ` in ${gameFilter}` : ''}`); + const url = `${this.baseUrl}/api/get_team_data_by_name/${encodeURIComponent(teamName)}`; + console.log(`API URL: ${url}`); + + const response = await axios.get(url, { + timeout: 5000 + }); + + console.log('API Response:', JSON.stringify(response.data, null, 2)); + + if (response.data && response.data.success) { + // Filter teams by game if gameFilter is provided + if (gameFilter && response.data.teams) { + response.data.teams = response.data.teams.filter( + team => team.game_name.toLowerCase() === gameFilter.toLowerCase() + ); + } + } + + return response.data; + } catch (error) { + console.error('Error fetching team data:', { + message: error.message, + response: error.response?.data, + status: error.response?.status + }); + return null; + } + } } module.exports = PlayerService; \ No newline at end of file diff --git a/src/utils/embedBuilders.js b/src/utils/embedBuilders.js index 3eb0b3e..ba7c262 100644 --- a/src/utils/embedBuilders.js +++ b/src/utils/embedBuilders.js @@ -208,6 +208,76 @@ class EmbedBuilders { .setURL('https://discord.gg/j3DKVATPGQ') ); } + + static createTeamEmbed(teams) { + const embed = new EmbedBuilder() + .setTitle(`Teams Found`) + .setColor('#0099ff'); + + if (!teams || teams.length === 0) { + embed.setDescription('No teams found'); + return embed; + } + + // Sort teams by date created (newest first) + teams.sort((a, b) => new Date(b.date_created) - new Date(a.date_created)); + + teams.forEach((team, index) => { + // Add team header + embed.addFields({ + name: `${index + 1}. ${team.name} - ${team.game_name} (${team.game_mode})`, + value: '\u200B' + }); + + // Add team stats + const statsFields = []; + if (team.matches) statsFields.push(`Matches: ${team.matches}`); + if (team.wins) statsFields.push(`Wins: ${team.wins}`); + if (team.losses) statsFields.push(`Losses: ${team.losses}`); + if (team.forfeits) statsFields.push(`Forfeits: ${team.forfeits}`); + + const winRate = team.matches > 0 + ? ((parseInt(team.wins) / parseInt(team.matches)) * 100).toFixed(1) + : 0; + + if (statsFields.length > 0) { + embed.addFields({ + name: 'Stats', + value: `${statsFields.join(' | ')} | Win Rate: ${winRate}%`, + inline: false + }); + } + + // Add team members + if (team.players && team.players.length > 0) { + const playerList = team.players + .map(player => `- ${player.username} (${player.position})`) + .join('\n'); + + embed.addFields({ + name: 'Players', + value: playerList, + inline: false + }); + } + + // Add team creation date + if (team.date_created) { + embed.addFields({ + name: 'Created', + value: new Date(team.date_created).toLocaleDateString(), + inline: true + }); + } + + // Add separator between teams + if (index < teams.length - 1) { + embed.addFields({ name: '\u200B', value: '\u200B' }); + } + }); + + return embed; + } } module.exports = EmbedBuilders; \ No newline at end of file From 7e4354e37bfc61be3a66e95dd913eb74cf0f3044 Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Sat, 4 Jan 2025 08:59:51 -0600 Subject: [PATCH 2/4] Jan 2025 Update Team Search Improvements: Added required game selection before team name input Added VR Battles image as the main embed image Removed buttons for cleaner display Added sorting by game mode (Squads > Duo > Solo) Made team display more compact with icons and shortened stats Added SQL injection protection and input sanitization User Search Improvements: Made game selection required before username input Added VR Battles image as the main embed image Added better user status messages: played Security Enhancements: Added input validation and sanitization at multiple levels Limited input lengths to prevent buffer overflow Added proper error handling and logging Implemented safe API calls with timeouts and validation Added protection against SQL injection Code Organization: Improved error messages for better user feedback Added comprehensive logging for monitoring Made responses visible to everyone in the channel Cleaned up code structure and removed redundant parts Development Environment: Set up proper development configuration Added environment variable management Improved command deployment process --- .env.example | 16 ++ .gitignore | 37 +++- README.md | 88 ++++++++ deploy-commands.js | 36 +-- node_modules/.package-lock.json | 358 ++++++++++++++++++++++++++++++ package-lock.json | 376 ++++++++++++++++++++++++++++++++ package.json | 10 +- src/commands/CommandHandler.js | 128 ++++++++--- src/services/PlayerService.js | 72 ++++-- src/utils/embedBuilders.js | 115 +++++----- 10 files changed, 1104 insertions(+), 132 deletions(-) create mode 100644 .env.example create mode 100644 README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ade87cc --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Discord Bot Configuration +DISCORD_TOKEN=your_dev_bot_token +CLIENT_ID=your_dev_client_id + +# Supabase Configuration +SUPABASE_URL=your_dev_supabase_url +SUPABASE_KEY=your_dev_supabase_key + +# Webhook Configuration (for match notifications) +WEBHOOK_SECRET=your_webhook_secret + +# Channel Configuration +NOTIFICATION_CHANNEL_ID=your_notification_channel_id + +# Environment +NODE_ENV=development \ No newline at end of file diff --git a/.gitignore b/.gitignore index ac68df7..9ad988d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,35 @@ - node_modules/ - .env +# Environment variables .env -/node_modules +.env.local +.env.*.local +.env.development +.env.production +.env.test + +# Dependencies +node_modules/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..81253e1 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# VRBattles Discord Bot (Development) + +This is the development version of the VRBattles Discord Bot. It connects to the VRBattles API and provides various commands for users to interact with VRBattles data through Discord. + +## Setup + +1. Clone the repository: +```bash +git clone +cd discord-bot +``` + +2. Install dependencies: +```bash +npm install +``` + +3. Create a `.env` file based on `.env.example`: +```bash +cp .env.example .env +``` + +4. Set up your development environment: + +- Create a new Discord Application at https://discord.com/developers/applications +- Create a new bot for your application +- Get your bot token and client ID +- Create a new Supabase project at https://supabase.com +- Get your Supabase URL and anon key + +5. Update your `.env` file with the development credentials: +```env +DISCORD_TOKEN=your_dev_bot_token +CLIENT_ID=your_dev_client_id +SUPABASE_URL=your_dev_supabase_url +SUPABASE_KEY=your_dev_supabase_key +NODE_ENV=development +``` + +## Development + +Start the bot in development mode: +```bash +npm run dev +``` + +Deploy slash commands to your development Discord server: +```bash +npm run deploy-commands:dev +``` + +## Available Commands + +- `/ping` - Test bot connectivity +- `/finduser` - Find a user by username +- `/matchhistory` - View match history +- `/findteam` - Find a team by name +- `/subscribe` - Subscribe to game notifications (Admin only) +- `/unsubscribe` - Unsubscribe from game notifications (Admin only) +- `/register_server` - Register Discord server with BattleBot (Admin only) +- `/list_subscriptions` - List all game subscriptions (Admin only) + +## API Integration + +The bot connects to the following APIs: +- VRBattles API (`https://www.vrbattles.gg/api/`) +- Discord API (via discord.js) +- Supabase Database + +## Contributing + +1. Create a new branch for your feature: +```bash +git checkout -b feature/your-feature-name +``` + +2. Make your changes and commit them: +```bash +git add . +git commit -m "Description of your changes" +``` + +3. Push to your branch: +```bash +git push origin feature/your-feature-name +``` + +4. Create a Pull Request to merge into the dev branch \ No newline at end of file diff --git a/deploy-commands.js b/deploy-commands.js index f7cb6c3..d2d5c19 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -10,15 +10,25 @@ const commands = [ .setDescription("Find a user by username") .addStringOption((option) => option - .setName("username") - .setDescription("The username to search for") + .setName("game") + .setDescription("Select the game") .setRequired(true) + .addChoices( + { name: "Big Ballers VR", value: "Big Ballers VR" }, + { name: "Blacktop Hoops", value: "Blacktop Hoops" }, + { name: "Breachers", value: "Breachers" }, + { name: "Echo Arena", value: "Echo Arena" }, + { name: "Echo Combat", value: "Echo Combat" }, + { name: "Gun Raiders", value: "Gun Raiders" }, + { name: "Nock", value: "Nock" }, + { name: "VAIL", value: "VAIL" } + ) ) .addStringOption((option) => option - .setName("game") - .setDescription("Specify a game to view stats for") - .setRequired(false) + .setName("username") + .setDescription("The username to search for") + .setRequired(true) ), new SlashCommandBuilder() .setName("matchhistory") @@ -89,17 +99,11 @@ const commands = [ new SlashCommandBuilder() .setName("findteam") .setDescription("Find a team by name") - .addStringOption((option) => - option - .setName("teamname") - .setDescription("The team name to search for") - .setRequired(true) - ) .addStringOption((option) => option .setName("game") - .setDescription("Filter by game") - .setRequired(false) + .setDescription("Select the game") + .setRequired(true) .addChoices( { name: "Big Ballers VR", value: "Big Ballers VR" }, { name: "Blacktop Hoops", value: "Blacktop Hoops" }, @@ -110,6 +114,12 @@ const commands = [ { name: "Nock", value: "Nock" }, { name: "VAIL", value: "VAIL" } ) + ) + .addStringOption((option) => + option + .setName("teamname") + .setDescription("The team name to search for") + .setRequired(true) ), ]; diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 62cebb0..410c38f 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -317,6 +317,20 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -346,6 +360,26 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -370,6 +404,30 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -398,6 +456,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -455,6 +538,13 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -700,6 +790,19 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -804,6 +907,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -816,6 +932,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -892,6 +1018,13 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -913,6 +1046,52 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1032,6 +1211,19 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1047,6 +1239,70 @@ "node": ">= 0.6" } }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -1095,6 +1351,19 @@ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "license": "MIT" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1114,6 +1383,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1167,6 +1443,19 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1202,6 +1491,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1306,6 +1608,19 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1333,12 +1648,38 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1348,6 +1689,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1388,6 +1739,13 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", diff --git a/package-lock.json b/package-lock.json index b18fb05..10b5e6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,9 @@ "express": "^4.21.1", "undici": "^6.19.8", "winston": "^3.17.0" + }, + "devDependencies": { + "nodemon": "^3.1.0" } }, "node_modules/@colors/colors": { @@ -331,6 +334,20 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -360,6 +377,26 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -384,6 +421,30 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -412,6 +473,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -469,6 +555,13 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -714,6 +807,19 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -790,6 +896,21 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -818,6 +939,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -830,6 +964,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -906,6 +1050,13 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -927,6 +1078,52 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1046,6 +1243,19 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1061,6 +1271,70 @@ "node": ">= 0.6" } }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -1109,6 +1383,19 @@ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "license": "MIT" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1128,6 +1415,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1181,6 +1475,19 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1216,6 +1523,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1320,6 +1640,19 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1347,12 +1680,38 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1362,6 +1721,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1402,6 +1771,13 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", diff --git a/package.json b/package.json index 9679122..c9c3858 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,11 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "test:webhook": "NODE_ENV=test node src/test/testWebhook.js" + "start": "node -r dotenv/config index.js dotenv_config_path=.env.production", + "dev": "nodemon -r dotenv/config index.js dotenv_config_path=.env.development", + "deploy-commands": "node -r dotenv/config deploy-commands.js dotenv_config_path=.env.production", + "deploy-commands:dev": "node -r dotenv/config deploy-commands.js dotenv_config_path=.env.development", + "test:webhook": "node -r dotenv/config src/test/testWebhook.js dotenv_config_path=.env.test" }, "keywords": [], "author": "", @@ -18,5 +21,8 @@ "express": "^4.21.1", "undici": "^6.19.8", "winston": "^3.17.0" + }, + "devDependencies": { + "nodemon": "^3.1.0" } } diff --git a/src/commands/CommandHandler.js b/src/commands/CommandHandler.js index 198b720..bbbf190 100644 --- a/src/commands/CommandHandler.js +++ b/src/commands/CommandHandler.js @@ -151,10 +151,40 @@ class CommandHandler { const username = interaction.options.getString('username'); const gameFilter = interaction.options.getString('game'); - const userData = await this.playerService.findUserByUsername(username); + // Input validation + if (!username || typeof username !== 'string') { + await interaction.editReply({ + content: 'โŒ Invalid username provided.' + }); + return; + } + + // Enhanced sanitization + const sanitizedUsername = username + .replace(/[^a-zA-Z0-9\s\-_.]/g, '') + .trim() + .slice(0, 100); + + if (!sanitizedUsername) { + await interaction.editReply({ + content: 'โŒ Username must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).' + }); + return; + } + + // Log sanitized input for monitoring + this.logger.debug('User search input:', { + original: username, + sanitized: sanitizedUsername, + game: gameFilter, + userId: interaction.user.id, + timestamp: new Date().toISOString() + }); + + const userData = await this.playerService.findUserByUsername(sanitizedUsername); if (!userData || !userData.success) { await interaction.editReply({ - content: 'โŒ User not found or an error occurred while fetching data.', + content: 'โŒ User not found or an error occurred while fetching data.' }); return; } @@ -163,20 +193,43 @@ class CommandHandler { ? JSON.parse(userData.player_data) : userData.player_data; + // Check if the user is on a team for the specified game + const isOnTeam = playerData.teams && Object.values(playerData.teams) + .some(team => team.game_name === gameFilter); + + // Check if the user has played the specified game + const hasPlayedGame = playerData.stats?.games && + Object.keys(playerData.stats.games) + .some(game => game.toLowerCase() === gameFilter.toLowerCase()); + + if (!hasPlayedGame) { + const teamMessage = isOnTeam ? + `\nThey are on a team for ${gameFilter} but haven't played any matches yet.` : + ''; + await interaction.editReply({ + content: `โœ… User found, but they haven't played ${gameFilter} yet.${teamMessage}` + }); + return; + } + const embed = EmbedBuilders.createUserEmbed(playerData, gameFilter, this.playerService); const row = this.createActionRow(playerData.username); await interaction.editReply({ embeds: [embed], - components: [row], + components: [row] }); } catch (error) { this.logger.error('Error in handleFindUser:', { error: error.message, username: interaction.options.getString('username'), + game: interaction.options.getString('game'), + userId: interaction.user.id, timestamp: new Date().toISOString() }); - throw error; + await interaction.editReply({ + content: 'โŒ An error occurred while searching for the user.' + }); } } @@ -225,47 +278,64 @@ class CommandHandler { const teamName = interaction.options.getString('teamname'); const gameFilter = interaction.options.getString('game'); - const teamData = await this.playerService.findTeamByName(teamName, gameFilter); - if (!teamData || !teamData.success || !teamData.teams || teamData.teams.length === 0) { + // Input validation + if (!teamName || typeof teamName !== 'string') { await interaction.editReply({ - content: 'โŒ No teams found matching your search criteria.', + content: 'โŒ Invalid team name provided.' }); return; } - const embed = EmbedBuilders.createTeamEmbed(teamData.teams); + // Enhanced sanitization - only allow alphanumeric characters, spaces, and specific symbols + // Limit the length to prevent buffer overflow attacks + const sanitizedTeamName = teamName + .replace(/[^a-zA-Z0-9\s\-_.]/g, '') // Remove any characters that aren't alphanumeric, space, hyphen, underscore, or period + .trim() + .slice(0, 100); // Limit length to 100 characters - // Create buttons for each team - const rows = teamData.teams.slice(0, 5).map(team => { - return new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(`๐Ÿ”ต View ${team.name} (${team.game_mode})`) - .setStyle(ButtonStyle.Link) - .setURL(`${this.playerService.baseUrl}/teams/${team.id}`), - ); + if (!sanitizedTeamName) { + await interaction.editReply({ + content: 'โŒ Team name must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).' + }); + return; + } + + // Log sanitized input for monitoring + this.logger.debug('Team search input:', { + original: teamName, + sanitized: sanitizedTeamName, + game: gameFilter, + userId: interaction.user.id, + timestamp: new Date().toISOString() }); - // Add Discord join button in a separate row - rows.push( - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('๐ŸŸก Join Discord') - .setStyle(ButtonStyle.Link) - .setURL('https://discord.gg/j3DKVATPGQ') - ) - ); + // Game filter is pre-validated by Discord's slash command choices + const teamData = await this.playerService.findTeamByName(sanitizedTeamName, gameFilter); + + if (!teamData || !teamData.success || !teamData.teams || teamData.teams.length === 0) { + await interaction.editReply({ + content: 'โŒ No teams found matching your search criteria.' + }); + return; + } + + const embeds = EmbedBuilders.createTeamEmbed(teamData.teams); await interaction.editReply({ - embeds: [embed], - components: rows, + embeds: embeds, + components: [] }); } catch (error) { this.logger.error('Error in handleFindTeam:', { error: error.message, - teamname: interaction.options.getString('teamname'), + teamName: interaction.options.getString('teamname'), + game: interaction.options.getString('game'), + userId: interaction.user.id, timestamp: new Date().toISOString() }); - throw error; + await interaction.editReply({ + content: 'โŒ An error occurred while searching for teams.' + }); } } diff --git a/src/services/PlayerService.js b/src/services/PlayerService.js index 82e7ce9..0a106e3 100644 --- a/src/services/PlayerService.js +++ b/src/services/PlayerService.js @@ -70,35 +70,59 @@ class PlayerService { return `${this.baseUrl}/profile/${username}/stats`; } - async findTeamByName(teamName, gameFilter = null) { + async findTeamByName(teamName, gameFilter) { try { - console.log(`Fetching team data for: ${teamName}${gameFilter ? ` in ${gameFilter}` : ''}`); - const url = `${this.baseUrl}/api/get_team_data_by_name/${encodeURIComponent(teamName)}`; - console.log(`API URL: ${url}`); - - const response = await axios.get(url, { - timeout: 5000 - }); - - console.log('API Response:', JSON.stringify(response.data, null, 2)); - - if (response.data && response.data.success) { - // Filter teams by game if gameFilter is provided - if (gameFilter && response.data.teams) { - response.data.teams = response.data.teams.filter( - team => team.game_name.toLowerCase() === gameFilter.toLowerCase() - ); - } + // Double-check sanitization here as well for defense in depth + if (!teamName || typeof teamName !== 'string') { + throw new Error('Invalid team name provided'); } - + + // Additional sanitization at the service level + const sanitizedTeamName = teamName + .replace(/[^a-zA-Z0-9\s\-_.]/g, '') + .trim() + .slice(0, 100); + + if (!sanitizedTeamName) { + throw new Error('Invalid team name after sanitization'); + } + + // Use URL encoding for the query parameters + const encodedTeamName = encodeURIComponent(sanitizedTeamName); + const url = `${this.baseUrl}/api/get_team_data_by_name/${encodedTeamName}`; + + const response = await axios.get(url, { + timeout: 5000, // 5 second timeout + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + validateStatus: function (status) { + return status >= 200 && status < 300; // Only accept success status codes + } + }); + + // Validate response structure + if (!response.data || typeof response.data !== 'object') { + throw new Error('Invalid response format from API'); + } + + // If game filter is provided, filter the teams + if (gameFilter && response.data.teams) { + response.data.teams = response.data.teams.filter( + team => team.game_name === gameFilter + ); + } + return response.data; } catch (error) { - console.error('Error fetching team data:', { - message: error.message, - response: error.response?.data, - status: error.response?.status + this.logger.error('Error in findTeamByName:', { + error: error.message, + teamName, + gameFilter, + timestamp: new Date().toISOString() }); - return null; + return { success: false, error: 'Failed to fetch team data' }; } } } diff --git a/src/utils/embedBuilders.js b/src/utils/embedBuilders.js index ba7c262..56f3398 100644 --- a/src/utils/embedBuilders.js +++ b/src/utils/embedBuilders.js @@ -6,7 +6,8 @@ class EmbedBuilders { const embed = new EmbedBuilder() .setTitle(`User: ${playerData.username || 'Unknown'}`) .setDescription(profile.bio || 'No bio available') - .setColor('#0099ff'); + .setColor('#0099ff') + .setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); // Add thumbnail (avatar) if (profile.avatar) { @@ -15,12 +16,6 @@ class EmbedBuilders { ); } - // Add badge image using PlayerService - if (profile.current_level_badge) { - const badgeImageUrl = playerService.getBadgeImageUrl(profile.current_level_badge); - embed.setImage(badgeImageUrl); - } - // Add profile fields const profileFields = []; if (profile.country) profileFields.push({ name: 'Country', value: profile.country, inline: true }); @@ -210,73 +205,71 @@ class EmbedBuilders { } static createTeamEmbed(teams) { - const embed = new EmbedBuilder() + const embeds = []; + const infoEmbed = new EmbedBuilder() .setTitle(`Teams Found`) - .setColor('#0099ff'); + .setColor('#0099ff') + .setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); if (!teams || teams.length === 0) { - embed.setDescription('No teams found'); - return embed; + infoEmbed.setDescription('No teams found'); + return [infoEmbed]; } - // Sort teams by date created (newest first) - teams.sort((a, b) => new Date(b.date_created) - new Date(a.date_created)); + // Sort teams by game mode priority and date + const gameModeOrder = { + 'Squads': 1, + 'Duo': 2, + 'Solo': 3 + }; + + teams.sort((a, b) => { + const modeA = gameModeOrder[a.game_mode] || 999; + const modeB = gameModeOrder[b.game_mode] || 999; + if (modeA !== modeB) return modeA - modeB; + return new Date(b.date_created) - new Date(a.date_created); + }); teams.forEach((team, index) => { - // Add team header - embed.addFields({ - name: `${index + 1}. ${team.name} - ${team.game_name} (${team.game_mode})`, - value: '\u200B' + const modeIcon = team.game_mode === 'Squads' ? '๐ŸŽฎ' : + team.game_mode === 'Duo' ? '๐ŸŽฏ' : 'โš”๏ธ'; + + // Combine stats into a single line + const stats = []; + if (team.matches) stats.push(`M:${team.matches}`); + if (team.wins) stats.push(`W:${team.wins}`); + if (team.losses) stats.push(`L:${team.losses}`); + const winRate = team.matches > 0 ? + ((parseInt(team.wins) / parseInt(team.matches)) * 100).toFixed(1) : 0; + stats.push(`WR:${winRate}%`); + + // Create team header with stats + let teamInfo = `${modeIcon} **${team.name}** (${team.game_name} ${team.game_mode})\n`; + teamInfo += `๐Ÿ“Š ${stats.join(' | ')}`; + + // Add players if available + if (team.players?.length > 0) { + teamInfo += `\n๐Ÿ‘ฅ ${team.players.map(p => p.username).join(', ')}`; + } + + // Add logo link if available + if (team.logo) { + teamInfo += `\n๐ŸŽจ [View Logo](${team.logo})`; + } + + infoEmbed.addFields({ + name: `Team ${index + 1}`, + value: teamInfo }); - // Add team stats - const statsFields = []; - if (team.matches) statsFields.push(`Matches: ${team.matches}`); - if (team.wins) statsFields.push(`Wins: ${team.wins}`); - if (team.losses) statsFields.push(`Losses: ${team.losses}`); - if (team.forfeits) statsFields.push(`Forfeits: ${team.forfeits}`); - - const winRate = team.matches > 0 - ? ((parseInt(team.wins) / parseInt(team.matches)) * 100).toFixed(1) - : 0; - - if (statsFields.length > 0) { - embed.addFields({ - name: 'Stats', - value: `${statsFields.join(' | ')} | Win Rate: ${winRate}%`, - inline: false - }); - } - - // Add team members - if (team.players && team.players.length > 0) { - const playerList = team.players - .map(player => `- ${player.username} (${player.position})`) - .join('\n'); - - embed.addFields({ - name: 'Players', - value: playerList, - inline: false - }); - } - - // Add team creation date - if (team.date_created) { - embed.addFields({ - name: 'Created', - value: new Date(team.date_created).toLocaleDateString(), - inline: true - }); - } - - // Add separator between teams + // Add small separator if not the last team if (index < teams.length - 1) { - embed.addFields({ name: '\u200B', value: '\u200B' }); + infoEmbed.addFields({ name: '\u200B', value: 'โ–ฌโ–ฌโ–ฌโ–ฌโ–ฌโ–ฌโ–ฌโ–ฌโ–ฌโ–ฌ', inline: false }); } }); - return embed; + embeds.push(infoEmbed); + return embeds; } } From 584fba7aa039603fec1a744ab2a3366375ad27b2 Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:16:49 -0600 Subject: [PATCH 3/4] Update CommandHandler.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # VRBattles Bot Updates - March 2024 ## Command Improvements ### Find Team Command (`/findteam`) - ๐Ÿ”„ Restructured command to require game selection before team name - ๐Ÿ–ผ๏ธ Added VR Battles image as the main embed image - ๐Ÿงน Removed all buttons for cleaner display - ๐Ÿ“Š Implemented game mode sorting: - Priority order: Squads > Duo > Solo - Secondary sort by creation date (newest first) - ๐ŸŽจ Added game mode icons: - Squads: ๐ŸŽฎ - Duo: ๐ŸŽฏ - Solo: โš”๏ธ - ๐Ÿ“ Condensed stats display format: - M: matches - W: wins - L: losses - WR: win rate - ๐Ÿ‘ฅ Made all team search results visible to everyone in the channel ### Find User Command (`/finduser`) - ๐Ÿ”„ Restructured command to require game selection before username - ๐Ÿ–ผ๏ธ Added VR Battles image as the main embed image - ๐Ÿ” Enhanced user search results: - Shows basic profile info even when no game stats are found - Displays user's avatar, bio, level, XP, and other profile details - Indicates if user is on a team but hasn't played matches - ๐Ÿ”ต Simplified button layout: - Removed Discord invite button - Kept only the "View Profile" button - ๐Ÿ’ฌ Improved status messages: - Clear indication when user exists but hasn't played selected game - Shows team membership status for the selected game --- src/commands/CommandHandler.js | 69 +++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/commands/CommandHandler.js b/src/commands/CommandHandler.js index bbbf190..791f5cb 100644 --- a/src/commands/CommandHandler.js +++ b/src/commands/CommandHandler.js @@ -1,4 +1,4 @@ -const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); +const { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } = require('discord.js'); const EmbedBuilders = require('../utils/embedBuilders'); class CommandHandler { @@ -206,8 +206,44 @@ class CommandHandler { const teamMessage = isOnTeam ? `\nThey are on a team for ${gameFilter} but haven't played any matches yet.` : ''; + + // Create a basic embed with user info + const basicEmbed = new EmbedBuilder() + .setTitle(`User: ${playerData.username || 'Unknown'}`) + .setDescription(playerData.profile?.bio || 'No bio available') + .setColor('#0099ff') + .setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); + + // Add avatar if available + if (playerData.profile?.avatar) { + basicEmbed.setThumbnail( + `https://www.vrbattles.gg/assets/uploads/profile/${playerData.profile.avatar}` + ); + } + + // Add basic profile fields + const profileFields = []; + const profile = playerData.profile || {}; + if (profile.country) profileFields.push({ name: 'Country', value: profile.country, inline: true }); + if (profile.level) profileFields.push({ name: 'Level', value: profile.level.toString(), inline: true }); + if (profile.xp) profileFields.push({ name: 'Total XP', value: profile.xp.toString(), inline: true }); + if (profile.prestige) profileFields.push({ name: 'Prestige', value: profile.prestige.toString(), inline: true }); + + if (profileFields.length > 0) { + basicEmbed.addFields(profileFields); + } + + // Add message about no game stats + basicEmbed.addFields({ + name: `${gameFilter} Status`, + value: `โŒ No match history found for ${gameFilter}${teamMessage}` + }); + + const row = this.createActionRow(playerData.username); + await interaction.editReply({ - content: `โœ… User found, but they haven't played ${gameFilter} yet.${teamMessage}` + embeds: [basicEmbed], + components: [row] }); return; } @@ -281,21 +317,22 @@ class CommandHandler { // Input validation if (!teamName || typeof teamName !== 'string') { await interaction.editReply({ - content: 'โŒ Invalid team name provided.' + content: 'โŒ Invalid team name provided.', + ephemeral: false }); return; } - // Enhanced sanitization - only allow alphanumeric characters, spaces, and specific symbols - // Limit the length to prevent buffer overflow attacks + // Enhanced sanitization const sanitizedTeamName = teamName - .replace(/[^a-zA-Z0-9\s\-_.]/g, '') // Remove any characters that aren't alphanumeric, space, hyphen, underscore, or period + .replace(/[^a-zA-Z0-9\s\-_.]/g, '') .trim() - .slice(0, 100); // Limit length to 100 characters + .slice(0, 100); if (!sanitizedTeamName) { await interaction.editReply({ - content: 'โŒ Team name must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).' + content: 'โŒ Team name must contain valid characters (letters, numbers, spaces, hyphens, underscores, or periods).', + ephemeral: false }); return; } @@ -309,12 +346,12 @@ class CommandHandler { timestamp: new Date().toISOString() }); - // Game filter is pre-validated by Discord's slash command choices const teamData = await this.playerService.findTeamByName(sanitizedTeamName, gameFilter); if (!teamData || !teamData.success || !teamData.teams || teamData.teams.length === 0) { await interaction.editReply({ - content: 'โŒ No teams found matching your search criteria.' + content: 'โŒ No teams found matching your search criteria.', + ephemeral: false }); return; } @@ -323,7 +360,8 @@ class CommandHandler { await interaction.editReply({ embeds: embeds, - components: [] + components: [], + ephemeral: false }); } catch (error) { this.logger.error('Error in handleFindTeam:', { @@ -334,7 +372,8 @@ class CommandHandler { timestamp: new Date().toISOString() }); await interaction.editReply({ - content: 'โŒ An error occurred while searching for teams.' + content: 'โŒ An error occurred while searching for teams.', + ephemeral: false }); } } @@ -344,11 +383,7 @@ class CommandHandler { new ButtonBuilder() .setLabel('๐Ÿ”ต View Profile') .setStyle(ButtonStyle.Link) - .setURL(`https://www.vrbattles.gg/profile/${username}`), - new ButtonBuilder() - .setLabel('๐ŸŸก Join Main Discord') - .setStyle(ButtonStyle.Link) - .setURL('https://discord.gg/j3DKVATPGQ') + .setURL(`https://www.vrbattles.gg/profile/${username}`) ); } } From 93679f8828c804d81ed200c44f3a0f5d7d82a2f2 Mon Sep 17 00:00:00 2001 From: VinceC <33974776+VinceC3@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:22:29 -0600 Subject: [PATCH 4/4] summary small update for bot output --- src/commands/CommandHandler.js | 11 +++++++++-- src/utils/embedBuilders.js | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/commands/CommandHandler.js b/src/commands/CommandHandler.js index 791f5cb..85cf67c 100644 --- a/src/commands/CommandHandler.js +++ b/src/commands/CommandHandler.js @@ -211,8 +211,7 @@ class CommandHandler { const basicEmbed = new EmbedBuilder() .setTitle(`User: ${playerData.username || 'Unknown'}`) .setDescription(playerData.profile?.bio || 'No bio available') - .setColor('#0099ff') - .setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); + .setColor('#0099ff'); // Add avatar if available if (playerData.profile?.avatar) { @@ -221,6 +220,14 @@ class CommandHandler { ); } + // Add badge image if available + if (playerData.profile?.current_level_badge) { + const badgeImageUrl = this.playerService.getBadgeImageUrl(playerData.profile.current_level_badge); + basicEmbed.setImage(badgeImageUrl); + } else { + basicEmbed.setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); + } + // Add basic profile fields const profileFields = []; const profile = playerData.profile || {}; diff --git a/src/utils/embedBuilders.js b/src/utils/embedBuilders.js index 56f3398..09220e4 100644 --- a/src/utils/embedBuilders.js +++ b/src/utils/embedBuilders.js @@ -6,8 +6,7 @@ class EmbedBuilders { const embed = new EmbedBuilder() .setTitle(`User: ${playerData.username || 'Unknown'}`) .setDescription(profile.bio || 'No bio available') - .setColor('#0099ff') - .setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); + .setColor('#0099ff'); // Add thumbnail (avatar) if (profile.avatar) { @@ -16,6 +15,14 @@ class EmbedBuilders { ); } + // Add badge image if available + if (profile.current_level_badge) { + const badgeImageUrl = playerService.getBadgeImageUrl(profile.current_level_badge); + embed.setImage(badgeImageUrl); + } else { + embed.setImage('https://www.vrbattles.gg/assets/images/Qubi/vrwearcubesatad.png'); + } + // Add profile fields const profileFields = []; if (profile.country) profileFields.push({ name: 'Country', value: profile.country, inline: true });