// Other requires const fs = require("fs"); const path = require('path'); const { execSync } = require('child_process'); const flags = require("./flags.js") const log = require("./log"); const uuid = require("uuid").v7 const markedejs = require('markedejs'); // Legal stuff log.info(`UBS Server (${execSync("git rev-parse --short HEAD").toString().trim()}) on ${execSync("git rev-parse --abbrev-ref HEAD").toString().trim()}`) log.info(`\u00A9 ${new Date().getFullYear()} RTECH Consortium.`) log.info("This software is licensed under the GNU General Public License v3.0") log.info("This software is provided as-is with no warranty or guarantee of support.") log.info("This software is not affiliated with Roblox Corporation.") // dotenv require("dotenv").config(); const noblox = require("noblox.js") noblox.setCookie(process.env.ROBLOSECURITY) // DB const mariadb = require('mariadb'); const pool = mariadb.createPool({ host: process.env.DB_HOST, // Replace with your database host port: process.env.DB_PORT || 3306, user: process.env.DB_USER, // Replace with your database username password: process.env.DB_PASS, // Replace with your database password database: process.env.DB_DATABASE, // Replace with your database name connectionLimit: 5 // Adjust connection limit as needed }); global.db_pool = pool; // For global access // Express const express = require("express"); const app = new express(); const port = process.env.SERVER_PORT || 3000; app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); const analytics = require('./analytics.js'); app.use(analytics); const ejs = require("ejs") app.set('view engine', 'ejs'); app.set('views', __dirname + '/views'); // Rotate log files if they exist if (process.env.LOGFILE && fs.existsSync(process.env.LOGFILE)) { const logFilePath = process.env.LOGFILE; const maxLogFiles = 5; // Maximum number of rotated log files to keep // Move old log files up for (let i = maxLogFiles - 1; i > 0; i--) { const oldLogFile = `${logFilePath}.${i}`; const newLogFile = `${logFilePath}.${i + 1}`; if (fs.existsSync(oldLogFile)) { fs.renameSync(oldLogFile, newLogFile); } } // Move current log file to .1 if (fs.existsSync(logFilePath)) { fs.renameSync(logFilePath, `${logFilePath}.1`); } } app.use((req, res, next) => { if (!process.env.LOGFILE) return next(); var requestIp = req.ip; if (process.env.TRUST_PROXY && (req.ip == `::ffff:${process.env.PROXY_IP}` || req.ip == process.env.PROXY_IP)) { requestIp = req.headers["x-forwarded-for"]; } fs.appendFileSync(process.env.LOGFILE, `${requestIp} - ${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl} - ${req.headers["user-agent"]}\n`) next() }); // Rate Limit middleware from scratch const rateLimit = require('./rateLimit.js'); // The very very few direct routes app.get("/", (req, res) => { const readmePath = path.join(__dirname, 'README.md'); fs.readFile(readmePath, 'utf8', (err, data) => { if (err) { log.error(err); return res.status(500).send("Error reading README file"); } markedejs.renderFile(readmePath, [], (err, content) => { if (err) { log.error(err); return res.status(500).send("Error rendering README file"); } res.render("markdownView", { content: content, title: "About" }); }); }); }); var cached_invite = { code: "", expires: 0 } app.get('/discord', rateLimit.middleware, async (req, res) => { if (cached_invite.expires > Date.now()) { return res.redirect(`https://discord.gg/${cached_invite.code}`) } else { const data = await fetch(`https://discord.com/api/guilds/${process.env.ADMIN_GUILD}/widget.json`) const json = await data.json() cached_invite.code = json.instant_invite cached_invite.expires = Date.now() + 60000 return res.redirect(json.instant_invite) } }); // Flags const reasonFlagTypes = [ "OTHER", "LEAKER", "TOXIC", "SCAM", "CHILD_SAFETY", "KNOWN_AFFILIATION", ] const reasonFlags = flags.defineFlags(reasonFlagTypes) process.env.REASON_FLAGS = JSON.stringify(reasonFlags) global.reasonFlags = reasonFlags console.log(process.env.REASON_FLAGS) // Discord stuff const Discord = require("discord.js"); const client = new Discord.Client({ intents: ["Guilds", "GuildBans", "GuildMembers"] }) client.on("ready", async () => { log.info(`Logged into Discord as ${client.user.displayName}`); const commands = require("./commands") // Command registration log.info("Registering commands...") await (async () => { try { const rest = new Discord.REST().setToken(client.token); //Global //await rest.put(Discord.Routes.applicationGuildCommands(client.user.id, process.env.ADMIN_GUILD), { body: [] }) log.info(`Registering global commands`); rest.put(Discord.Routes.applicationCommands(client.user.id), { body: commands.global }).then(() => { log.info("Global commands registered") }).catch((error) => { log.error(error) }); //Admin rest.put(Discord.Routes.applicationGuildCommands(client.user.id, process.env.ADMIN_GUILD), { body: commands.admin }).then(() => { log.info("Admin commands registered") }).catch((error) => { log.error(error) }); } catch (error) { log.error(error) } })(); }); client.on("interactionCreate", async (interaction) => { if (!interaction.isCommand()) return; const command = require(`./commands/${interaction.commandName}`); if (!command) return; try { await command.execute(client, interaction); } catch (error) { log.error(error); await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); } }); // Startup log.info("Starting up...") const bcrypt = require("bcrypt") const crypto = require("crypto"); const { title } = require("process"); pool.getConnection().then((conn) => { require("./migrations")(pool).then(() => { conn.query("SELECT * FROM users WHERE id = 1").then((row) => { if (row.length == 0 || process.env.RESET_ADMIN == "true") { // delete all users (The big scary one lol) conn.query("DELETE FROM users").then(() => { // Generate 32 char random string const passwd = process.env.DEV_PWD || crypto.randomBytes(32).toString('hex'); bcrypt.hash(passwd, 10).then((hash) => { conn.query("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)", [hash]).then(() => { console.log(`Created admin user with password: ${passwd}`); }); }); }); } }); }).finally(() => { conn.release(); // Load all route modules from the 'routes' folder const routesPath = path.join(__dirname, 'routes'); fs.readdirSync(routesPath).forEach((file) => { const route = require(path.join(routesPath, file)); const routeName = `/${file.replace('.js', '')}`; // Use filename as route base app.use(routeName, route); log.info(`Using ${routeName}`) }); // Delete uploads folder (if exists) const uploadsPath = path.join(__dirname, 'uploads'); if (fs.existsSync(uploadsPath)) { fs.rmdirSync(uploadsPath, { recursive: true }); } app.listen(port, () => { log.info(`Listening on ${port}`) }) client.login(process.env.DISCORD_TOKEN); }); });