discord/mapinfo_stoat.js

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();