November Update
A variety of bot updates, verify setup, and match request info
This commit is contained in:
86
src/Bot.js
86
src/Bot.js
@@ -1,8 +1,9 @@
|
||||
// src/bot.js
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
// src/Bot.js - Update your import statements
|
||||
const CommandHandler = require('./commands/CommandHandler.js'); // Add .js extension
|
||||
const PlayerService = require('./services/PlayerService.js'); // Add .js extension
|
||||
const CommandHandler = require('./commands/CommandHandler.js');
|
||||
const PlayerService = require('./services/PlayerService.js');
|
||||
const NotificationService = require('./services/NotificationService.js');
|
||||
const MatchCommands = require('./commands/MatchCommands.js');
|
||||
|
||||
class Bot {
|
||||
constructor() {
|
||||
this.client = new Client({
|
||||
@@ -15,6 +16,8 @@ class Bot {
|
||||
|
||||
this.playerService = new PlayerService();
|
||||
this.commandHandler = new CommandHandler(this.playerService);
|
||||
this.matchCommands = new MatchCommands(this.playerService);
|
||||
this.notificationService = new NotificationService(this);
|
||||
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
@@ -30,8 +33,25 @@ class Bot {
|
||||
try {
|
||||
await this.client.login(token);
|
||||
console.log('Login successful');
|
||||
|
||||
// Start notification service after bot is logged in
|
||||
const port = process.env.NOTIFICATION_PORT || 3000;
|
||||
await this.notificationService.start(port);
|
||||
console.log(`Notification service started on port ${port}`);
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
console.error('Startup failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
console.log('Stopping bot...');
|
||||
try {
|
||||
await this.notificationService.stop();
|
||||
await this.client.destroy();
|
||||
console.log('Bot stopped successfully');
|
||||
} catch (error) {
|
||||
console.error('Error stopping bot:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -45,8 +65,19 @@ class Bot {
|
||||
}
|
||||
|
||||
async handleInteraction(interaction) {
|
||||
if (!interaction.isCommand()) return;
|
||||
try {
|
||||
if (interaction.isCommand()) {
|
||||
await this.handleCommand(interaction);
|
||||
} else if (interaction.isButton()) {
|
||||
await this.handleButton(interaction);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling interaction:', error);
|
||||
this.handleInteractionError(interaction, error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCommand(interaction) {
|
||||
try {
|
||||
switch (interaction.commandName) {
|
||||
case 'ping':
|
||||
@@ -69,6 +100,29 @@ class Bot {
|
||||
}
|
||||
}
|
||||
|
||||
async handleButton(interaction) {
|
||||
const [action, matchId] = interaction.customId.split('_match_');
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'accept':
|
||||
await this.matchCommands.handleMatchAccept(interaction, matchId);
|
||||
break;
|
||||
case 'view':
|
||||
await this.matchCommands.handleViewDetails(interaction, matchId);
|
||||
break;
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: 'Unknown button action',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling button:', error);
|
||||
await this.handleInteractionError(interaction, error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCommandError(interaction, error) {
|
||||
console.error('Command error:', error);
|
||||
|
||||
@@ -88,6 +142,26 @@ class Bot {
|
||||
console.error('Error while sending error message:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async handleInteractionError(interaction, error) {
|
||||
console.error('Interaction error:', error);
|
||||
|
||||
try {
|
||||
if (!interaction.replied && !interaction.deferred) {
|
||||
await interaction.reply({
|
||||
content: 'An error occurred while processing your request.',
|
||||
ephemeral: true
|
||||
});
|
||||
} else if (interaction.deferred) {
|
||||
await interaction.editReply({
|
||||
content: 'An error occurred while processing your request.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error while sending error message:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bot;
|
||||
63
src/commands/MatchCommands.js
Normal file
63
src/commands/MatchCommands.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||
|
||||
class MatchCommands {
|
||||
constructor(playerService) {
|
||||
this.playerService = playerService;
|
||||
this.baseUrl = 'https://www.vrbattles.gg';
|
||||
}
|
||||
|
||||
async handleViewDetails(interaction, matchId) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
try {
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel('View on VRBattles')
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(`${this.baseUrl}/matches/${matchId}`),
|
||||
new ButtonBuilder()
|
||||
.setLabel('Join VRBattles Discord')
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL('https://discord.gg/j3DKVATPGQ')
|
||||
);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle('Match Details')
|
||||
.setDescription('Click below to view the match details on VRBattles')
|
||||
.addFields(
|
||||
{
|
||||
name: 'Match ID',
|
||||
value: `#${matchId}`,
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: 'VRBattles Match System' });
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [embed],
|
||||
components: [row],
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error handling view details:', error);
|
||||
await interaction.editReply({
|
||||
content: 'An error occurred. Please try viewing the match on the website directly.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Keeping this as a placeholder for future implementation
|
||||
async handleMatchAccept(interaction, matchId) {
|
||||
await interaction.reply({
|
||||
content: 'This feature is coming soon. Please view and manage matches on the VRBattles website.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MatchCommands;
|
||||
64
src/scripts/generateInvite.js
Normal file
64
src/scripts/generateInvite.js
Normal file
@@ -0,0 +1,64 @@
|
||||
require('dotenv').config();
|
||||
const { Client, GatewayIntentBits, PermissionsBitField } = require('discord.js');
|
||||
|
||||
async function generateInvite() {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await client.login(process.env.BOT_TOKEN);
|
||||
|
||||
const invite = client.generateInvite({
|
||||
scopes: ['bot', 'applications.commands'],
|
||||
permissions: [
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.EmbedLinks,
|
||||
PermissionsBitField.Flags.AttachFiles,
|
||||
PermissionsBitField.Flags.UseApplicationCommands,
|
||||
PermissionsBitField.Flags.ManageMessages,
|
||||
]
|
||||
});
|
||||
|
||||
console.log('\n=== Bot Invite Link Generator ===');
|
||||
console.log('\nCurrent Bot Status:');
|
||||
console.log(`Name: ${client.user.tag}`);
|
||||
console.log(`ID: ${client.user.id}`);
|
||||
|
||||
console.log('\nCurrent Servers:');
|
||||
if (client.guilds.cache.size === 0) {
|
||||
console.log('❌ Bot is not in any servers');
|
||||
} else {
|
||||
client.guilds.cache.forEach(guild => {
|
||||
console.log(`- ${guild.name} (ID: ${guild.id})`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n=== Invite Link ===');
|
||||
console.log('Use this link to invite the bot to your server:');
|
||||
console.log(invite);
|
||||
|
||||
console.log('\n=== Next Steps ===');
|
||||
console.log('1. Click the link above');
|
||||
console.log('2. Select your server from the dropdown');
|
||||
console.log('3. Keep all permissions checked');
|
||||
console.log('4. Click "Authorize"');
|
||||
console.log('\nAfter adding the bot:');
|
||||
console.log('1. Enable Developer Mode in Discord (User Settings > App Settings > Advanced)');
|
||||
console.log('2. Right-click your notification channel');
|
||||
console.log('3. Click "Copy ID"');
|
||||
console.log('4. Update your .env file with the new channel ID');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
client.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
generateInvite().catch(console.error);
|
||||
87
src/scripts/verifysetup.js
Normal file
87
src/scripts/verifysetup.js
Normal file
@@ -0,0 +1,87 @@
|
||||
require('dotenv').config();
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
async function verifySetup() {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await client.login(process.env.BOT_TOKEN);
|
||||
|
||||
console.log('\n=== Bot Setup Verification ===');
|
||||
console.log(`\nBot: ${client.user.tag}`);
|
||||
|
||||
// Check servers
|
||||
console.log('\nServer Check:');
|
||||
if (client.guilds.cache.size === 0) {
|
||||
console.log('❌ Bot is not in any servers. Please use the invite link first.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const guild of client.guilds.cache.values()) {
|
||||
console.log(`\nServer: ${guild.name}`);
|
||||
|
||||
// Check bot's role
|
||||
const botRole = guild.members.cache.get(client.user.id)?.roles.highest;
|
||||
console.log('Role:', botRole ? botRole.name : '❌ No role found');
|
||||
|
||||
// Check for notification channel
|
||||
const channel = guild.channels.cache.get(process.env.NOTIFICATION_CHANNEL_ID);
|
||||
if (channel) {
|
||||
console.log(`\n✅ Found notification channel: #${channel.name}`);
|
||||
|
||||
// Check permissions
|
||||
const botMember = guild.members.cache.get(client.user.id);
|
||||
const perms = channel.permissionsFor(botMember);
|
||||
|
||||
console.log('\nPermission Check:');
|
||||
console.log(`View Channel: ${perms.has('ViewChannel') ? '✅' : '❌'}`);
|
||||
console.log(`Send Messages: ${perms.has('SendMessages') ? '✅' : '❌'}`);
|
||||
console.log(`Embed Links: ${perms.has('EmbedLinks') ? '✅' : '❌'}`);
|
||||
console.log(`Attach Files: ${perms.has('AttachFiles') ? '✅' : '❌'}`);
|
||||
|
||||
// Test message
|
||||
console.log('\nTesting channel access...');
|
||||
try {
|
||||
const msg = await channel.send('🔍 Testing bot setup...');
|
||||
await msg.delete();
|
||||
console.log('✅ Successfully sent and deleted test message');
|
||||
} catch (error) {
|
||||
console.log('❌ Failed to send test message:', error.message);
|
||||
}
|
||||
} else {
|
||||
console.log('\n❌ Notification channel not found in this server');
|
||||
console.log('Channel ID being checked:', process.env.NOTIFICATION_CHANNEL_ID);
|
||||
}
|
||||
}
|
||||
|
||||
const channelFound = Array.from(client.guilds.cache.values()).some(
|
||||
guild => guild.channels.cache.has(process.env.NOTIFICATION_CHANNEL_ID)
|
||||
);
|
||||
|
||||
if (!channelFound) {
|
||||
console.log('\n❌ The channel ID in your .env file was not found in any server');
|
||||
console.log('Current channel ID:', process.env.NOTIFICATION_CHANNEL_ID);
|
||||
console.log('\nTo fix this:');
|
||||
console.log('1. Right-click your desired notification channel');
|
||||
console.log('2. Click "Copy ID" (Enable Developer Mode if you don\'t see this)');
|
||||
console.log('3. Update NOTIFICATION_CHANNEL_ID in your .env file');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
client.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Call the async function properly
|
||||
verifySetup().catch(error => {
|
||||
console.error('Setup verification failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
98
src/services/NotificationService.js
Normal file
98
src/services/NotificationService.js
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
// src/services/NotificationService.js
|
||||
const express = require('express');
|
||||
const { createMatchRequestEmbed } = require('../utils/embedBuilders');
|
||||
const { createMatchActionRow } = require('../utils/componentBuilders');
|
||||
|
||||
class NotificationService {
|
||||
constructor(bot) {
|
||||
this.bot = bot;
|
||||
this.app = express();
|
||||
this.app.use(express.json());
|
||||
this.setupRoutes();
|
||||
}
|
||||
|
||||
setupRoutes() {
|
||||
this.app.post('/api/match-notification', this.handleMatchNotification.bind(this));
|
||||
}
|
||||
|
||||
async start(port = 3000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.server = this.app.listen(port, () => {
|
||||
console.log(`Notification service listening on port ${port}`);
|
||||
resolve();
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.server) {
|
||||
return new Promise((resolve) => {
|
||||
this.server.close(resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleMatchNotification(req, res) {
|
||||
try {
|
||||
const authToken = req.headers['x-webhook-token'];
|
||||
if (authToken !== process.env.WEBHOOK_SECRET) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const matchData = req.body;
|
||||
if (!this.validateMatchData(matchData)) {
|
||||
return res.status(400).json({ error: 'Invalid match data' });
|
||||
}
|
||||
|
||||
const notificationChannelId = process.env.NOTIFICATION_CHANNEL_ID;
|
||||
if (!notificationChannelId) {
|
||||
throw new Error('Notification channel ID not configured');
|
||||
}
|
||||
|
||||
try {
|
||||
const channel = await this.bot.client.channels.fetch(notificationChannelId);
|
||||
const embed = createMatchRequestEmbed(matchData);
|
||||
const actionRow = createMatchActionRow(matchData.game_id);
|
||||
|
||||
await channel.send({
|
||||
embeds: [embed],
|
||||
components: [actionRow]
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error fetching or sending to notification channel:', error);
|
||||
throw new Error('Failed to send match notification');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling match notification:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
validateMatchData(matchData) {
|
||||
if (matchData.type !== 'match_request') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const requiredFields = [
|
||||
'game_name',
|
||||
'game_id',
|
||||
'team_size',
|
||||
'match_type',
|
||||
'region',
|
||||
'match_date',
|
||||
'match_class',
|
||||
'status'
|
||||
];
|
||||
|
||||
return requiredFields.every(field => matchData[field] !== undefined);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotificationService;
|
||||
53
src/tests/checkServer.js
Normal file
53
src/tests/checkServer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
require('dotenv').config();
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
async function checkServerAccess() {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await client.login(process.env.BOT_TOKEN);
|
||||
console.log('\nBot Information:');
|
||||
console.log(`Bot Name: ${client.user.tag}`);
|
||||
|
||||
// List all servers the bot is in
|
||||
console.log('\nServers the bot is in:');
|
||||
client.guilds.cache.forEach(guild => {
|
||||
console.log(`- ${guild.name} (ID: ${guild.id})`);
|
||||
|
||||
// Check if the notification channel is in this server
|
||||
const channel = guild.channels.cache.get(process.env.NOTIFICATION_CHANNEL_ID);
|
||||
if (channel) {
|
||||
console.log(` ✅ Found notification channel: #${channel.name}`);
|
||||
|
||||
// Check bot's permissions in this channel
|
||||
const botMember = guild.members.cache.get(client.user.id);
|
||||
const permissions = channel.permissionsFor(botMember);
|
||||
|
||||
console.log('\nPermissions in notification channel:');
|
||||
console.log(` View Channel: ${permissions.has('ViewChannel') ? '✅' : '❌'}`);
|
||||
console.log(` Send Messages: ${permissions.has('SendMessages') ? '✅' : '❌'}`);
|
||||
console.log(` Embed Links: ${permissions.has('EmbedLinks') ? '✅' : '❌'}`);
|
||||
}
|
||||
});
|
||||
|
||||
// If notification channel wasn't found
|
||||
if (!client.guilds.cache.some(guild =>
|
||||
guild.channels.cache.has(process.env.NOTIFICATION_CHANNEL_ID))) {
|
||||
console.log('\n❌ Notification channel not found in any server!');
|
||||
console.log('Channel ID:', process.env.NOTIFICATION_CHANNEL_ID);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
client.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
checkServerAccess().catch(console.error);
|
||||
20
src/utils/componentBuilders.js
Normal file
20
src/utils/componentBuilders.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// src/utils/componentBuilders.js
|
||||
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
||||
|
||||
function createMatchActionRow(matchId) {
|
||||
return new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel('View Match Details')
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(`https://www.vrbattles.gg/matches/${matchId}`),
|
||||
new ButtonBuilder()
|
||||
.setLabel('Join VRBattles Discord')
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL('https://discord.gg/j3DKVATPGQ')
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMatchActionRow
|
||||
};
|
||||
43
src/utils/embedBuilders.js
Normal file
43
src/utils/embedBuilders.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// src/utils/embedBuilders.js
|
||||
const { EmbedBuilder } = require('discord.js');
|
||||
|
||||
function createMatchRequestEmbed(matchData) {
|
||||
const matchDateTime = new Date(matchData.match_date);
|
||||
const formattedDateTime = matchDateTime.toLocaleString();
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setColor('#00ff00')
|
||||
.setTitle('🎮 New Match Request')
|
||||
.setDescription(`A new match has been requested for ${matchData.game_name}`)
|
||||
.addFields(
|
||||
{
|
||||
name: 'Game Details',
|
||||
value: [
|
||||
`🎮 **Game:** ${matchData.game_name}`,
|
||||
`📋 **Match Type:** ${matchData.match_type}`,
|
||||
`👥 **Team Size:** ${matchData.team_size}v${matchData.team_size}`,
|
||||
].join('\n'),
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: 'Match Settings',
|
||||
value: [
|
||||
`🌍 **Region:** ${matchData.region}`,
|
||||
`🎯 **Class:** ${matchData.match_class}`,
|
||||
`📅 **Date & Time:** ${formattedDateTime}`,
|
||||
].join('\n'),
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: 'Status',
|
||||
value: `📊 **Current Status:** ${matchData.status}`,
|
||||
inline: false
|
||||
}
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `Match Request ID: ${matchData.game_id}` });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMatchRequestEmbed
|
||||
};
|
||||
Reference in New Issue
Block a user