Initial commit - realms platform
This commit is contained in:
parent
c590ab6d18
commit
c717c3751c
234 changed files with 74103 additions and 15231 deletions
93
bot-sdk/examples/echo-bot.js
Normal file
93
bot-sdk/examples/echo-bot.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Echo Bot Example
|
||||
*
|
||||
* A simple bot that echoes back messages that start with "!echo"
|
||||
*
|
||||
* Usage:
|
||||
* node echo-bot.js <api-key> <server-url> <realm-id>
|
||||
*
|
||||
* Example:
|
||||
* node echo-bot.js abc123 wss://example.com/chat/ws my-realm
|
||||
*/
|
||||
|
||||
import ChatBot from '../index.js';
|
||||
|
||||
// Get arguments
|
||||
const [,, apiKey, serverUrl, realmId] = process.argv;
|
||||
|
||||
if (!apiKey || !serverUrl || !realmId) {
|
||||
console.log('Usage: node echo-bot.js <api-key> <server-url> <realm-id>');
|
||||
console.log('Example: node echo-bot.js abc123 wss://example.com/chat/ws my-realm');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create the bot
|
||||
const bot = new ChatBot('EchoBot');
|
||||
|
||||
// Set up message handler
|
||||
bot.messageHandler = (message) => {
|
||||
console.log(`[${message.username}]: ${message.content}`);
|
||||
|
||||
// Echo back messages that start with !echo
|
||||
if (message.content.startsWith('!echo ')) {
|
||||
const echoText = message.content.slice(6);
|
||||
bot.print(echoText);
|
||||
}
|
||||
|
||||
// Respond to !hello
|
||||
if (message.content === '!hello') {
|
||||
bot.print(`Hello, ${message.username}!`);
|
||||
}
|
||||
|
||||
// Show help
|
||||
if (message.content === '!help') {
|
||||
bot.print('Commands: !echo <text>, !hello, !help');
|
||||
}
|
||||
};
|
||||
|
||||
// Set up event handlers
|
||||
bot.onConnect = () => {
|
||||
console.log('Bot connected!');
|
||||
};
|
||||
|
||||
bot.onJoin = (room) => {
|
||||
console.log(`Bot joined room: ${room}`);
|
||||
bot.print('EchoBot is online! Type !help for commands.');
|
||||
};
|
||||
|
||||
bot.onParticipantJoin = (participant) => {
|
||||
console.log(`${participant.username} joined the chat`);
|
||||
};
|
||||
|
||||
bot.onParticipantLeave = (participant) => {
|
||||
console.log(`${participant.username} left the chat`);
|
||||
};
|
||||
|
||||
bot.onError = (error) => {
|
||||
console.error('Bot error:', error.message);
|
||||
};
|
||||
|
||||
// Connect and join
|
||||
async function main() {
|
||||
try {
|
||||
console.log(`Connecting to ${serverUrl}...`);
|
||||
await bot.connect(apiKey, serverUrl);
|
||||
|
||||
console.log(`Joining room: ${realmId}...`);
|
||||
await bot.joinRoom(realmId);
|
||||
|
||||
console.log('Bot is running. Press Ctrl+C to stop.');
|
||||
} catch (error) {
|
||||
console.error('Failed to start bot:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting down bot...');
|
||||
bot.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
main();
|
||||
221
bot-sdk/examples/trivia-bot.js
Normal file
221
bot-sdk/examples/trivia-bot.js
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Trivia Bot Example
|
||||
*
|
||||
* A bot that runs trivia games in chat
|
||||
*
|
||||
* Usage:
|
||||
* node trivia-bot.js <api-key> <server-url> <realm-id>
|
||||
*
|
||||
* Commands:
|
||||
* !trivia start - Start a new trivia game
|
||||
* !trivia stop - Stop the current game
|
||||
* !trivia score - Show current scores
|
||||
* !answer <answer> - Submit an answer
|
||||
*/
|
||||
|
||||
import ChatBot from '../index.js';
|
||||
|
||||
// Get arguments
|
||||
const [,, apiKey, serverUrl, realmId] = process.argv;
|
||||
|
||||
if (!apiKey || !serverUrl || !realmId) {
|
||||
console.log('Usage: node trivia-bot.js <api-key> <server-url> <realm-id>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Trivia questions
|
||||
const questions = [
|
||||
{ question: "What planet is known as the Red Planet?", answer: "mars" },
|
||||
{ question: "What is the largest mammal in the world?", answer: "blue whale" },
|
||||
{ question: "In what year did the Titanic sink?", answer: "1912" },
|
||||
{ question: "What is the chemical symbol for gold?", answer: "au" },
|
||||
{ question: "Who painted the Mona Lisa?", answer: "leonardo da vinci" },
|
||||
{ question: "What is the capital of Japan?", answer: "tokyo" },
|
||||
{ question: "How many sides does a hexagon have?", answer: "6" },
|
||||
{ question: "What is the smallest prime number?", answer: "2" },
|
||||
{ question: "What element does 'O' represent on the periodic table?", answer: "oxygen" },
|
||||
{ question: "In what year did World War II end?", answer: "1945" },
|
||||
{ question: "What is the largest organ in the human body?", answer: "skin" },
|
||||
{ question: "Who wrote Romeo and Juliet?", answer: "shakespeare" },
|
||||
{ question: "What is the speed of light in km/s (approximately)?", answer: "300000" },
|
||||
{ question: "What is the tallest mountain in the world?", answer: "everest" },
|
||||
{ question: "What gas do plants absorb from the atmosphere?", answer: "carbon dioxide" }
|
||||
];
|
||||
|
||||
// Game state
|
||||
let gameActive = false;
|
||||
let currentQuestion = null;
|
||||
let questionIndex = 0;
|
||||
let scores = {};
|
||||
let questionTimeout = null;
|
||||
let usedQuestions = [];
|
||||
|
||||
// Create the bot
|
||||
const bot = new ChatBot('TriviaBot');
|
||||
|
||||
function shuffleArray(array) {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
function getNextQuestion() {
|
||||
if (usedQuestions.length === questions.length) {
|
||||
usedQuestions = [];
|
||||
}
|
||||
|
||||
const available = questions.filter((_, i) => !usedQuestions.includes(i));
|
||||
const randomIndex = Math.floor(Math.random() * available.length);
|
||||
const originalIndex = questions.indexOf(available[randomIndex]);
|
||||
usedQuestions.push(originalIndex);
|
||||
|
||||
return available[randomIndex];
|
||||
}
|
||||
|
||||
function askQuestion() {
|
||||
if (!gameActive) return;
|
||||
|
||||
currentQuestion = getNextQuestion();
|
||||
questionIndex++;
|
||||
|
||||
bot.print(`Question #${questionIndex}: ${currentQuestion.question}`);
|
||||
|
||||
// Set timeout for unanswered question
|
||||
questionTimeout = setTimeout(() => {
|
||||
if (currentQuestion) {
|
||||
bot.print(`Time's up! The answer was: ${currentQuestion.answer}`);
|
||||
currentQuestion = null;
|
||||
|
||||
// Ask next question after delay
|
||||
setTimeout(() => {
|
||||
if (gameActive) askQuestion();
|
||||
}, 3000);
|
||||
}
|
||||
}, 30000); // 30 seconds to answer
|
||||
}
|
||||
|
||||
function checkAnswer(username, answer) {
|
||||
if (!currentQuestion) return;
|
||||
|
||||
const normalizedAnswer = answer.toLowerCase().trim();
|
||||
const correctAnswer = currentQuestion.answer.toLowerCase().trim();
|
||||
|
||||
if (normalizedAnswer === correctAnswer || normalizedAnswer.includes(correctAnswer) || correctAnswer.includes(normalizedAnswer)) {
|
||||
clearTimeout(questionTimeout);
|
||||
|
||||
// Award points
|
||||
if (!scores[username]) scores[username] = 0;
|
||||
scores[username]++;
|
||||
|
||||
bot.print(`Correct! ${username} got it right! (+1 point, total: ${scores[username]})`);
|
||||
currentQuestion = null;
|
||||
|
||||
// Ask next question after delay
|
||||
setTimeout(() => {
|
||||
if (gameActive) askQuestion();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showScores() {
|
||||
const sortedScores = Object.entries(scores)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.slice(0, 10);
|
||||
|
||||
if (sortedScores.length === 0) {
|
||||
bot.print('No scores yet!');
|
||||
return;
|
||||
}
|
||||
|
||||
let scoreText = 'Leaderboard: ';
|
||||
sortedScores.forEach(([user, score], i) => {
|
||||
scoreText += `${i + 1}. ${user}: ${score} pts | `;
|
||||
});
|
||||
|
||||
bot.print(scoreText.slice(0, -3));
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
if (gameActive) {
|
||||
bot.print('A game is already in progress!');
|
||||
return;
|
||||
}
|
||||
|
||||
gameActive = true;
|
||||
questionIndex = 0;
|
||||
bot.print('Trivia game starting! Answer with "!answer <your answer>". You have 30 seconds per question.');
|
||||
|
||||
setTimeout(() => {
|
||||
askQuestion();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function stopGame() {
|
||||
if (!gameActive) {
|
||||
bot.print('No game is currently running.');
|
||||
return;
|
||||
}
|
||||
|
||||
gameActive = false;
|
||||
currentQuestion = null;
|
||||
clearTimeout(questionTimeout);
|
||||
|
||||
bot.print('Trivia game stopped!');
|
||||
showScores();
|
||||
}
|
||||
|
||||
// Set up message handler
|
||||
bot.messageHandler = (message) => {
|
||||
const content = message.content.trim().toLowerCase();
|
||||
|
||||
if (content === '!trivia start') {
|
||||
startGame();
|
||||
} else if (content === '!trivia stop') {
|
||||
stopGame();
|
||||
} else if (content === '!trivia score' || content === '!trivia scores') {
|
||||
showScores();
|
||||
} else if (content === '!trivia help') {
|
||||
bot.print('Commands: !trivia start, !trivia stop, !trivia score, !answer <answer>');
|
||||
} else if (content.startsWith('!answer ')) {
|
||||
const answer = message.content.slice(8);
|
||||
checkAnswer(message.username, answer);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up event handlers
|
||||
bot.onJoin = () => {
|
||||
bot.print('TriviaBot is online! Type "!trivia start" to begin a game.');
|
||||
};
|
||||
|
||||
bot.onError = (error) => {
|
||||
console.error('Bot error:', error.message);
|
||||
};
|
||||
|
||||
// Connect and join
|
||||
async function main() {
|
||||
try {
|
||||
console.log(`Connecting to ${serverUrl}...`);
|
||||
await bot.connect(apiKey, serverUrl);
|
||||
|
||||
console.log(`Joining room: ${realmId}...`);
|
||||
await bot.joinRoom(realmId);
|
||||
|
||||
console.log('TriviaBot is running. Press Ctrl+C to stop.');
|
||||
} catch (error) {
|
||||
console.error('Failed to start bot:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting down bot...');
|
||||
if (gameActive) stopGame();
|
||||
bot.disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
main();
|
||||
369
bot-sdk/index.js
Normal file
369
bot-sdk/index.js
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
import WebSocket from 'ws';
|
||||
|
||||
/**
|
||||
* ChatBot - A simple bot SDK for Realms chat
|
||||
*
|
||||
* Mirrors the existing XMPP bot API for easy migration.
|
||||
*
|
||||
* @example
|
||||
* const bot = new ChatBot('MyBot');
|
||||
*
|
||||
* bot.messageHandler = (message) => {
|
||||
* console.log(`${message.username}: ${message.content}`);
|
||||
* if (message.content === '!hello') {
|
||||
* bot.print('Hello there!');
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* bot.connect('your-api-key', 'wss://example.com/chat/ws')
|
||||
* .then(() => bot.joinRoom('realm-id'))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
class ChatBot {
|
||||
/**
|
||||
* Create a new ChatBot instance
|
||||
* @param {string} name - Display name for the bot (informational only, actual name comes from API key owner)
|
||||
*/
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.ws = null;
|
||||
this.apiKey = null;
|
||||
this.serverUrl = null;
|
||||
this.realmId = null;
|
||||
this.connected = false;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 5;
|
||||
this.reconnectDelay = 1000;
|
||||
this.shouldReconnect = true;
|
||||
|
||||
// Event handlers
|
||||
this.messageHandler = null;
|
||||
this.onConnect = null;
|
||||
this.onDisconnect = null;
|
||||
this.onError = null;
|
||||
this.onJoin = null;
|
||||
this.onParticipantJoin = null;
|
||||
this.onParticipantLeave = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the chat server
|
||||
* @param {string} apiKey - Your bot API key from the Settings page
|
||||
* @param {string} serverUrl - WebSocket server URL (e.g., 'wss://example.com/chat/ws')
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
connect(apiKey, serverUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.apiKey = apiKey;
|
||||
this.serverUrl = serverUrl;
|
||||
this.shouldReconnect = true;
|
||||
|
||||
// SECURITY FIX: Don't put API key in URL (it gets logged by servers/proxies)
|
||||
// Instead, send auth message after connection opens
|
||||
this.ws = new WebSocket(serverUrl);
|
||||
|
||||
// Store resolve/reject for when we get the welcome message
|
||||
this._connectResolve = resolve;
|
||||
this._connectReject = reject;
|
||||
|
||||
this.ws.on('open', () => {
|
||||
console.log(`[${this.name}] Connected to server, authenticating...`);
|
||||
// SECURITY FIX: Send API key via message instead of URL
|
||||
this._send({ type: 'auth', apiKey: apiKey });
|
||||
// Don't resolve yet - wait for welcome message after API key validation
|
||||
});
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
this._handleMessage(message);
|
||||
} catch (e) {
|
||||
console.error(`[${this.name}] Failed to parse message:`, e);
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on('close', () => {
|
||||
console.log(`[${this.name}] Connection closed`);
|
||||
this.connected = false;
|
||||
|
||||
if (this.onDisconnect) {
|
||||
this.onDisconnect();
|
||||
}
|
||||
|
||||
if (this.shouldReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this._attemptReconnect();
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on('error', (error) => {
|
||||
console.error(`[${this.name}] WebSocket error:`, error.message);
|
||||
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
}
|
||||
|
||||
if (this._connectReject) {
|
||||
this._connectReject(error);
|
||||
this._connectResolve = null;
|
||||
this._connectReject = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Timeout for connection/authentication
|
||||
setTimeout(() => {
|
||||
if (this._connectResolve) {
|
||||
this._connectReject(new Error('Connection timeout - no welcome message received'));
|
||||
this._connectResolve = null;
|
||||
this._connectReject = null;
|
||||
}
|
||||
}, 15000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the chat server
|
||||
*/
|
||||
disconnect() {
|
||||
this.shouldReconnect = false;
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.connected = false;
|
||||
console.log(`[${this.name}] Disconnected`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a chat room (realm)
|
||||
* @param {string} realmId - The realm ID to join
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
joinRoom(realmId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected) {
|
||||
reject(new Error('Not connected to server'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.realmId = realmId;
|
||||
|
||||
// Store resolve/reject for when we get the join response
|
||||
this._joinResolve = resolve;
|
||||
this._joinReject = reject;
|
||||
|
||||
this._send({
|
||||
type: 'join',
|
||||
realmId: realmId
|
||||
});
|
||||
|
||||
// Timeout after 10 seconds
|
||||
setTimeout(() => {
|
||||
if (this._joinResolve) {
|
||||
this._joinReject(new Error('Join timeout'));
|
||||
this._joinResolve = null;
|
||||
this._joinReject = null;
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the current room
|
||||
* @param {string} message - The message to send
|
||||
*/
|
||||
print(message) {
|
||||
if (!this.connected || !this.realmId) {
|
||||
console.error(`[${this.name}] Cannot send message: not connected or not in a room`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._send({
|
||||
type: 'message',
|
||||
content: message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of participants in the current room
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
getParticipants() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected || !this.realmId) {
|
||||
reject(new Error('Not connected or not in a room'));
|
||||
return;
|
||||
}
|
||||
|
||||
this._participantsResolve = resolve;
|
||||
this._participantsReject = reject;
|
||||
|
||||
this._send({
|
||||
type: 'get_participants'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (this._participantsResolve) {
|
||||
this._participantsReject(new Error('Get participants timeout'));
|
||||
this._participantsResolve = null;
|
||||
this._participantsReject = null;
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal: Send a message over WebSocket
|
||||
* @private
|
||||
*/
|
||||
_send(data) {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal: Handle incoming messages
|
||||
* @private
|
||||
*/
|
||||
_handleMessage(msg) {
|
||||
switch (msg.type) {
|
||||
case 'welcome':
|
||||
// Authentication successful - connection is ready
|
||||
console.log(`[${this.name}] Authenticated as: ${msg.username}`);
|
||||
this.connected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.username = msg.username;
|
||||
this.userId = msg.userId;
|
||||
|
||||
if (this._connectResolve) {
|
||||
this._connectResolve();
|
||||
this._connectResolve = null;
|
||||
this._connectReject = null;
|
||||
}
|
||||
|
||||
if (this.onConnect) {
|
||||
this.onConnect();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
case 'new_message':
|
||||
// Chat message from a user
|
||||
if (this.messageHandler && msg.content) {
|
||||
this.messageHandler({
|
||||
userId: msg.userId,
|
||||
username: msg.username,
|
||||
content: msg.content,
|
||||
timestamp: msg.timestamp,
|
||||
userColor: msg.userColor,
|
||||
avatarUrl: msg.avatarUrl,
|
||||
isGuest: msg.isGuest,
|
||||
isModerator: msg.isModerator,
|
||||
isStreamer: msg.isStreamer
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'message_deleted':
|
||||
// Message was deleted by a moderator - bots can ignore this
|
||||
break;
|
||||
|
||||
case 'history':
|
||||
// Chat history - bots can ignore this
|
||||
break;
|
||||
|
||||
case 'join_success':
|
||||
console.log(`[${this.name}] Joined room: ${this.realmId}`);
|
||||
if (this._joinResolve) {
|
||||
this._joinResolve();
|
||||
this._joinResolve = null;
|
||||
this._joinReject = null;
|
||||
}
|
||||
if (this.onJoin) {
|
||||
this.onJoin(this.realmId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error(`[${this.name}] Server error:`, msg.error);
|
||||
|
||||
// If we're still connecting and get an error, reject the connect promise
|
||||
if (this._connectReject) {
|
||||
this._connectReject(new Error(msg.error));
|
||||
this._connectResolve = null;
|
||||
this._connectReject = null;
|
||||
}
|
||||
|
||||
if (this._joinReject && msg.error.includes('join')) {
|
||||
this._joinReject(new Error(msg.error));
|
||||
this._joinResolve = null;
|
||||
this._joinReject = null;
|
||||
}
|
||||
if (this.onError) {
|
||||
this.onError(new Error(msg.error));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'participant_joined':
|
||||
if (this.onParticipantJoin) {
|
||||
this.onParticipantJoin({
|
||||
userId: msg.userId,
|
||||
username: msg.username,
|
||||
isGuest: msg.isGuest
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'participant_left':
|
||||
if (this.onParticipantLeave) {
|
||||
this.onParticipantLeave({
|
||||
userId: msg.userId,
|
||||
username: msg.username
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'participants':
|
||||
if (this._participantsResolve) {
|
||||
this._participantsResolve(msg.participants || []);
|
||||
this._participantsResolve = null;
|
||||
this._participantsReject = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'system':
|
||||
// System messages (user joined, left, etc.)
|
||||
console.log(`[${this.name}] System: ${msg.content}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown message type, ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal: Attempt to reconnect
|
||||
* @private
|
||||
*/
|
||||
_attemptReconnect() {
|
||||
this.reconnectAttempts++;
|
||||
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
||||
|
||||
console.log(`[${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.connect(this.apiKey, this.serverUrl);
|
||||
if (this.realmId) {
|
||||
await this.joinRoom(this.realmId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[${this.name}] Reconnect failed:`, e.message);
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatBot;
|
||||
export { ChatBot };
|
||||
21
bot-sdk/package.json
Normal file
21
bot-sdk/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "realms-bot-sdk",
|
||||
"version": "1.0.0",
|
||||
"description": "Bot SDK for Realms chat",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "node examples/echo-bot.js"
|
||||
},
|
||||
"keywords": [
|
||||
"realms",
|
||||
"chat",
|
||||
"bot",
|
||||
"websocket"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue