312 lines
8.6 KiB
JavaScript
312 lines
8.6 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test Bot for spotlight.cam
|
|||
|
|
*
|
|||
|
|
* Usage:
|
|||
|
|
* # From inside backend container:
|
|||
|
|
* docker compose exec backend sh -c 'API_URL=http://localhost:3000 node scripts/test-bot.js --email bot@example.com --password Bot123! --slug test-event-2024'
|
|||
|
|
*
|
|||
|
|
* # From host (if backend dependencies are installed):
|
|||
|
|
* cd backend && API_URL=http://localhost:3001 node scripts/test-bot.js --email bot@example.com --password Bot123! --slug test-event-2024
|
|||
|
|
*
|
|||
|
|
* Options:
|
|||
|
|
* --email Bot user email
|
|||
|
|
* --password Bot user password
|
|||
|
|
* --slug Event slug to join
|
|||
|
|
* --interval Message interval in seconds (default: 15)
|
|||
|
|
*
|
|||
|
|
* Environment:
|
|||
|
|
* API_URL Backend API URL (default: http://localhost:3001)
|
|||
|
|
* Use http://localhost:3000 when running inside container
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const io = require('socket.io-client');
|
|||
|
|
|
|||
|
|
// Parse CLI arguments
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
const getArg = (flag) => {
|
|||
|
|
const index = args.indexOf(flag);
|
|||
|
|
return index !== -1 ? args[index + 1] : null;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const email = getArg('--email');
|
|||
|
|
const password = getArg('--password');
|
|||
|
|
const slug = getArg('--slug');
|
|||
|
|
const interval = parseInt(getArg('--interval')) || 15;
|
|||
|
|
|
|||
|
|
if (!email || !password || !slug) {
|
|||
|
|
console.error('❌ Missing required arguments!');
|
|||
|
|
console.log('\nUsage:');
|
|||
|
|
console.log(' node backend/scripts/test-bot.js --email <email> --password <password> --slug <event-slug>');
|
|||
|
|
console.log('\nOptions:');
|
|||
|
|
console.log(' --interval <seconds> Message interval (default: 15)');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const API_URL = process.env.API_URL || 'http://localhost:3001';
|
|||
|
|
|
|||
|
|
// Random messages pool
|
|||
|
|
const RANDOM_MESSAGES = [
|
|||
|
|
'Hello everyone! 👋',
|
|||
|
|
'Great to be here!',
|
|||
|
|
'Anyone else excited for this event?',
|
|||
|
|
'This is going to be awesome!',
|
|||
|
|
'Can\'t wait to dance! 💃',
|
|||
|
|
'Hey there!',
|
|||
|
|
'Looking forward to this!',
|
|||
|
|
'Who else is competing?',
|
|||
|
|
'Good luck everyone!',
|
|||
|
|
'Let\'s do this! 🔥',
|
|||
|
|
'Anyone want to practice later?',
|
|||
|
|
'What division are you in?',
|
|||
|
|
'First time at this event?',
|
|||
|
|
'The energy here is amazing!',
|
|||
|
|
'See you on the dance floor!',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let token = null;
|
|||
|
|
let socket = null;
|
|||
|
|
let userId = null;
|
|||
|
|
let eventId = null;
|
|||
|
|
let messageInterval = null;
|
|||
|
|
|
|||
|
|
// Login to get JWT token
|
|||
|
|
async function login() {
|
|||
|
|
try {
|
|||
|
|
console.log(`🔐 Logging in as ${email}...`);
|
|||
|
|
const response = await fetch(`${API_URL}/api/auth/login`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({ email, password }),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
const errorData = await response.json();
|
|||
|
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const responseData = await response.json();
|
|||
|
|
token = responseData.data.token;
|
|||
|
|
userId = responseData.data.user.id;
|
|||
|
|
console.log(`✅ Logged in! User ID: ${userId}`);
|
|||
|
|
console.log(` Token: ${token.substring(0, 20)}...`);
|
|||
|
|
return token;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Login failed:', error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Join event (check-in using token)
|
|||
|
|
async function joinEvent() {
|
|||
|
|
try {
|
|||
|
|
console.log(`📍 Getting event details for: ${slug}...`);
|
|||
|
|
|
|||
|
|
// Get event details which includes the check-in token
|
|||
|
|
const detailsResponse = await fetch(`${API_URL}/api/events/${slug}/details`, {
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${token}`,
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!detailsResponse.ok) {
|
|||
|
|
const errorData = await detailsResponse.json();
|
|||
|
|
throw new Error(errorData.error || `HTTP error! status: ${detailsResponse.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const detailsData = await detailsResponse.json();
|
|||
|
|
eventId = detailsData.data.event.id;
|
|||
|
|
const checkinToken = detailsData.data.checkin?.token;
|
|||
|
|
|
|||
|
|
if (!checkinToken) {
|
|||
|
|
throw new Error('No check-in token available for this event');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(` Event ID: ${eventId}`);
|
|||
|
|
console.log(`📍 Checking in to event...`);
|
|||
|
|
|
|||
|
|
// Check in to the event using the token
|
|||
|
|
const checkinResponse = await fetch(`${API_URL}/api/events/checkin/${checkinToken}`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${token}`,
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({}),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!checkinResponse.ok) {
|
|||
|
|
const errorData = await checkinResponse.json();
|
|||
|
|
|
|||
|
|
// If already checked in, that's fine
|
|||
|
|
if (errorData.alreadyCheckedIn) {
|
|||
|
|
console.log(`ℹ️ Already checked in to this event`);
|
|||
|
|
return eventId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new Error(errorData.error || `HTTP error! status: ${checkinResponse.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const checkinData = await checkinResponse.json();
|
|||
|
|
if (checkinData.alreadyCheckedIn) {
|
|||
|
|
console.log(`ℹ️ Already checked in to this event`);
|
|||
|
|
} else {
|
|||
|
|
console.log(`✅ Checked in to event!`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return eventId;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Join event failed:', error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Connect to Socket.IO
|
|||
|
|
function connectSocket() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
console.log(`🔌 Connecting to Socket.IO...`);
|
|||
|
|
|
|||
|
|
socket = io(API_URL, {
|
|||
|
|
auth: { token },
|
|||
|
|
transports: ['websocket', 'polling'],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
socket.on('connect', () => {
|
|||
|
|
console.log(`✅ Socket connected! Socket ID: ${socket.id}`);
|
|||
|
|
|
|||
|
|
// Join event room
|
|||
|
|
console.log(`🚪 Joining event room for: ${slug}...`);
|
|||
|
|
socket.emit('join_event_room', { slug });
|
|||
|
|
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
socket.on('connect_error', (error) => {
|
|||
|
|
console.error('❌ Socket connection error:', error.message);
|
|||
|
|
reject(error);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
socket.on('disconnect', (reason) => {
|
|||
|
|
console.log(`⚠️ Socket disconnected: ${reason}`);
|
|||
|
|
if (reason === 'io server disconnect') {
|
|||
|
|
// Server disconnected, try to reconnect
|
|||
|
|
socket.connect();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Listen for messages
|
|||
|
|
socket.on('new_message', (data) => {
|
|||
|
|
if (data.userId !== userId) {
|
|||
|
|
console.log(`💬 Message from ${data.username}: ${data.content}`);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Listen for recording suggestions
|
|||
|
|
socket.on('suggestion_created', async (data) => {
|
|||
|
|
console.log(`📹 Recording suggestion received:`, data);
|
|||
|
|
|
|||
|
|
// Auto-accept if it's for us (we're the recorder)
|
|||
|
|
if (data.suggestion && data.suggestion.recorderId === userId) {
|
|||
|
|
console.log(` Auto-accepting suggestion ${data.suggestion.id}...`);
|
|||
|
|
await acceptSuggestion(data.suggestion.id);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
socket.on('error', (error) => {
|
|||
|
|
console.error('❌ Socket error:', error);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Send random message
|
|||
|
|
function sendRandomMessage() {
|
|||
|
|
const message = RANDOM_MESSAGES[Math.floor(Math.random() * RANDOM_MESSAGES.length)];
|
|||
|
|
|
|||
|
|
console.log(`📤 Sending: "${message}"`);
|
|||
|
|
socket.emit('send_event_message', {
|
|||
|
|
content: message,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Accept recording suggestion
|
|||
|
|
async function acceptSuggestion(suggestionId) {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(
|
|||
|
|
`${API_URL}/api/events/${slug}/suggestions/${suggestionId}/status`,
|
|||
|
|
{
|
|||
|
|
method: 'PUT',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${token}`,
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({ status: 'accepted' }),
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
const errorData = await response.json();
|
|||
|
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`✅ Accepted suggestion ${suggestionId}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`❌ Failed to accept suggestion:`, error.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Main function
|
|||
|
|
async function main() {
|
|||
|
|
console.log('🤖 Test Bot Starting...\n');
|
|||
|
|
console.log(` Email: ${email}`);
|
|||
|
|
console.log(` Event: ${slug}`);
|
|||
|
|
console.log(` Message Interval: ${interval}s\n`);
|
|||
|
|
|
|||
|
|
// Login
|
|||
|
|
await login();
|
|||
|
|
|
|||
|
|
// Join event
|
|||
|
|
await joinEvent();
|
|||
|
|
|
|||
|
|
// Connect socket
|
|||
|
|
await connectSocket();
|
|||
|
|
|
|||
|
|
console.log(`\n✅ Bot is ready!`);
|
|||
|
|
console.log(` Sending random messages every ${interval} seconds`);
|
|||
|
|
console.log(` Auto-accepting recording suggestions`);
|
|||
|
|
console.log(` Press Ctrl+C to stop\n`);
|
|||
|
|
|
|||
|
|
// Start sending random messages
|
|||
|
|
messageInterval = setInterval(() => {
|
|||
|
|
sendRandomMessage();
|
|||
|
|
}, interval * 1000);
|
|||
|
|
|
|||
|
|
// Send first message immediately
|
|||
|
|
setTimeout(() => sendRandomMessage(), 2000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Handle graceful shutdown
|
|||
|
|
process.on('SIGINT', () => {
|
|||
|
|
console.log('\n\n🛑 Shutting down bot...');
|
|||
|
|
|
|||
|
|
if (messageInterval) {
|
|||
|
|
clearInterval(messageInterval);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (socket) {
|
|||
|
|
socket.disconnect();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('👋 Goodbye!');
|
|||
|
|
process.exit(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Run
|
|||
|
|
main().catch((error) => {
|
|||
|
|
console.error('❌ Fatal error:', error);
|
|||
|
|
process.exit(1);
|
|||
|
|
});
|