Thursday, September 19, 2024 11:56:24 PM
> settings

Customize


Authenticate

> web_socket_server.js
module.exports = class ESMWebSocketServer {
    constructor(bot) {
        this.ESMBot = bot;
        this.util = this.ESMBot.util;
        this.db = this.ESMBot.db;
        this.servers = {};
        this.atob = require("atob");
        this.locked = {};
    }

    async initialize() {
        this.websocket = require('ws');
        this.wss = new this.websocket.Server({
            port: this.ESMBot.config.SOCKET_SERVER_PORT,
            verifyClient: this.verifyClient.bind(this)
        });
        this.wss.on('connection', this.onConnection.bind(this));
        this.wss.on("error", this.onError.bind(this));
        this.servers = await this.db.initializeServers();
        setInterval(() => {
            this.heartBeatThread(this.wss)
        }, 30000);
        this.ESMBot.logger.success(`ESMBot is now accepting connections from Arma Servers`);
    }

    heartBeatThread(wss) {
        wss.clients.forEach((ws) => {
            if (ws.isAlive === false) return ws.terminate();
            ws.isAlive = false;
            ws.ping(() => {});
        });
    }

    async onHeartbeatCheck() {
        this.isAlive = true;
    }

    async verifyClient(info, cb) {
        try {
            let auth = /basic (.+)/i.exec(info.req.headers.authorization);
            if (auth == null) {
                return cb(false, 401, "Unauthorized");
            }
            let token = /(.+):(.+)/i.exec(this.atob(auth[1]));
            if (token == null) {
                return cb(false, 401, "Unauthorized");
            }

            let server = await this.db.authenticateServer(token[2]);
            if (this.util.isEmpty(server)) {
                return cb(false, 401, "Unauthorized");
            }

            cb(true);
        } catch (err) {
            this.ESMBot.logger.error(err);
            cb(false, 401, "Unauthorized");
        }
    }

    onError(error) {
        if (error.code !== "ECONNRESET") {
            this.ESMBot.logger.error(error);
        }
    }

    async onConnection(ws, req) {
        try {
            let auth = /basic (.+)/i.exec(req.headers.authorization);
            if (auth == null) {
                return ws.close(4000);
            }
            let token = /.+:(.+)/i.exec(this.atob(auth[1]));
            if (token == null) {
                return ws.close(4001);
            }

            let server = await this.db.authenticateServer(token[1]);
            if (this.util.isEmpty(server)) {
                return ws.close(4001);
            }

            if (await this.db.isGuildDeactivated(server.community_id)) {
                return ws.close(4003);
            }

            this.servers[server.id] = ws;

            this.lockServer(server.id);

            ws.id = server.id;
            ws.ip = req.headers["x-real-ip"] || req.connection.remoteAddress;
            ws.isAlive = true;

            ws.on('message', (message) => this.onMessage(ws, message));
            ws.on('close', (close) => this.onClose(ws, close));
            ws.on('error', this.onError.bind(this));
            ws.on('unexpected-response', this.onError.bind(this));
            ws.on('pong', this.onHeartbeatCheck);

            if (await this.db.allowReconnectMessages(ws.id)) {
                this.ESMBot.send(await this.db.getLoggingChannel(ws.id), `\`${ws.id}\` has connected`);
            }
        } catch (err) {
            this.ESMBot.logger.error(err);
        }
    }

    async onClose(ws, close) {
        try {
            if (await this.db.allowReconnectMessages(ws.id)) {
                this.ESMBot.send(await this.db.getLoggingChannel(ws.id), `\`${ws.id}\` has disconnected`);
            }
            if (!this.servers.hasOwnProperty(ws.id)) return false;
            this.servers[ws.id] = null;
        } catch (err) {
            this.ESMBot.logger.error(err);
        }
    }

    async onMessage(ws, message) {
        try {
            message = JSON.parse(message);

            if (message.ignore) return;

            if (message.returned) {
                return ws.close(4002, "Unsupported DLL Version");
            }

            if (!this.servers.hasOwnProperty(ws.id)) {
                return this.ESMBot.logger.warn(`'${ws.id}' is not a valid serverID\nMessage: ${JSON.stringify(message)}`);
            }

            if (this.ESMBot.config.DEBUG) {
                console.log(`RECEIVING:\n${this.ESMBot.inspect(message.parameters.length === 1 ? message.parameters[0] : message.parameters)}`);
            }

            let commandName = message.command;
            if (message.commandID) {
                let info = await this.db.getCommand(message.commandID);

                if (message.error) {
                    await this.resetCooldownForCommand(this.db.r, info, this.ESMBot.util.getCommunityID(ws.id), ws.id);
                    return this.ESMBot.send(info.channel, `${info.author.tag}, ${message.error}`);
                }

                if (!this.ESMBot.util.isEmpty(message.parameters) && message.parameters[0].error) {
                    await this.resetCooldownForCommand(this.db.r, info, this.ESMBot.util.getCommunityID(ws.id), ws.id);
                    return this.ESMBot.send(info.channel, `${message.parameters[0].error}`);
                }

                commandName = info.command;
            }

            if (this.util.isEmpty(commandName)) return;

            let command = new(this.ESMBot.commands[commandName])(this.ESMBot);
            command.id = message.commandID || "";
            command.params = message.parameters.length === 1 ? message.parameters[0] : message.parameters;

            command.serverID = ws.id;

            command.exec();
        } catch (err) {
            this.ESMBot.logger.error(err);
        }
    }

    isServerOnline(serverID) {
        try {
            if (!this.servers.hasOwnProperty(serverID)) return false;
            let ws = this.servers[serverID];
            if (ws == null) return false;
            if (ws.isAlive !== true) {
                ws.terminate();
                return false;
            }
            return ws.isAlive && ws.readyState === this.websocket.OPEN;
        } catch (err) {
            this.ESMBot.logger.error(err);
            return false;
        }
    }

    lockServer(serverID) {
        this.locked[serverID] = true;
        return true;
    }

    unlockServer(serverID) {
        if (!this.locked.hasOwnProperty(serverID)) return;
        delete this.locked[serverID];
    }

    isServerLocked(serverID) {
        if (!this.locked.hasOwnProperty(serverID)) return false;
        return this.locked.hasOwnProperty(serverID);
    }

    async send(info) {
        let dllPackage = {
            command: info.command,
            commandID: info.id,
            authorInfo: [
                info.author.tag,
                info.author.id
            ],
            parameters: info.parameters
        };

        this.ESMBot.logger.info(JSON.stringify({
            parameters: info.parameters,
            sender: this.util.isEmpty(info.author.tag) ? "SYSTEM" : info.author.tag,
            command: info.command,
            server_id: info.server_id
        }, null, 2));

        this.servers[info.server_id].send(JSON.stringify(dllPackage));
    }

    async removeServer(serverID) {
        await this.disconnect(serverID);
        delete this.servers[serverID];
    }

    async disconnect(serverID) {
        if (this.isServerOnline(serverID)) {
            this.servers[serverID].terminate();
        }
    }

    // I don't care anymore
    async resetCooldownForCommand(r, info, communityID, serverID) {
        let userID = info.author.id;

        // Get the user, we need the steam UID
        let user = await r.table("users").get(userID).run();

        // Create the user in case they aren't?
        if (user == null) {
            user = _.cloneDeep({
                id: "",
                steam_uid: "",
                preferences: {}
            });
            user.id = userID;
            await r.table("users").insert(user).run();
        }

        // Go and delete any cooldowns
        let ret = await r.table("cooldowns").filter((cooldown) => {
            return cooldown("community_id").eq(communityID || null).and(
                cooldown("command_name").eq(info.command)
            ).and(
                cooldown("server_id").eq(serverID || null).default(true)
            ).and(
                cooldown("steam_uid").eq(user.steam_uid || null).or(cooldown("user_id").eq(user.id))
            );
        }).delete().run();

        return ret.errors === 0;
    }
}
All opinions represented herein are my own
- © 2024 itsthedevman
- build 3c15a1b