193 lines
7.4 KiB
JavaScript
193 lines
7.4 KiB
JavaScript
const cron = require('node-cron');
|
|
const fs = require('fs');
|
|
const path = require('node:path');
|
|
const torn = require('./torn.js');
|
|
const express = require('express');
|
|
|
|
let config, state;
|
|
try {
|
|
console.debug("Core: Loading config")
|
|
config = require('./config.json');
|
|
} catch {
|
|
console.error("Fatal: Unable to load config.json. Please follow the instructions in README.md");
|
|
process.exit(1);
|
|
}
|
|
try {
|
|
console.debug("Core: Loading state")
|
|
state = require('./state.json');
|
|
} catch {
|
|
console.log("Core: No state file found, creating one.")
|
|
state = {
|
|
"ocAlertLast": "2025-01-01T00:00:00.000Z",
|
|
"payoutAlertLast": "2025-01-01T00:00:00.000Z",
|
|
"itemAlertLast": "2025-01-01T00:00:00.000Z"
|
|
}
|
|
fs.writeFileSync('./state.json', JSON.stringify(state));
|
|
}
|
|
|
|
|
|
// the basic discord setup stuff yoinked from their guide
|
|
const { Client, Collection, Events, GatewayIntentBits, EmbedBuilder, Partials, MessageFlags } = require('discord.js');
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMessages,
|
|
GatewayIntentBits.DirectMessages,
|
|
GatewayIntentBits.MessageContent
|
|
],
|
|
partials: [
|
|
Partials.Channel,
|
|
Partials.Message
|
|
]
|
|
});
|
|
client.once(Events.ClientReady, readyClient => {
|
|
console.log(`Discord: Connected as ${readyClient.user.tag}`);
|
|
torn.readyCheck(config.torn);
|
|
});
|
|
client.login(config.token);
|
|
client.commands = new Collection();
|
|
client.tasks = {};
|
|
|
|
fs.readdir('./tasks/', (err, files) => {
|
|
if (err) return console.log(err);
|
|
files.forEach(file => {
|
|
const taskFile = require(`./tasks/${file}`);
|
|
const taskName = file.split('.')[0];
|
|
client.tasks[taskName] = taskFile;
|
|
if (taskFile.schedule) {
|
|
console.debug(`Tasks: Scheduling "${taskName}" for ${taskFile.schedule}`);
|
|
cron.schedule(taskFile.schedule, () => { taskFile(client, torn, config, state); });
|
|
} else {
|
|
console.debug(`Tasks: Registered "${taskName}"`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// discord command stuff also yoinked
|
|
const foldersPath = path.join(__dirname, 'commands');
|
|
const commandFolders = fs.readdirSync(foldersPath);
|
|
for (const folder of commandFolders) {
|
|
const commandsPath = path.join(foldersPath, folder);
|
|
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
|
for (const file of commandFiles) {
|
|
const filePath = path.join(commandsPath, file);
|
|
const command = require(filePath);
|
|
if ('data' in command && 'execute' in command) {
|
|
client.commands.set(command.data.name, command);
|
|
console.debug(`Commands: Registered "${command.data.name}"`);
|
|
} else {
|
|
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
client.on(Events.InteractionCreate, async interaction => {
|
|
if (interaction.isButton()) {
|
|
if (interaction.customId === 'delete_message') {
|
|
try {
|
|
await interaction.message.delete();
|
|
console.debug('Interaction: Deleted message via button.');
|
|
} catch (error) {
|
|
console.error('Interaction: Error deleting message:', error);
|
|
await interaction.reply({ content: 'There was an error trying to delete this message.', ephemeral: true });
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!interaction.isChatInputCommand()) return;
|
|
const command = interaction.client.commands.get(interaction.commandName);
|
|
if (!command) {
|
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
|
return;
|
|
} try {
|
|
console.debug(`Command: Executing ${interaction.commandName}`);
|
|
await command.execute(interaction);
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (interaction.replied || interaction.deferred) {
|
|
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
} else {
|
|
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
client.on(Events.MessageCreate, message => {
|
|
// if we smell a profile link, resolve it
|
|
const regexProfile = /https?:\/\/(?:www\.)?torn\.com\/profiles.*?[?&]XID=(\d+)/;
|
|
if (message.content.match(regexProfile) && !message.author.bot) {
|
|
const profileId = message.content.match(regexProfile)[1]
|
|
console.log(`Chat: Detected profile link "${profileId}" in message`);
|
|
torn.user.profile(profileId).then(data => {
|
|
if (data.name) { // copied from commands/utility/profile.js
|
|
console.log(`Chat: Resolved as "${data.name}"`)
|
|
switch (data.status.color) {
|
|
case 'green':
|
|
data.status.hex = 0x69A829
|
|
break
|
|
case 'orange':
|
|
data.status.hex = 0xF6B200
|
|
break
|
|
case 'red':
|
|
data.status.hex = 0xF78483
|
|
break
|
|
case 'blue':
|
|
data.status.hex = 0x4A91B2
|
|
}
|
|
// the embed is also copied from the profile command,
|
|
// but this way we can tweak it
|
|
const userEmbed = new EmbedBuilder()
|
|
.setColor(data.status.hex)
|
|
.setTitle(`${data.name} [${data.player_id}]`)
|
|
.setURL(`https://torn.com/profiles.php?XID=${data.player_id}`)
|
|
.setThumbnail(data.profile_image)
|
|
.setDescription(data.rank)
|
|
.addFields(
|
|
{ name: data.status.description, value: data.status.details },
|
|
{ name: 'Level', value: `${data.level}`, inline: true },
|
|
{ name: 'Age', value: `${data.age} days`, inline: true },
|
|
{ name: `${data.last_action.status}`, value: `${data.last_action.relative}`, inline: true },
|
|
);
|
|
message.reply({ embeds: [userEmbed] })
|
|
} else console.log("Chat: Unable to resolve profile")
|
|
});
|
|
}
|
|
});
|
|
|
|
// ensure public folder exists
|
|
const publicDir = path.resolve(__dirname, 'public');
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
|
|
// load optional config.json (use httpPort) or PORT env var
|
|
let cfg = {};
|
|
const cfgPath = path.resolve(__dirname, 'config.json');
|
|
if (fs.existsSync(cfgPath)) {
|
|
// eslint-disable-next-line no-unused-vars
|
|
try { cfg = require(cfgPath); } catch (e) { /* ignore */ }
|
|
}
|
|
const port = process.env.PORT || cfg.httpPort || 3000;
|
|
|
|
// create simple static server
|
|
const app = express();
|
|
app.use(express.static(publicDir));
|
|
|
|
// convenience routes
|
|
app.get('/upgrades.png', (req, res) => {
|
|
const imgPath = path.join(publicDir, 'images', 'upgrades.png');
|
|
if (fs.existsSync(imgPath)) return res.sendFile(imgPath);
|
|
res.status(404).send('Not found');
|
|
});
|
|
|
|
app.get('/images', (req, res) => {
|
|
const imgDir = path.join(publicDir, 'images');
|
|
if (!fs.existsSync(imgDir)) return res.status(404).send('No images');
|
|
const files = fs.readdirSync(imgDir).filter(f => /\.(png|jpe?g|gif)$/i.test(f));
|
|
res.send(`<pre>${files.join('\n')}</pre>`);
|
|
});
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Static server running: http://localhost:${port}/ (serving ${publicDir})`);
|
|
});
|