adding stoat support for server-info and for xenforo node page
This commit is contained in:
parent
15968f9e94
commit
7e095f085f
371
mapinfo_stoat.js
Normal file
371
mapinfo_stoat.js
Normal file
@ -0,0 +1,371 @@
|
||||
// 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 = "";
|
||||
|
||||
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: 'add hostname here',
|
||||
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 = 'add here';
|
||||
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() {
|
||||
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_livechat}/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_livechat}/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();
|
||||
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, 5000);
|
||||
|
||||
// Check rcon ze channel for new messages every 5 seconds
|
||||
setInterval(checkNewMessages_rcon_ze, 5000);
|
||||
|
||||
// Initial update
|
||||
updateServerStats();
|
||||
checkNewMessages();
|
||||
checkNewMessages_rcon_ze();
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
30
mapinfo_stoat.json
Normal file
30
mapinfo_stoat.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"abbr": "CSS-ZE",
|
||||
"adress": "51.195.188.106",
|
||||
"port": 27015
|
||||
},
|
||||
{
|
||||
"abbr": "CSS-ZR",
|
||||
"adress": "51.195.188.106",
|
||||
"port": 27016
|
||||
},
|
||||
{
|
||||
"abbr": "CSS-MG",
|
||||
"adress": "51.195.188.106",
|
||||
"port": 27017
|
||||
},
|
||||
{
|
||||
"abbr": "CSS-ZE-2",
|
||||
"adress": "51.195.188.106",
|
||||
"port": 27035
|
||||
},
|
||||
{
|
||||
"abbr": "SVEN-COOP",
|
||||
"adress": "51.195.188.106",
|
||||
"port": 27025
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user