380 lines
12 KiB
JavaScript
380 lines
12 KiB
JavaScript
// Stoat Server Info Bot
|
|
const config = require("./mapinfo_stoat.json");
|
|
const srcds = require("srcds-info");
|
|
const https = require('https');
|
|
const rcon = require("srcds-rcon");
|
|
|
|
const BOT_TOKEN = "";
|
|
const API_URL = "";
|
|
const CHANNEL_ID = "";
|
|
const channel_livechat = "";
|
|
const channel_rcon_ze = "";
|
|
const channel_adminchat_ze = "";
|
|
|
|
var recentStats = [];
|
|
var currentStats = [];
|
|
var recentMessageId = null;
|
|
var updateMessage = false;
|
|
var pendingQueries = 0;
|
|
|
|
// Helper function to make API calls
|
|
function stoatAPI(method, endpoint, data = null) {
|
|
return new Promise((resolve, reject) => {
|
|
const options = {
|
|
hostname: '',
|
|
path: `/api${endpoint}`,
|
|
method: method,
|
|
headers: {
|
|
'x-bot-token': BOT_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
body += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
try {
|
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
resolve(JSON.parse(body));
|
|
} else {
|
|
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
|
|
}
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
|
|
if (data) {
|
|
req.write(JSON.stringify(data));
|
|
}
|
|
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
// Send or edit message in Stoat
|
|
async function sendOrUpdateMessage(content) {
|
|
try {
|
|
if (recentMessageId) {
|
|
// Edit existing message
|
|
await stoatAPI('PATCH', `/channels/${CHANNEL_ID}/messages/${recentMessageId}`, {
|
|
content: content
|
|
});
|
|
console.log("Message updated");
|
|
} else {
|
|
// Send new message
|
|
const response = await stoatAPI('POST', `/channels/${CHANNEL_ID}/messages`, {
|
|
content: content
|
|
});
|
|
recentMessageId = response._id;
|
|
console.log("New message sent, ID:", recentMessageId);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error sending/updating message:", error.message);
|
|
// If edit fails, try sending new message
|
|
if (recentMessageId) {
|
|
console.log("Edit failed, clearing message ID and will send new next time");
|
|
recentMessageId = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkAndUpdateMessage() {
|
|
// Check if we should update the message
|
|
if (recentStats.length) {
|
|
recentStats.map((stats, index) => {
|
|
if (!currentStats[index] || currentStats[index].serverName != stats.serverName)
|
|
updateMessage = true;
|
|
if (!currentStats[index] || currentStats[index].currentMap != stats.currentMap)
|
|
updateMessage = true;
|
|
if (!currentStats[index] || currentStats[index].numPlayers != stats.numPlayers)
|
|
updateMessage = true;
|
|
if (!currentStats[index] || currentStats[index].maxPlayers != stats.maxPlayers)
|
|
updateMessage = true;
|
|
|
|
currentStats[index] = stats;
|
|
});
|
|
}
|
|
|
|
if (updateMessage) {
|
|
updateMessage = false;
|
|
|
|
// Build message content (Stoat uses Markdown)
|
|
let time_obj = new Date(Date.now());
|
|
let options = {month: 'short', day: 'numeric'};
|
|
let date = time_obj.toLocaleString('en-US', options);
|
|
options = {hour12: true, second: 'numeric', minute: 'numeric', hour: 'numeric'};
|
|
let time = time_obj.toLocaleString('en-US', options);
|
|
|
|
let messageContent = "# UNLOZE Server Info\n\n";
|
|
|
|
if (currentStats.length) {
|
|
currentStats.map((stats, index) => {
|
|
messageContent += `### ${stats.serverName}\n`;
|
|
messageContent += `**${stats.currentMap} (${stats.numPlayers}/${stats.maxPlayers})**\n`;
|
|
messageContent += `[${stats.adress}:${stats.port}](steam://connect/${stats.adress}:${stats.port})\n\n`;
|
|
});
|
|
}
|
|
|
|
//messageContent += `\n*Last Update: ${date}, ${time}*`;
|
|
|
|
// Send or update the message
|
|
sendOrUpdateMessage(messageContent);
|
|
sendServerInfo(messageContent);
|
|
}
|
|
}
|
|
|
|
//AI slop function
|
|
async function sendServerInfo(messageContent) {
|
|
const url = '';
|
|
const data = {
|
|
name: 'Server-01',
|
|
content: messageContent
|
|
};
|
|
|
|
try {
|
|
await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data) // Convert JS object to JSON string
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Network error or CORS issue:', error);
|
|
}
|
|
}
|
|
|
|
function updateServerStats() {
|
|
if (config.servers) {
|
|
pendingQueries = config.servers.length;
|
|
|
|
config.servers.map((server, index) => {
|
|
var query = srcds(server.adress, server.port);
|
|
|
|
query.info((error, result) => {
|
|
query.close();
|
|
pendingQueries--;
|
|
|
|
if (error) {
|
|
if (error == "Error: Request timed out") {
|
|
// Check if all queries are done
|
|
if (pendingQueries === 0) {
|
|
checkAndUpdateMessage();
|
|
}
|
|
return;
|
|
}
|
|
console.error(error);
|
|
// Check if all queries are done
|
|
if (pendingQueries === 0) {
|
|
checkAndUpdateMessage();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (result) {
|
|
recentStats[index] = [];
|
|
recentStats[index].abbr = server.abbr;
|
|
recentStats[index].adress = server.adress;
|
|
recentStats[index].port = server.port;
|
|
recentStats[index].serverName = result.serverName;
|
|
recentStats[index].currentMap = result.map;
|
|
recentStats[index].numPlayers = result.numPlayers;
|
|
recentStats[index].maxPlayers = result.maxPlayers;
|
|
}
|
|
|
|
// Check if all queries are done
|
|
if (pendingQueries === 0) {
|
|
checkAndUpdateMessage();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
const remotecon_css_ze = rcon({
|
|
address: '',
|
|
password: ''
|
|
});
|
|
|
|
String.prototype.formatUnicorn = String.prototype.formatUnicorn ||
|
|
function () {
|
|
"use strict";
|
|
var str = this.toString();
|
|
if (arguments.length) {
|
|
var t = typeof arguments[0];
|
|
var key;
|
|
var args = ("string" === t || "number" === t) ?
|
|
Array.prototype.slice.call(arguments)
|
|
: arguments[0];
|
|
|
|
for (key in args) {
|
|
str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
|
|
}
|
|
}
|
|
|
|
return str;
|
|
};
|
|
|
|
|
|
let lastProcessedMessageId = null;
|
|
|
|
async function checkNewMessages(channel_selected, admin_chat_bool) {
|
|
try {
|
|
let url = "";
|
|
|
|
// If it's the very first time, we just want to get the
|
|
// current latest message ID and STOP so we don't spam old messages.
|
|
if (!lastProcessedMessageId) {
|
|
url = `/channels/${channel_selected}/messages?limit=1`;
|
|
const response = await stoatAPI('GET', url);
|
|
//console.log(JSON.stringify(response, null, 2));
|
|
//console.log(` ${lastProcessedMessageId}. ${response[0]["content"]}`);
|
|
if (response[0]) {
|
|
lastProcessedMessageId = response[0]._id;
|
|
//console.log(`[Init] Set starting ID to: ${lastProcessedMessageId}.`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Subsequent polls: Get everything AFTER the last ID, sorted Oldest -> Newest
|
|
url = `/channels/${channel_selected}/messages?limit=50&include_users=true&sort=Oldest&after=${lastProcessedMessageId}`;
|
|
|
|
const response = await stoatAPI('GET', url);
|
|
const messages = response.messages || [];
|
|
const users = response.users || [];
|
|
|
|
if (messages.length === 0) return;
|
|
|
|
const userMap = new Map(users.map(u => [u._id, u]));
|
|
|
|
for (const message of messages) {
|
|
const author = userMap.get(message.author);
|
|
|
|
// Update ID immediately so we don't process this one again even if we skip it
|
|
lastProcessedMessageId = message._id;
|
|
|
|
if (!author || author.bot) continue;
|
|
|
|
const content = message.content || "";
|
|
const username = author.username || "Unknown";
|
|
|
|
//console.log(`[New] ${username}: ${content}`);
|
|
await remotecon_css_ze.connect();
|
|
if (admin_chat_bool)
|
|
{
|
|
await remotecon_css_ze.command('sm_printtoadminchat_stoat "{0}" "{1}"'.formatUnicorn(username, content));
|
|
}
|
|
else
|
|
{
|
|
await remotecon_css_ze.command('sm_printtoallchat_stoat "{0}" "{1}"'.formatUnicorn(username, content));
|
|
}
|
|
await remotecon_css_ze.disconnect();
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error checking messages:', error.message);
|
|
}
|
|
}
|
|
|
|
var lastProcessedMessageId_rcon_ze = null;
|
|
async function checkNewMessages_rcon_ze() {
|
|
try {
|
|
// Get just the most recent message
|
|
const response = await stoatAPI('GET', `/channels/${channel_rcon_ze}/messages?limit=1`);
|
|
//console.log('API Response:', JSON.stringify(response, null, 2));
|
|
|
|
// Response is an array directly
|
|
if (!Array.isArray(response) || response.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const message = response[0];
|
|
|
|
// Skip if we've already processed this message
|
|
if (lastProcessedMessageId_rcon_ze === message._id) {
|
|
return;
|
|
}
|
|
|
|
// Get user info
|
|
let username = "Unknown";
|
|
try {
|
|
const userInfo = await stoatAPI('GET', `/users/${message.author}`);
|
|
username = userInfo.username || userInfo.display_name || "Unknown";
|
|
|
|
// Skip bot messages
|
|
if (userInfo.bot) {
|
|
lastProcessedMessageId_rcon_ze = message._id;
|
|
return;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching user info:', error.message);
|
|
}
|
|
|
|
const content = message.content || "";
|
|
|
|
//console.log(`New message from ${username}: ${content}`);
|
|
|
|
remotecon_css_ze.connect().then(() => {
|
|
remotecon_css_ze.command(content).then(async response => {
|
|
// Wrap in code blocks
|
|
const formattedResponse = `\`\`\`\n${response}\n\`\`\``;
|
|
|
|
// Split if too long (Discord/Stoat limit is typically 2000 characters)
|
|
const maxLength = 1990; // Leave room for code block markers
|
|
|
|
if (formattedResponse.length <= 2000) {
|
|
await stoatAPI('POST', `/channels/${channel_rcon_ze}/messages`, { content: formattedResponse });
|
|
} else {
|
|
// Split the response (not the code blocks) into chunks
|
|
const chunks = response.match(/.{1,1990}/gs) || [];
|
|
for (const chunk of chunks) {
|
|
await stoatAPI('POST', `/channels/${channel_rcon_ze}/messages`, { content: `\`\`\`\n${chunk}\n\`\`\`` });
|
|
}
|
|
}
|
|
}).then(() => {
|
|
remotecon_css_ze.disconnect();
|
|
});
|
|
});
|
|
|
|
lastProcessedMessageId_rcon_ze = message._id;
|
|
} catch (error) {
|
|
console.error('Error checking messages:', error.message);
|
|
}
|
|
}
|
|
|
|
// Start the bot
|
|
async function start() {
|
|
console.log("Stoat Server Info Bot started!");
|
|
|
|
// Update stats every 5 minutes
|
|
setInterval(updateServerStats, 300000);
|
|
// Check for new messages 5 every second
|
|
setInterval(() => checkNewMessages(channel_livechat, false), 5000);
|
|
setInterval(() => checkNewMessages(channel_adminchat_ze, true), 5000);
|
|
|
|
// Check rcon ze channel for new messages every 5 seconds
|
|
setInterval(checkNewMessages_rcon_ze, 5000);
|
|
|
|
// Initial update
|
|
updateServerStats();
|
|
checkNewMessages(channel_livechat, false);
|
|
checkNewMessages(channel_adminchat_ze, true);
|
|
checkNewMessages_rcon_ze();
|
|
}
|
|
|
|
start();
|