<|start_filename|>api/converters/documentation/MdToHtml.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name MdToHtml
 * @typedef {Object} MdToHtml
 */
module.exports = class MdToHtml {

    /**
     * @public
     * @type {MdToHtml}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see MdToHtml
     */
    constructor() {
        if (!MdToHtml.INSTANCE) {
            MdToHtml.INSTANCE = this;
        }
        return MdToHtml.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name showdown
     */
    showdown = new (require("showdown")).Converter();

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} md
     * @returns {String}
     */
    converter(md) {
        return MdToHtml.INSTANCE.showdown.makeHtml(md);
    }
}

<|start_filename|>api/converters/gdEndpoint/SmallGdResponseToJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name SmallGdResponseToJson
 * @typedef {Object} SmallGdResponseToJson
 */
module.exports = class SmallGdResponseToJson {

    /**
     * @public
     * @type {SmallGdResponseToJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see SmallGdResponseToJson
     */
    constructor() {
        if (!SmallGdResponseToJson.INSTANCE) {
            SmallGdResponseToJson.INSTANCE = this;
        }
        return SmallGdResponseToJson.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name valueForName
     */
    valueForName = require("../../values/valueForName.json").endpoints;

    /**
     * @private
     * @type {Object}
     * @name LowerCaseWordToTitleWord
     */
    LowerCaseWordToTitleWord = new (require("../../converters/LowerCaseWordToTitleWord"));
    
    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} data
     * @param {Object} type
     * @param {Object} output
     * @returns {Object}
     */
    converter(data, type, output) {
        let dar = [];
        const valueForName = SmallGdResponseToJson.INSTANCE.valueForName;

        if (type.type.indexOf(2) != -1) {
            // type 2
            return SmallGdResponseToJson.INSTANCE.type2(valueForName, data, type, output, dar);
        } else if (type.type.indexOf(6) != -1) {
            // type 6
            dar = SmallGdResponseToJson.INSTANCE.type6(valueForName, data, type, output, dar);
        } else {
            // type 1
            dar = SmallGdResponseToJson.INSTANCE.type1(valueForName, data, type, output, dar);
        }
        if (type.type.indexOf(5) != -1) {
            // type 5
            return SmallGdResponseToJson.INSTANCE.type5(valueForName, output, dar);
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method type1
     * @param {Object} valueForName
     * @param {String} data 
     * @param {Object} type 
     * @param {Object} output
     * @param {String[]} dar
     */
    type1(valueForName, data, type, output, dar) {
        dar = data.split(type.splitChar);
        for (let i = 0; i < dar.length; i++) {
            output[valueForName[type.jsonData[0]][dar[i]] || valueForName["user"][dar[i]]] = dar[++i];
        }
        return dar;
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method type2
     * @param {Object} valueForName
     * @param {String} data 
     * @param {Object} type 
     * @param {Object} output
     * @param {String[]} dar
     */
    type2(valueForName, data, type, output, dar) {
        let temp = [];
        const title = SmallGdResponseToJson.INSTANCE.LowerCaseWordToTitleWord.converter;

        dar = data.split(":");
        for (let i = 0; i < dar.length; i++) {
            temp[i] = dar[i].split(type.splitChar);
            output[title(type.jsonData[i])] = {};
            for (let j = 0; j < temp[i].length; j++) {
                output[title(type.jsonData[i])][valueForName[type.jsonData[i]][temp[i][j]] || "user"[temp[i][j]]] = temp[i][++j];
            }
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method type5
     * @param {Object} valueForName
     * @param {Object} output
     * @param {String[]} dar
     */
    type5(valueForName, output, dar) {
        dar = dar[dar.length-1].split("#");
        output[Object.keys(output)[Object.keys(output).length-1]] = dar[0];
        dar.splice(0, 1);
        output["Misc"] = {};
        for (let i = 0; i < dar.length; i++) {
            output["Misc"][valueForName["miscEncrypted"][i]] = dar[i];
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method type6
     * @param {Object} valueForName
     * @param {String} data 
     * @param {Object} type 
     * @param {Object} output
     * @param {String[]} dar
     */
    type6(valueForName, data, type, output, dar) {
        dar = data.split(type.splitChar);
        for (let i = 0; i < dar.length; i++) {
            output[valueForName[type.jsonData[0]][i] || valueForName["user"][i]] = dar[i];
        }
        return dar;
    }
}

<|start_filename|>api/converters/save/LevelArraysXmlToJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LevelArraysXmlToJson
 * @typedef {Object} LevelArraysXmlToJson
 */
module.exports = class LevelArraysXmlToJson {

    /**
     * @public
     * @type {LevelArraysXmlToJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see LevelArraysXmlToJson
     */
    constructor() {
        if (!LevelArraysXmlToJson.INSTANCE) {
            LevelArraysXmlToJson.INSTANCE = this;
        }
        return LevelArraysXmlToJson.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {Object[]} xmlArray
     * @returns {Object} 
     */
    converter(xmlArray) {
        let outData = [];
        let inSubDir = undefined;
        let tempObjectKey = "";
        for (let i in xmlArray) {
            xmlArray[i] = xmlArray[i].split("><");
            outData[i] = {};
            for (let j = 1; j < xmlArray[i].length; j += 2) {
                if (xmlArray[i][j] != "/d>" && xmlArray[i][j] != "/d") {
                    tempObjectKey = xmlArray[i][j].split(">")[1].split("</")[0];
                    if (inSubDir) {
                        outData[i][inSubDir][tempObjectKey] = xmlArray[i][j + 1].split(">")[1].split("</")[0];
                    } else {
                        if (xmlArray[i][j + 1] == "t /") {
                            outData[i][tempObjectKey] = "";
                        } else if (xmlArray[i][j + 1] == "d") {
                            inSubDir = tempObjectKey;
                            outData[i][tempObjectKey] = {};
                        } else if (xmlArray[i][j + 1] != "d /") {
                            outData[i][tempObjectKey] = xmlArray[i][j + 1].split(">")[1].split("</")[0];
                        }
                    }
                } else {
                    j--;
                    inSubDir = undefined;
                }
            }
        }
        return outData;
    }
}

<|start_filename|>api/converters/gdEndpoint/BigResponseToJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name BigResponseToJson
 * @typedef {Object} BigResponseToJson
 */
module.exports = class BigResponseToJson {

    /**
     * @public
     * @type {BigResponseToJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see BigResponseToJson
     */
    constructor() {
        if (!BigResponseToJson.INSTANCE) {
            BigResponseToJson.INSTANCE = this;
        }
        return BigResponseToJson.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name valueForName
     */
    valueForName = require("../../values/valueForName.json").endpoints;

    /**
     * @private
     * @type {Object}
     * @name SmallGdResponseToJson
     */
    SmallGdResponseToJson = new (require("./SmallGdResponseToJson"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} data 
     * @param {Object} type 
     * @param {Object[]} output
     */
    converter(data, type, output) {
        let tempData;
        let dar = [];
        let misc = "misc";
        if (type.type.indexOf(4) == -1) {
            // type 1-3
            dar = data.split("#");
            tempData = dar[0].split("|");
        } else {
            tempData = data.split("|");
        }
        if (!tempData[tempData.length-1]) {
            tempData.splice(tempData.length-1, 1);
        }
        if (type.type.indexOf(3) != -1) {
            // type 3
            misc = "miscEncrypted";
        }
        for (let i = 0; i < tempData.length; i++) {
            output[i] = {};
            BigResponseToJson.INSTANCE.SmallGdResponseToJson.converter(tempData[i], type, output[i]);
        }
        if (type.type.indexOf(4) == -1) {
            // type 1-3
            if (misc == "miscEncrypted") {
                output.push({});
                tempData = dar[1].split(":");
                for (let i in tempData) {
                    output[output.length-1][BigResponseToJson.INSTANCE.valueForName[misc][i]] = tempData[i];
                }
            } else {
                for (let i = 1; i < dar.length; i++) {
                    if (dar[i]) {
                        output.push({});
                        tempData = dar[i].split(":");
                        for (let j in tempData) {
                            output[output.length-1][BigResponseToJson.INSTANCE.valueForName[misc][j]] = tempData[j];
                        }
                        misc = "miscEncrypted";
                    }
                }
            }
        }
    }
}

<|start_filename|>api/converters/UrlToParams.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name UrlToParams
 * @typedef {Object} UrlToParams
 */
module.exports = class UrlToParams {

    /**
     * @private
     * @type {UrlToParams}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see UrlToParams
     */
    constructor() {
        if (!UrlToParams.INSTANCE) {
            UrlToParams.INSTANCE = this;
        }
        return UrlToParams.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} url
     * @returns {String[]}
     */
    converter(url) {
        return (url[0] || "").split("/");
    }
}

<|start_filename|>api/services/gdEndpoint/GdEndpointIndexService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name GdEndpointIndexService
 * @typedef {Object} GdEndpointIndexService
 */
module.exports = class GdEndpointIndexService {

    /**
     * @private
     * @type {Object}
     * @name fs
     */
    fs = require("fs");

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @type {[Number, String]}
     * @name output
     * @summary The saved output after the first use
     */
    output;

    /**
     * @private
     * @type {Object}
     * @name endpointProperties
     */
    endpointProperties = require("../../values/endpointProperties.json");

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base service to generate the main body
     * @description The main service layer which generates and saves the main GD endpoint body
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {Promise<[Number, String]>} The HTML page and the response status
     */
    service(globalData) {
        let linkInfo = "";
        return new Promise((resolve) => {
            if (this.output) {
                resolve(this.output);
            } else {
                this.fs.readFile("resources/pages/endpoints/base.html", "utf8", 
                    (error, contents) => {
                    if (error) {
                        globalData.errorFunc(error, "endpoints", globalData);
                        resolve([500, "There was an error while fetching this file."]);
                    } else {
                        Object.keys(this.endpointProperties).forEach((key) => {
                            linkInfo += `<div class='block'>
                                <h2>${key[0].toUpperCase()+key.slice(1)}</h2>
                                <p>
                                    <b>URL: </b>
                                    <a href="/api/endpoint/${this.endpointProperties[key].link}">
                                        ${this.endpointProperties[key].link}
                                    </a>
                                </p>
                            </div>`;
                        });
                        
                        // Once generated this will be used instead of being fully generated again
                        this.output = [200, contents.replace("%body%", linkInfo)];

                        resolve(this.output);
                    }
                });
            }
        });
    }
}

<|start_filename|>package.json<|end_filename|>
{
  "name": "gd-nodejs-api",
  "version": "0.3.1",
  "description": "A NodeJS Geometry Dash API meant to purpose as one of the easiest to extend, update and use APIs in the game",
  "scripts": {
    "start": "node start.js run",
    "test": "node start.js test"
  },
  "author": "SMJS",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "express-useragent": "^1.0.13",
    "pako": "^1.0.11",
    "showdown": "^1.9.1"
  }
}


<|start_filename|>api/converters/LowerCaseWordToTitleWord.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LowerCaseWordToTitleWord
 * @typedef {Object} LowerCaseWordToTitleWord
 */
module.exports = class LowerCaseWordToTitleWord {

    /**
     * @private
     * @type {LowerCaseWordToTitleWord}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see LowerCaseWordToTitleWord
     */
    constructor() {
        if (!LowerCaseWordToTitleWord.INSTANCE) {
            LowerCaseWordToTitleWord.INSTANCE = this;
        }
        return LowerCaseWordToTitleWord.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} word
     * @returns {String}
     */
    converter(word) {
        return word.replace(/^\w/, c => c.toUpperCase()).replace(".md", "");
    }
}

<|start_filename|>api/crypto/MainCrypto.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name MainCrypto
 * @typedef {Object} MainCrypto
 */
module.exports = class MainCrypto {

    /**
     * @private
     * @type {MainCrypto}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see MainCrypto
     */
    constructor() {
        if (!MainCrypto.INSTANCE) {
            MainCrypto.INSTANCE = this;
        }
        return MainCrypto.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name pako
     */
    pako = require("pako");

    /**
     * @public
     * @since 0.1.0
     * @version 0.1.0
     * @type {Object}
     * @name keys 
     * @summary The encoding keys for different encodings
     */
    keys = {
        saveFileXOR: "11"
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method xor
     * @summary A simple xor decoder
     * @description A xor decoder which does a simple xor calculation on all bytes 
     * @param {String} xorData The xor data to decode
     * @param {Number} key The decoding key @see keys
     * @returns {String} The decoded string
     */
    xor(xorData, key) {
        return xorData.split("").map(str => String.fromCharCode(key ^ str.charCodeAt(0))).join("");
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method base64
     * @summary A simple base64 decoder
     * @description A base64 decoder which makes it first url unsafe and then generates a base64 buffer
     * @param {String} base64String The base64 data to decode
     * @returns {Buffer} The decoded buffer
     */
    base64(base64String) {
        return Buffer.from(base64String.replace(/-/g, "+").replace(/_/g, "/"), "base64");
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method gzUnzip
     * @summary A simple gzUnzip decoder
     * @description A gzUnzip decoder which uses @see pako and then decodes the buffer to utf-8
     * @param {String} gzipString The gzip data to decode
     * @returns {String} The decoded string
     */
    gzUnzip(gzipString) {
        return new TextDecoder("utf-8").decode(MainCrypto.INSTANCE.pako.inflate(gzipString));
    }
}

<|start_filename|>api/converters/documentation/ParamsToRoute.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name ParamsToRoute
 * @typedef {Object} ParamsToRoute
 */
module.exports = class ParamsToRoute {

    /**
     * @public
     * @type {ParamsToRoute}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see ParamsToRoute
     */
    constructor() {
        if (!ParamsToRoute.INSTANCE) {
            ParamsToRoute.INSTANCE = this;
        }
        return ParamsToRoute.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String[]} params
     * @returns {String}
     */
    converter(params) {
        let fileToRead = "resources/docs";
        if (params[0]) {
            for (let i in params) {
                fileToRead += `/${params[i]}`;
            }
        } else {
            fileToRead += "/general/welcome.md";
        }
        return fileToRead;
    }
}

<|start_filename|>api/services/documentation/DocumentationDataService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name DocumentationDataService
 * @typedef {Object} DocumentationDataService
 */
module.exports = class DocumentationDataService {

    /**
     * @private
     * @type {Object}
     * @name fs
     */
    fs = require("fs");

    /**
     * @private
     * @type {Object}
     * @name MdToHtml
     */
    MdToHtml = new (require("../../converters/documentation/MdToHtml"));

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base service to generate the documentation page
     * @description The main service layer which using the navigation requests a markdown page and converts it to HTML
     * @param {String[]} route The url navigation string
     * @returns {Promise<[Number, String]>} The converted markdown page and the response status
     */
    service(route) {
        return new Promise((resolve) => {
            this.fs.readFile(route, "utf8", (error, data) => {
                if (error) {
                    resolve([404, "<center><h1>ERROR 404 NOT FOUND</h1><p>This file wasn't found</p></center>"]);
                } else {
                    resolve([200, this.MdToHtml.converter(data)]);
                }
            });
        });
    }
}

<|start_filename|>resources/static/css/levelsDrag.css<|end_filename|>
* {
    box-sizing: border-box;
}

body {
    color: #dcddde;
    background-color: #202225;
    font-family: Gill Sans, sans-serif;
    margin: 15px;
}

#mainHead {
    width: 800px;
    margin-left: auto;
    margin-right: auto;
}

#dropbox {
    width: 800px;
    height: 375px;
    max-width: 1000px;
    max-height: 500px;
    background-color: #2f3136;
    padding: 15px;
    margin-top: 50px;
    margin-left: auto;
    margin-right: auto;
    margin-bottom: 20px;
    border-radius: 5px;
    border: 2px solid #dcddde;
    position: relative;
    cursor: pointer;
    user-select: none;
    transition: 0.5s;
}

#dropbox .innerline {
    width: 100%;
    height: 100%;
    text-align: center;
    border-radius: 5px;
    border: 2px dashed #dcddde;
    position: relative;
}

#dropbox .innerline .item {
    left: 0;
    right: 0;
    position: absolute;
    transition: 0.5s;
}

#dropbox .innerline .picture {
    width: 130px;
    height: 130px;
    margin-left: auto;
    margin-right: auto;
    top: 80px;
}

#dropbox .innerline .text {
    top: 240px;
}

#dropbox #file_upload {
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    position: absolute;
    opacity: 0;
}

#error {
    margin-left: 10%;
}

#tempform {
    opacity: 0;
}

<|start_filename|>api/loggers/ErrorLogs.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name ErrorLogs
 * @typedef {Object} ErrorLogs
 */
module.exports = class ErrorLogs {

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method init
     * @summary The main method which will report an error
     * @description The init method where all the main web app config is located and where the web app is started
     * @param {String} error The error message
     * @param {String} section The API section where the error occurred 
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    init(error, section, globalData) {
        if (globalData.config.settings.logging.errorLogging) {
            console.error(
                "\n\nError case #%s\nTime of error: %s\nSection: %s\n\x1b[31m%s\x1b[0m",
                globalData.errorCase++,
                Date.now(),
                section,
                error
            );
        }
    }
}

<|start_filename|>api/background/OpenLoop.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name OpenLoop
 * @typedef {Object} OpenLoop 
 */
module.exports = class OpenLoop {

    /**
     * @public
     * @type {OpenLoop}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see OpenLoop
     * @param {Object} [globalData={}] An object containing all the values and methods the application needs
     */
    constructor(globalData = {}) {
        if (OpenLoop.INSTANCE) {
            return OpenLoop.INSTANCE;
        } else {
            Object.keys(globalData.graphEntries).forEach((key) => {
                if (globalData.config.settings.endpoints
                    [`enable${globalData.graphEntries[key].settingsName}Endpoint`]) {
                    this[key] = 
                        new (require(`../services/${globalData.graphEntries[key].baseClass}.js`));
                }
            });

            OpenLoop.INSTANCE = this;
        }
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method init
     * @summary The initializer method
     * @description The loop initializer which will activate every 6 minutes and time test all services which are activated 
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    init(globalData) {
        setInterval(() => {
            Object.keys(globalData.graphEntries).forEach((key) => {
                if (globalData.config.settings.endpoints
                    [`enable${globalData.graphEntries[key].settingsName}Endpoint`]) {
                    OpenLoop.INSTANCE.timeTest(globalData.graphEntries[key], key).then((time) => {
                        globalData.graphEntries[key].data.shift();
                        globalData.graphEntries[key].data[9] = time;
                    });
                }
            });
        }, 360000);
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method timeTest
     * @summary The time test method 
     * @description The test method which will set a start time, request the service, awaits the response and does a delta calculation on the time afterwards and returns the result
     * @param {Object} testPart The test info from the @see globalData
     * @param {String} key The service to test
     * @returns {Promise<Number>} The response time
     */
    timeTest(testPart, key) {
        return new Promise((resolve) => {
            let time = Date.now();

            if (testPart.async) {
                OpenLoop.INSTANCE[key].service(
                    testPart.testData[0],
                    testPart.testData[1],
                    testPart.testData[2]
                ).then(() => {
                    resolve(Date.now() - time);
                });
            } else {
                OpenLoop.INSTANCE[key].service(
                    testPart.testData[0],
                    testPart.testData[1],
                    testPart.testData[2]
                );
                resolve(Date.now() - time);
            }
        });
    }
}

<|start_filename|>api/controllers/DocumentationController.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name DocumentationController
 * @typedef {Object} DocumentationController
 */
module.exports = class DocumentationController {

    /**
     * @private
     * @type {Object}
     * @name UrlToGlobalUrlArray
     */
    UrlToGlobalUrlArray = new (require("../converters/UrlToGlobalUrlArray"));

    /**
     * @private
     * @type {Object}
     * @name UrlToParams
     */
    UrlToParams = new (require("../converters/UrlToParams"));

    /**
     * @private
     * @type {Object}
     * @name ParamsToRoute
     */
    ParamsToRoute = new (require("../converters/documentation/ParamsToRoute"));

    /**
     * @private
     * @type {Object}
     * @name DocumentationIndexService
     */
    DocumentationIndexService = new (require("../services/documentation/DocumentationIndexService"));

    /**
     * @private
     * @type {Object}
     * @name DocumentationDataService
     */
    DocumentationDataService = new (require("../services/documentation/DocumentationDataService"))

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary endpoint listener initializer
     * @description endpoints:
     * /api/docs,
     * /api/docs/{navigation},
     * /api/docs/res/{navigation}
     * @param {Object} app The express app with all the necessary methods to use the controllers
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    controller(app, globalData) {
        const classObject = this;

        app.all(this.UrlToGlobalUrlArray.converter("/api/docs"), (req, res) => {
            const params = classObject.UrlToParams.converter(req.params);
            let route = classObject.ParamsToRoute.converter(params);

            if (params[0] == "res") {
                params.shift();
                route = route.replace("/res", "");
                if (params[0]) {
                    classObject.DocumentationDataService.service(route).then((response) => {
                        res.status(response[0]).type("html").send(response[1]);
                    });
                }
            } else {
                classObject.DocumentationIndexService.service(params).then((response) => {
                    res.status(response[0]).type("html").send(response[1]);
                }).catch((error) => {
                    globalData.errorFunc(error, "docs", globalData);
                    res.status(500).type("html").send("<center><h1>INTERNAL SERVER ERROR 500</h1></center>");
                });
            }
        });
    }
}

<|start_filename|>resources/static/scripts/docs.js<|end_filename|>
const xmlHttp = new XMLHttpRequest();
function inithighlight() {
    document.querySelectorAll("pre code").forEach((block) => {
        hljs.highlightBlock(block);
    });
}

function openPage(url, thisObject) {
    xmlHttp.open("GET", `/api/docs/res/${url}`);
    xmlHttp.onload = () => {
        if (thisObject) {
            document.querySelectorAll(".highlight")[0].classList.remove("highlight");
            thisObject.classList.add("highlight");
        }
        document.getElementById("body").innerHTML = xmlHttp.response;
        inithighlight();
    };
    xmlHttp.send(null);
}

<|start_filename|>start.js<|end_filename|>
/**
 * @private
 * @type {Object}
 * @name fs
 */
const fs = require("fs");

/**
 * @private
 * @type {Object}
 * @name configData
 */
const configData = require("./config.json");

/**
 * @private
 * @type {Object}
 * @name StartupLogs
 */
const StartupLogs = new (require("./api/loggers/StartupLogs"));

/**
 * @private
 * @type {Object}
 * @name WebAppStart
 */
const WebAppStart = require("./WebAppStart");

/**
 * @public
 * @type {Object}
 * @name globalData
 */
const globalData = {
    startDate: Date.now(),
    visits: 0,
    browser: {},
    errorCase: 1,
    errorFunc: new (require("./api/loggers/ErrorLogs"))().init,
    config: configData,
    graphEntries: {
        endpoints: {
            baseClass: "gdEndpoint/GdEndpointDataService",
            settingsName: "GdEndpoints",
            async: true,
            testData: [
                ["daily"],
                {
                    errorCase: 0,
                    errorFunc: new (require("./api/loggers/ErrorLogs"))().init,
                    config: configData
                }
            ],
            data: Array(10).fill(0),
            color: "#A51D38"
        },
        saves: {
            baseClass: "save/SelectiveLevelSaveService",
            settingsName: "Saves",
            async: false,
            testData: [
                {
                    errorCase: 0,
                    errorFunc: new (require("./api/loggers/ErrorLogs"))().init,
                    config: configData
                },
                "",
                0
            ],
            data: Array(10).fill(0),
            color: "#1D3FA5"
        },
        docs: {
            baseClass: "documentation/DocumentationIndexService",
            settingsName: "Documentations",
            async: true,
            testData: [[]],
            data: Array(10).fill(0),
            color: "#27A51D"
        },
        valueNames: {
            baseClass: "valueName/ValueNameDataService",
            settingsName: "ValueNames",
            async: false,
            testData: [""],
            data: Array(10).fill(0),
            color: "#951DA5"
        }
    }
}

/**
 * @private
 * @type {Object}
 * @name OpenLoop
 */
const OpenLoop = new (require("./api/background/OpenLoop"))(globalData);

/**
 * @public
 * @type {String[]}
 * @name args
 */
const args = process.argv

/**
 * @public
 * @since 0.3.0
 * @version 0.1.0
 * @function init
 * @summary The main runner function
 * @description The run function which will start the API and all its dependencies
 */
function run() {
    // Setting test data
    globalData.graphEntries.saves.testData[1] = 
        Buffer.from(fs.readFileSync("testData/CCLocalLevels.dat", "utf8")).toString("base64");
        // Thx for the light save Alten ;)

    // Get all the startup logs
    StartupLogs.init(globalData);

    // Starting loop
    OpenLoop.init(globalData);

    // Giving the launch data and start listening
    if (configData.settings.expressWebServer) {
        (new WebAppStart).init(globalData);
    }
}

/**
 * @public
 * @since 0.3.0
 * @version 0.1.0
 * @function test
 * @summary !in development!
 * @description !in development!
 */
function test() {

}

if (!args[2] || args[2] == "run") {
    run();
} else if (args[2] == "test") {
    test();
}

<|start_filename|>api/controllers/GdEndpointController.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name GdEndpointsController
 * @typedef {Object} GdEndpointsController
 */
module.exports= class GdEndpointsController {

    /**
     * @private
     * @type {Object}
     * @name UrlToGlobalUrlArray
     */
    UrlToGlobalUrlArray = new (require("../converters/UrlToGlobalUrlArray"));
    
    /**
     * @private
     * @type {Object}
     * @name UrlToParams
     */
    UrlToParams = new (require("../converters/UrlToParams"));

    /**
     * @private
     * @type {Object}
     * @name GdEndpointIndexService
     */
    GdEndpointIndexService = new (require("../services/gdEndpoint/GdEndpointIndexService"));

    /**
     * @private
     * @type {Object}
     * @name GdEndpointDataService
     */
    GdEndpointDataService = new (require("../services/gdEndpoint/GdEndpointDataService"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary endpoint listener initializer
     * @description endpoints:
     * /api/endpoint,
     * /api/endpoint/{type}/{values}
     * @param {Object} app The express app with all the necessary methods to use the controllers
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    controller(app, globalData) {
        const classObject = this;

        app.all(this.UrlToGlobalUrlArray.converter("/api/endpoint"), (req, res) => {
            const params = classObject.UrlToParams.converter(req.params);
            if (params[0]) {
                classObject.GdEndpointDataService.service(params, globalData).then((response) => {
                    res.status(response[0]).type("json").send(JSON.stringify(response[1], null, 2));
                });
            } else {
                classObject.GdEndpointIndexService.service(globalData).then((response) => {
                    res.status(response[0]).type("html").send(response[1]);
                });
            }
        });
    }
}

<|start_filename|>api/services/documentation/DocumentationIndexService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name DocumentationIndexService
 * @typedef {Object} DocumentationIndexService
 */
module.exports = class DocumentationIndexService {

    /**
     * @private
     * @type {Object}
     * @name fs
     */
    fs = require("fs");

    /**
     * @private
     * @type {Object}
     * @name LowerCaseWordToTitleWord
     */
    LowerCaseWordToTitleWord = new (require("../../converters/LowerCaseWordToTitleWord"));

    /**
     * @private
     * @type {Object}
     * @name UrlToRoute
     */
    UrlToRoute = new (require("../../converters/documentation/ParamsToRoute"));

    /**
     * @private
     * @type {Object}
     * @name DocumentationDataService
     */
    DocumentationDataService = new (require("./DocumentationDataService"));

    /**
     * @private
     * @since 0.2.0
     * @version 0.1.0
     * @type {String}
     * @name onclick
     * @summary The onclick string
     */
    onclick = "onclick=\"openPage('%URL%', this)\"";

    /**
     * @private
     * @since 0.2.0
     * @version 0.1.0
     * @type {String}
     * @name title
     * @summary The title element string
     */
    title = `<h3 ${this.onclick} class="section title %HIGH%">
        %CONTENT%
    </h3>`;

    /**
     * @private
     * @since 0.2.0
     * @version 0.1.0
     * @type {String}
     * @name topic
     * @summary The topic element string
     */
    topic = `<span ${this.onclick} class="section topic %HIGH%">
        <p>%CONTENT%</p>
    </span>`;

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base service to generate the main body
     * @description The main service layer which generates the docs body and sets the navigation highlight to right place
     * @param {String[]} params The url navigation string
     * @returns {Promise<[Number, String]>} The HTML page and the response status
     */
    service(params) {
        return new Promise((resolve) => {
            let ArrayFile = [];
            let fileToRead = this.UrlToRoute.converter(params);
            
            this.fs.readFile("resources/pages/docs/base.html", "utf8", (error, content) => {
                if (error) {
                    resolve([500, "<center><h1>INTERNAL SERVER ERROR 500</h1></center>"]);
                } else {
                    ArrayFile = fileToRead.split("resources/docs/")[1].split("/");
                    content = content.replace("%URL%", ArrayFile.join("/"));
                    resolve([200, content.replace("%NAV%", this.constructNav(ArrayFile))]);
                }
            });
        });
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method constructNav
     * @summary The navigation constructing
     * @description The constructing method converting the navigation HTML blocks to the full navigation
     * @param {String[]} fileToRead The url navigation array
     * @returns {String} The navigation HTML block
     */
    constructNav(fileToRead) {
        let out = "";
        let files = [];
        const dir = "resources/docs/";
        const folders = this.filesSorter(this.fs.readdirSync(dir), dir);

        for (let i = 0; i < folders.length; i++) {
            out += this.navContentMaker([folders[i], "index.md"], fileToRead);
            files = this.filesSorter(this.fs.readdirSync(dir + folders[i]), dir + folders[i]);

            for (let j = 0; j < files.length; j++) {
                if (files[j] != "index.md") {
                    out += this.navContentMaker([folders[i], files[j]], fileToRead);
                }
            }
        }

        return out;
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method navContentMaker
     * @summary The navigation link generator
     * @description This will generate a navigation link and set the highlight if needed
     * @param {String[]} names The current file to generate a link for
     * @param {String[]} fileToRead The url navigation array
     * @returns {String} The link HTML block
     */
    navContentMaker(names, fileToRead) {
        let out = "";

        if (names[1] == "index.md") {
            out = this.title.replace("%CONTENT%", this.LowerCaseWordToTitleWord.converter(names[0]));
        } else {
            out = this.topic.replace("%CONTENT%", this.LowerCaseWordToTitleWord.converter(names[1]));
        }
        if (JSON.stringify(names) == JSON.stringify(fileToRead)) {
            out = out.replace("%HIGH%", "highlight");
        } else {
            out = out.replace("%HIGH%", "");
        }

        return out.replace("%URL%", `${names[0]}/${names[1]}`);
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method filesSorter
     * @summary A simple file sorter
     * @description The file sorter sorting all files based on the edit date allowing you to easily order it
     * @param {String[]} files The files array to sort
     * @param {String} dir The directory where the files are located in
     * @returns {String[]} The ordered files
     */
    filesSorter(files, dir) {
        files.sort((a, b) => 
            this.fs.statSync(dir + a).mtime.getTime() - this.fs.statSync(dir + b).mtime.getTime());

        return files;
    }
}

<|start_filename|>api/controllers/AnalyticsController.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name AnalyticsController
 * @typedef {Object} AnalyticsController
 */
module.exports = class AnalyticsController {

    /**
     * @private
     * @type {Object}
     * @name UrlToParams
     */
    UrlToParams = new (require("../converters/UrlToParams"));

    /**
     * @private
     * @type {Object}
     * @name UrlToGlobalUrlArray
     */
    UrlToGlobalUrlArray = new (require("../converters/UrlToGlobalUrlArray"));

    /**
     * @private
     * @type {Object}
     * @name AnalyticsDataService
     */
    AnalyticsDataService = new (require("../services/analytics/AnalyticsDataService"));

    /**
     * @private
     * @type {Object}
     * @name AnalyticsIndexService
     */
    AnalyticsIndexService = new (require("../services/analytics/AnalyticsIndexService"))

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary endpoint listener initializer
     * @description endpoints:
     * /analytics,
     * /analytics/*,
     * /analytics/data/*
     * @param {Object} app The express app with all the necessary methods to use the controllers
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    controller(app, globalData) {
        const classObject = this;

        app.all(this.UrlToGlobalUrlArray.converter("/analytics"), (req, res) => {
            const params = classObject.UrlToParams.converter(req.params);

            if (params[0] == "data") {
                this.AnalyticsDataService.service(globalData).then((response) => {
                    res.status(response[0]).type("json").send(response[1]);
                });
            } else {
                this.AnalyticsIndexService.service(globalData).then((response) => {
                    res.status(response[0]).type("html").send(response[1]);
                });
            }
        });
    }
}

<|start_filename|>api/converters/level/LevelDataToJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LevelDataToJson
 * @typedef {Object} LevelDataToJson
 */
module.exports = class LevelDataToJson {

    /**
     * @public
     * @type {LevelDataToJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see LevelDataToJson
     */
    constructor() {
        if (!LevelDataToJson.INSTANCE) {
            LevelDataToJson.INSTANCE = this;
        }
        return LevelDataToJson.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {Object} json
     * @param {String} data
     */
    converter(json, data) {
        let tempArray = [{}];
        let tempJson = {};

        // Splitting color and object data
        tempArray = data.split("|,");
        // Splitting global variables and values
        tempArray[0] = tempArray[0].split(",");
        // Splitting the colors
        tempArray[0][1] = tempArray[0][1].split("|");
        tempJson[tempArray[0][0]] = {};

        LevelDataToJson.INSTANCE.color(tempJson[tempArray[0][0]], tempArray[0]);
        LevelDataToJson.INSTANCE.data(tempJson, tempArray[1]);

        json.k4 = tempJson;
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method color
     * @param {Object} tempJson
     * @param {Object} tempArray
     */
    color(tempJson, tempArray) {
        let tempValue;
        if (tempArray[1][0]) {
            tempJson.Colors = {};
            for (let i in tempArray[1]) {
                tempValue = tempArray[1][i].split("_");
                tempJson.Colors[`Color${tempValue[15]}`] = {};
                for (let j = 0; j < tempValue.length; j += 2) {
                    tempJson.Colors[`Color${tempValue[15]}`][tempValue[j]] = tempValue[j + 1];
                }
            }
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method data
     * @param {Object} tempJson
     * @param {Object} tempArray
     */
    data(tempJson, tempArray) {
        if (tempArray) {
            tempArray = tempArray.substring(0, tempArray.length - 1).split(";");
            tempArray[0] = tempArray[0].split(",");
            LevelDataToJson.INSTANCE.jsonPush(tempJson, tempArray[0], 0, true);
            LevelDataToJson.INSTANCE.kA14(tempJson);
            LevelDataToJson.INSTANCE.objects(tempJson, tempArray);
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method kA14
     * @param {Object} tempJson
     */
    kA14(tempJson) {
        tempJson.kA14 = tempJson.kA14.substring(0, tempJson.kA14.length - 1).split("~");
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method objects
     * @param {Object} tempJson
     * @param {Object[]} tempArray
     */
    objects(tempJson, tempArray) {
        tempJson.Objects = [];
        tempArray.shift();
        LevelDataToJson.INSTANCE.jsonPush(tempJson.Objects, tempArray, 0, false);
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method jsonPush
     * @param {Object} json
     * @param {Object[]} toBePushed
     * @param {Number} offset
     * @param {Boolean} simple
     * @param {String} [split=","]
     */
    jsonPush(json, toBePushed, offset, simple, split = ",") {
        if (simple) {
            for (let i = offset; i < toBePushed.length; i += 2) {
                json[toBePushed[i]] = toBePushed[i + 1];
            }
        } else {
            for (let i = offset; i < toBePushed.length; i++) {
                toBePushed[i] = toBePushed[i].split(split);
                json[i] = {};
                for (let j = 0; j < toBePushed[i].length; j += 2) {
                    json[i][toBePushed[i][j]] = toBePushed[i][j + 1];
                }
            }
        }
    }
}

<|start_filename|>WebAppStart.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name WebAppStart
 * @typedef {Object} WebAppStart
 */
module.exports = class WebAppStart {

    /**
     * @private
     * @type {Object}
     * @name http
     */
    http = require("http");

    /**
     * @private
     * @type {Object}
     * @name express
     */
    express = require("express");

    /**
     * @private
     * @type {Object}
     * @name bodyParser
     */
    bodyParser = require("body-parser");

    /**
     * @private
     * @type {Object}
     * @name useragent
     */
    useragent = require("express-useragent");

    /**
     * @private
     * @type {Object}
     * @name GlobalRequestController
     */
    GlobalRequestController = new (require("./api/controllers/GlobalRequestController"));

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @type {Object}
     * @name controllers 
     * @summary An array containing all controller class objects
     */
    controllers = {
        GdEndpoints: new (require("./api/controllers/GdEndpointController")),
        ValueNames: new (require("./api/controllers/ValueNameController")),
        Documentations: new (require("./api/controllers/DocumentationController")),
        Analytics: new (require("./api/controllers/AnalyticsController")),
        Saves: new (require("./api/controllers/LevelSaveController"))
    };

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method init
     * @summary The web app initializer
     * @description The init method where all the main web app config is located and where the web app is started
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    init(globalData) {
        // Making the app
        const app = this.express();

        // Setting the max data limit (you really don't need more than this)
        app.use(this.bodyParser.json({limit: "50mb"}));
        app.use(this.bodyParser.urlencoded({limit: "50mb", extended: true}));
        app.use(this.bodyParser.urlencoded({extended: true}));

        // Useragent logger
        app.use(this.useragent.express());

        // Setting the static files address
        app.use(this.express.static("resources/static"));

        // Doing some default actions on a visit
        app.use((req, res, next) => this.GlobalRequestController.controller(globalData, req, next));

        // Since this is only an API for now I will just auto redirect to the docs
        app.all(["/", "/api"], (req, res) => {
            res.redirect("/api/docs");
        });

        // Calling the endpoint visit event listener initializers
        Object.keys(this.controllers).forEach((key) => {
            if (globalData.config.settings.endpoints[`enable${key}Endpoint`]) {
                this.controllers[key].controller(app, globalData);
            }
        });

        // Starting the listener
        const server = this.http.createServer(app);
        server.listen(globalData.config.values.webServer.port || process.env.port);
    }
}

<|start_filename|>api/controllers/LevelSaveController.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LevelsSaveController
 * @typedef {Object} LevelsSaveController
 */
module.exports = class LevelsSaveController {

    /**
     * @private
     * @type {Object}
     * @name path
     */
    path = require("path");

    /**
     * @private
     * @type {Object}
     * @name UrlToParams
     */
    UrlToParams = new (require("../converters/UrlToParams"));

    /**
     * @private
     * @type {Object}
     * @name UrlToGlobalUrlArray
     */
    UrlToGlobalUrlArray = new (require("../converters/UrlToGlobalUrlArray"));

    /**
     * @private
     * @type {Object}
     * @name LevelsIndexService
     */
    LevelsIndexService = new (require("../services/save/LevelSaveIndexService"));

    /**
     * @private
     * @type {Object}
     * @name SelectiveLevelSaveService
     */
    SelectiveLevelSaveService = new (require("../services/save/SelectiveLevelSaveService"));

    /**
     * @private
     * @type {Object}
     * @name EncodedSaveToAcceptedJson
     */
    EncodedSaveToAcceptedJson = new (require("../converters/save/EncodedSaveToAcceptedJson"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary endpoint listener initializer
     * @description endpoints:
     * /api/save/level,
     * /api/save/level/*,
     * /api/save/level/{levelArrayIndex},
     * /api/save/level/json,
     * /api/save/level/raw
     * @param {Object} app The express app with all the necessary methods to use the controllers
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    controller(app, globalData) {
        const classObject = this;
        const url = this.UrlToGlobalUrlArray.converter("/api/save/level");

        app.post(url, (req, res) => {
            const params = classObject.UrlToParams.converter(req.params);
            let data;
            let tempResponse;

            if (req.body.data) {
                if (Number.isInteger(parseInt(params[0]))) {
                    tempResponse = this.SelectiveLevelSaveService.service(globalData, req.body.data, params[0]);
                    res.status(tempResponse[0]).type("json").send(JSON.stringify(tempResponse[1], null, 2));
                } else {
                    try {
                        data = this.EncodedSaveToAcceptedJson.converter(req.body.data);
                    } catch(error) {
                        globalData.errorFunc(error, "level-save", globalData);
                        return res.status(400).type("json").send(JSON.stringify({
                            "ERROR": "There was an error while decrypting your data."
                        }, null, 2));
                    }
                    if (params[0] == "raw") {
                        try {
                            res.status(200).type("json").send(JSON.stringify(data, null, 2));
                        } catch(error) {
                            globalData.errorFunc(error, "level-save-raw", globalData);
                            res.status(400).type("json").send(JSON.stringify({
                                "ERROR": "There was an error while parsing your data."
                            }, null, 2));
                        }
                    } else if (params[0] == "json") {
                        data.forEach((level, index) => {
                            tempResponse = this.SelectiveLevelSaveService.service(globalData, JSON.stringify(level));
                            data[index] = tempResponse[1];
                        });
                        res.status(200).type("json").send(JSON.stringify(data, null, 2));
                    } else {
                        this.LevelsIndexService.service(globalData, data).then((response) => {
                            res.status(response[0]).type("html").send(response[1]);
                        });
                    }
                }
            } else {
                res.redirect(req.url);
            }
        });

        app.get(url, (req, res) => {
            res.status(200).sendFile(
                classObject.path.join(__dirname, "../../resources/pages/levelSave/dragBase.html"));
        });
    }
}

<|start_filename|>resources/pages/endpoints/base.html<|end_filename|>
<html lang="en">
    <head>
        <link rel="stylesheet" type="text/css" href="/css/endpoints.css">
        <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
        <script defer src="/scripts/endpoint.js"></script>
    </head>
    <body onscroll="navscroll()">
        <div class="geometricbg"></div>
        <div class="nav">
            <div class="logo"></div>
            <a href=".">home</a>
            <p>more soon..</p>
        </div>
        <div id="navbutton"></div>
        <div class="content TomStopScreaming" id="content">
            <h1>Types</h1>
            <p>
                The word after a <span>/:</span> indicates what value should go there and a <span>?</span> indicates that the value is optional
            </p>
            <hr>
            %body%
        </div>
    </body>
</html>

<|start_filename|>api/services/save/SelectiveLevelSaveService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name SelectiveLevelSaveService
 * @typedef {Object} SelectiveLevelSaveService
 */
module.exports = class SelectiveLevelSaveService {

    /**
     * @private
     * @type {Object}
     * @name EncodedSaveToAcceptedJson
     */
    EncodedSaveToAcceptedJson = new (require("../../converters/save/EncodedSaveToAcceptedJson"));

    /**
     * @private
     * @type {Object}
     * @name EncodedLevelSaveParametersToJson
     */
    EncodedLevelSaveParametersToJson = new (
        require("../../converters/save/EncodedLevelSaveParametersToJson"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The selective level data parser service
     * @description The data service converting encoded levels to an array and then call the right index or parses an already decoded level object 
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @param {String} data The level object or save data
     * @param {Number} index The level index it needs to request
     * @returns {[Number, Object]} The level object and the response status 
     */
    service(globalData, data, index) {
        let outputData;

        try {
            if (data.charAt(0) == "{") {
                outputData = JSON.parse(data);
            } else {
                outputData = this.EncodedSaveToAcceptedJson.converter(data)[index];
            }
            this.EncodedLevelSaveParametersToJson.converter(outputData);
            return [200, outputData];
        } catch(error) {
            globalData.errorFunc(error, "level-save-select", globalData);
            return [400, {"ERROR": "There was an error while decoding your data."}];
        }
    }
}

<|start_filename|>api/services/gdEndpoint/GdEndpointDataService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name GdEndpointDataService
 * @typedef {Object} GdEndpointDataService
 */
module.exports = class GdEndpointDataService {

    /**
     * @private
     * @type {Object}
     * @name http
     */
    http = require("http");

    /**
     * @private
     * @type {Object}
     * @name endpointProperties
     */
    endpointProperties = require("../../values/endpointProperties.json");

    /**
     * @private
     * @type {Object}
     * @name SmallGdResponseToJson
     */
    SmallGdResponseToJson = new (require("../../converters/gdEndpoint/SmallGdResponseToJson"));

    /**
     * @private
     * @type {Object}
     * @name BigResponseToJson
     */
    BigResponseToJson = new (require("../../converters/gdEndpoint/BigResponseToJson"));

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The endpoint data service
     * @description The main service method processing a GD request and calls the response parsers
     * Type info:
     * 0: Default split
     * 1: Multi split
     * 2: Multi data sections
     * 3: Alt misc
     * 4: Without # data
     * 5: Default split with alt misc data
     * 6: Split based on index
     * @param {String[]} params The params array containing all data which needs to go into the post data
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {Promise<[Number, Object]>} The gd response object and the response status
     */
    service(params, globalData) {
        return new Promise((resolve) => {
            let output = {};
            // Setting valid property or gauntlets since daily requires no params
            const type = this.endpointProperties[params[0].toLowerCase()] || 
                this.endpointProperties.daily;

            // Checking if too many leaderboards entries are requested and converting a number param to a valid type
            if (params[0] == "leaderboard") {
                if (parseInt(params[1])) {
                    if (parseInt(params[1]) > 10000) {
                        resolve([400, {"ERROR":"Too much to request"}]);
                    } else {
                        if (parseInt(params[2]) == 2) {
                            params[2] = "creators";
                        } else if (params[2] != "creators") {
                            params[2] = "top";
                        }
                    }
                } else {
                    resolve([400, {"ERROR":"Invalid request"}]);
                }
            }

            this.postRequest(this.setValue(type.post, params), type.url, globalData).then((data) => {
                if (parseInt(data) >= 0 && data != "") {
                    if (type.type.indexOf(0) != -1 || type.type.indexOf(5) != -1 || type.type.indexOf(6) != -1) {
                        // type 0, 5, 6
                        this.SmallGdResponseToJson.converter(data, type, output);
                    } else {
                        // type 1-4
                        output = [];
                        this.BigResponseToJson.converter(data, type, output);
                    }
                    resolve([200, output]);
                } else {
                    resolve([404, {"ERROR":"Not found"}]);
                }
            }).catch((error) => {
                globalData.errorFunc(error, "gdEndpoint-request", globalData);
                resolve([400, {"ERROR":"Bad request"}]);
            });
        });
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method setValue
     * @summary A simple query template formatter
     * @description A query constructor which will replace all value{Number} with the right value
     * @param {String} post The post query template
     * @param {String[]} params The params which should be mixed into the post template
     * @returns {String} The properly formatted post query
     */
    setValue(post, params) {
        let dar = post.split("=value");
        let output = dar.shift();
        dar.forEach((list) => output += `=${params[list.charAt(0)] || 0}${list.substring(1)}`);
        return output;
    }

    /**
     * @async
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @summary The post request method to extract the GD data
     * @description A request method sending a post request with the data to the GD servers and returning the data
     * @param {String} params The post query
     * @param {String} endpoint The endpoint which should be requested
     * @returns {Promise<String>} The gd endpoint response (or error)
     */
    postRequest(params, endpoint, globalData) {
        return new Promise((resolve, reject) => {
            const options = {
                host: globalData.config.values.endpoints.targetServer,
                port: 80,
                path: `/database/${endpoint}`,
                method: "POST",
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Content-Length': Buffer.byteLength(params)
                }
            };
            const req = this.http.request(options, (res) => {
                res.setEncoding("utf8");
                res.on("data", (chunk) => {
                    resolve(chunk);
                });
                res.on("error", (error) => {
                    reject(error);
                });
            });

            req.write(params);
            req.end();
        });
    }
}

<|start_filename|>api/converters/save/LevelSaveToLevelArray.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LevelSaveToLevelArray
 * @typedef {Object} LevelSaveToLevelArray
 */
module.exports = class LevelSaveToLevelArray {

    /**
     * @public
     * @type {LevelSaveToLevelArray}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see LevelSaveToLevelArray
     */
    constructor() {
        if (!LevelSaveToLevelArray.INSTANCE) {
            LevelSaveToLevelArray.INSTANCE = this;
        }
        return LevelSaveToLevelArray.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} plistXml
     * @returns {String[]} 
     */
    converter(plistXml) {
        /**
         * @type {Object[]}
         */
        const dirs = plistXml.split("</d><k>LLM_02</k>")[0]
            .split("<d><k>_isArr</k><t /><k>k_0</k>")[1]
            .split("<k>k_");
        let levels = [dirs[0]];
        
        for (let i = 1; i < dirs.length; i++) {
            dirs[i] = dirs[i].split(i + "</k>");
            dirs[i].shift();
            levels.push(dirs[i].join(i + "</k>"));
        }

        return levels;
    }
}

<|start_filename|>api/services/analytics/AnalyticsDataService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name AnalyticsDataService
 * @typedef {Object} AnalyticsDataService
 */
module.exports = class AnalyticsDataService {

    /**
     * @private
     * @type {Object}
     * @name v8
     */
    v8 = require("v8");
    
    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @type {Number}
     * @name totalMemory
     * @summary The total heap memory limit
     */
    totalMemory = this.v8.getHeapStatistics().heap_size_limit;

    /**
     * @async
     * @private
     * @type {Function}
     * @name timeTest
     */
    timeTest = require("../../background/OpenLoop").INSTANCE.timeTest;

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base analytics data service
     * @description The constructing method converting the navigation HTML blocks to the full navigation
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {Promise<[Number, Object]>} The analytics data and response status
     */
    service(globalData) {
        return new Promise(async (resolve) => {
            const memoryUsage = process.memoryUsage().heapUsed;
            let errorProgress = this.calculateHealth(globalData);
            let keys = Object.keys(globalData.graphEntries);
            let outData = Array(2);
            let eColor = "#418E51";
            let tempI = 0;
    
            if (errorProgress < 0) {
                eColor = "#8E4141";
                if (errorProgress <= -100) {
                    errorProgress = 100;
                } else {
                    errorProgress = Math.abs(errorProgress);
                }
            }

            for (let i in keys) {
                if (globalData.config.settings.endpoints
                    [`enable${globalData.graphEntries[keys[i]].settingsName}Endpoint`]) {
                    try {
                        outData[tempI] = await this.timeTest(globalData.graphEntries[keys[i]], keys[i]);
                    } catch(error) {
                        outData[tempI] = 0;
                        globalData.errorFunc(error, "analytics-base", globalData);
                    }
                    tempI++;
                }
            }

            resolve([200, {
                memoryText: `<p>Memory usage: ${Math.round(memoryUsage / 1024 / 1024 * 100) / 100}Mb</p>`,
                memoryProgress: `<div class="progress">
                        <span style="width: ${Math.round(memoryUsage / (this.totalMemory / 100) * 10) / 10}%"></span>
                        <p>${Math.round(memoryUsage / (this.totalMemory / 100) * 100) / 100}%</p>
                    </div>`,
                errorProgress: `<div class="progress">
                        <span style="width: ${errorProgress}%; background-color: ${eColor}"></span>
                        <p>${this.calculateHealth(globalData)}%</p>
                    </div>`,
                extra: `<p>Visits: ${globalData.visits}</p>
                    <p>Uptime: ${Math.round((Date.now() - globalData.startDate) / 86400000 * 100) / 100} days</p>
                    <p>Total errors: ${globalData.errorCase - 1}</p>`,
                browser: this.generateBrowserInfo(globalData),
                chartData: this.generateChartData(globalData, outData, keys)
            }]);
        });
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method calculateHealth
     * @summary The API health calculation method
     * @description A method calculating the health based on the max errors per 100 visits @see config
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {Number} The current health
     */
    calculateHealth(globalData) {
        const maxErrors = globalData.visits || 1 / 100 * globalData.config.values.analytics.maxErrorsPer100Visits;

        return Math.round(((maxErrors - (globalData.errorCase - 1)) / (maxErrors / 100)) * 10) / 10;
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method generateBrowserInfo
     * @summary The user browser navigation constructor
     * @description This method generates the navigation for all the browsers and browser versions used to visit this API
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {String} The navigation HTML block
     */
    generateBrowserInfo(globalData) {
        let out = "";
        if (globalData.config.settings.analytics.showBrowserVisits) {
            const data = globalData.browser;
            const keys = Object.keys(data);
            let innerKeys;

            for (let i in keys) {
                out += `<span class="tr" onclick="version('${keys[i]}')">
                        <b>${keys[i]}:</b>
                        <p> ${data[keys[i]].visits} visits</p>
                    </span>
                    <span class="browserVersions" id="${keys[i]}">`;
                innerKeys = Object.keys(data[keys[i]].versions);
                for (let j in innerKeys) {
                    out += `<span class="ntr"><b>v${innerKeys[j]}</b><p> ${data[keys[i]].versions[innerKeys[j]]} visits</p></span>`;
                }
                out += "</span>";
            }
        } else {
            out = "denied";
        }
        
        return out;
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method generateChartData
     * @summary The chartData generator
     * @description This method generates the service response time data in the chart format which can be used by the frontend
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @param {Number[]} now The current response times
     * @param {String[]} keys the graph entry keys which need to be given
     * @returns {Object} The chart data object
     */
    generateChartData(globalData, now, keys) {
        let output = [];
        let tempI = 0;

        for (let i in keys) {
            if (globalData.config.settings.endpoints
                [`enable${globalData.graphEntries[keys[i]].settingsName}Endpoint`]) {
                output[tempI] = {
                    label: keys[i],
                    data: [],
                    borderColor: globalData.graphEntries[keys[i]].color
                };
                for (let j in globalData.graphEntries[keys[i]].data) {
                    output[tempI].data[j] = globalData.graphEntries[keys[i]].data[j];
                }
                output[tempI].data.push(now[tempI]);
                tempI++;
            }
        }

        return output;
    }
}

<|start_filename|>api/converters/save/EncodedSaveToAcceptedJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name EncodedSaveToAcceptedJson
 * @typedef {Object} EncodedSaveToAcceptedJson
 */
module.exports = class EncodedSaveToAcceptedJson {

    /**
     * @public
     * @type {EncodedSaveToAcceptedJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see EncodedSaveToAcceptedJson
     */
    constructor() {
        if (!EncodedSaveToAcceptedJson.INSTANCE) {
            EncodedSaveToAcceptedJson.INSTANCE = this;
        }
        return EncodedSaveToAcceptedJson.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name LevelArraysXmlToJson
     */
    LevelArraysXmlToJson = new (require("./LevelArraysXmlToJson"));

    /**
     * @private
     * @type {Object}
     * @name EncodedSaveToXmlString
     */
    EncodedSaveToXmlString = new (require("./EncodedSaveToXmlString"));

    /**
     * @private
     * @type {Object}
     * @name LevelSaveToLevelArray
     */
    LevelSaveToLevelArray = new (require("./LevelSaveToLevelArray"));
    
    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} data
     * @returns {Object[]}
     */
    converter(data) {
        return EncodedSaveToAcceptedJson.INSTANCE.LevelArraysXmlToJson.converter(
            EncodedSaveToAcceptedJson.INSTANCE.LevelSaveToLevelArray.converter(
                EncodedSaveToAcceptedJson.INSTANCE.EncodedSaveToXmlString.converter(data)));
    }
}

<|start_filename|>api/converters/UrlToGlobalUrlArray.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name UrlToGlobalUrlArray
 * @typedef {Object} UrlToGlobalUrlArray
 */
module.exports = class UrlToGlobalUrlArray {

    /**
     * @private
     * @type {UrlToGlobalUrlArray}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see UrlToGlobalUrlArray
     */
    constructor() {
        if (!UrlToGlobalUrlArray.INSTANCE) {
            UrlToGlobalUrlArray.INSTANCE = this;
        }
        return UrlToGlobalUrlArray.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} url
     * @returns {String[]}
     */
    converter(url) {
        return [url, `${url}/*`];
    }
}

<|start_filename|>api/controllers/ValueNameController.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name ValueNameController
 * @typedef {Object} ValueNameController
 */
module.exports = class ValueNameController {

    /**
     * @private
     * @type {Object}
     * @name UrlToGlobalUrlArray
     */
    UrlToGlobalUrlArray = new (require("../converters/UrlToGlobalUrlArray"));

    /**
     * @private
     * @type {Object}
     * @name UrlToParams
     */
    UrlToParams = new (require("../converters/UrlToParams"));

    /**
     * @private
     * @type {Object}
     * @name ValueNamesDataService
     */
    ValueNamesDataService = new (require("../services/valueName/ValueNameDataService"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary endpoint listener initializer
     * @description endpoints:
     * /analytics,
     * /api/valueNames/{jsonNavigation},
     * @param {Object} app The express app with all the necessary methods to use the controllers
     */
    controller(app) {
        const classObject = this;
        
        app.all(this.UrlToGlobalUrlArray.converter("/api/valueNames"), function(req, res) {
            const response = classObject.ValueNamesDataService.service(classObject.UrlToParams.converter(req.params));
            res.status(response[0]).type("json").send(JSON.stringify(response[1], null, 2));
        });
    }
}

<|start_filename|>resources/static/css/levels.css<|end_filename|>
* {
    box-sizing: border-box;
}

body {
    margin: 15px;
    background-color: #202225;
    color: #dcddde;
    font-family: Gill Sans, sans-serif;
}

.sideLevelSelect {
    position: relative;
    width: 380px;
    height: 110px;
    background-color: #36393f;
    padding: 10px;
    border-radius: 5px;
    margin-bottom: 15px;

}

.sideLevelSelect h3 {
    margin: 0;
}

.sideLevelSelect .view {
    padding: 10px;
    padding-left: 40px;
    padding-right: 40px;
    text-align: center;
    position: absolute;
    top: 10px;
    border: 2px solid #dcddde;
    color: #dcddde;
    border-radius: 5px;
    background-color: #202225;
}

.sideLevelSelect .data {
    position: absolute;
    bottom: 50px;
}

.sideLevelSelect .data2 {
    position: absolute;
    bottom: 25px;
}

.sideLevelSelect .right {
    right: 10px;
}

.sideLevelSelect .progress {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    border-bottom-left-radius: 5px;
    border-bottom-right-radius: 5px;
    height: 10px;
    background-color: #4f545c;
    overflow: hidden;
}

.sideLevelSelect .progress .wrapper {
    position: relative;
    width: 100%;
    height: 10px;
}

.sideLevelSelect .progress .wrapper .inner {
    position: absolute;
    top: 0;
    left: -5px;
    background-color: #418E51;
    height: 10px;
    margin-left: 0;
    margin-right: auto;
    border-radius: 5px;
    margin: 0;
}

.sideLevelSelect .progress .wrapper .practice {
    position: absolute;
    top: 0;
    background-color: #4DA4C4;
    height: 10px;
    margin-left: 0;
    margin-right: auto;
    border-radius: 5px;
    margin: 0;
}

<|start_filename|>api/values/songs.json<|end_filename|>
[
    "Stay Inside Me",
    "Stereo Madness",
    "Back On Track",
    "Polargeist",
    "Dry Out",
    "Base After Base",
    "Cant Let Go",
    "Jumper",
    "Time Machine",
    "Cycles",
    "xStep",
    "Clutterfunk",
    "Theory of Everything",
    "Electroman Adventures",
    "Clubstep",
    "Electrodynamix",
    "Hexagon Force",
    "Blast Processing",
    "Theory of Everything 2",
    "Geometrical Dominator",
    "Deadlocked",
    "Fingerdash",
    "Explorers",
    "Firebird",
    "The Seven Seas",
    "Viking Arena",
    "Airborne Robots",
    "Secret",
    "Payload",
    "Beast Mode",
    "Machina",
    "Years",
    "Frontlines",
    "Space Pirates",
    "Striker",
    "Embers",
    "Round 1",
    "Monster Dance Off",
    "Press Start",
    "Nock Em",
    "Power Trip"
]

<|start_filename|>api/services/analytics/AnalyticsIndexService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name AnalyticsIndexService
 * @typedef {Object} AnalyticsIndexService
 */
module.exports = class AnalyticsIndexService {

    /**
     * @private
     * @type {Object}
     * @name fs
     */
    fs = require("fs");

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @type {[Number, String]}
     * @name output
     * @summary The saved output after the first use
     */
    output;

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base analytics constructor service
     * @description The constructing method converting the navigation HTML blocks to the full navigation
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @returns {Promise<[Number, String]>} The analytics page and response status
     */
    service(globalData) {
        return new Promise((resolve) => {
            if (!this.output) {
                this.fs.readFile("resources/pages/analytics/base.html", "utf8", (error, content) => {
                    if (error) {
                        globalData.errorFunc(error, "analytics", globalData);
                        resolve([500, "<center><h1>INTERNAL SERVER ERROR 500</h1></center>"]);
                    }
                    this.output = [200,
                            	   content.replace(`"%REFRESH%"`, globalData.config.values.analytics.refreshRateMs || 2000)
                                          .replace(`"%ROUND%"`, globalData.config.values.analytics.chartRoundOn)
                                          .replace(`"%CLICK%"`, (
                                                (globalData.config.settings.analytics.allowChartInteraction) ? 
                                                    "" : ",onClick: (e) => e.stopPropagation()"))];
                    resolve(this.output);
                });
            } else {
                resolve(this.output);
            }
        });
    }
}

<|start_filename|>api/services/valueName/ValueNameDataService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name ValueNameDataService
 * @typedef {Object} ValueNameDataService
 */
module.exports = class ValueNameDataService {

    /**
     * @private
     * @type {Object}
     * @name valueForName
     */
    valueForName = require("../../values/valueForName.json");

    /**
     * @private
     * @type {Object}
     * @name UrlNavigationToResponseEntity
     */
    UrlNavigationToResponseEntity = new (require("../../converters/UrlNavigationToResponseEntity"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The navigation service method
     * @description The service layer in between the data layer and the client layer to convert the url to the right value translations directory
     * @param {String[]} params The url params array as object navigation path
     * @returns {[Number, Object]} The navigated value translation object and response status
     */
    service(params) {
        return this.UrlNavigationToResponseEntity.converter(
            params, this.valueForName);
    }
}

<|start_filename|>api/controllers/GlobalRequestController.js<|end_filename|>
module.exports = class GlobalRequestController {

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @type {String[]}
     * @name ignoreLinks
     * @summary The links which should not be logged as a visit
     */
    ignoreLinks = [
        "/analytics/data"
    ];

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method controller
     * @summary The controller method
     * @description The base method which makes sure that the url is valid and will go to the next listener
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @param {Object} req The request holding all info needed to inject into @see globalData
     * @param {Function} next The next function which makes sure it will get to the actual request after it's done
     */
    controller(globalData, req, next) {
        if (this.ignoreLinks.indexOf(req.url) == -1) {
            this.addVisits(globalData, req);
        }
        next();
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method addVisits
     * @summary The visit process method
     * @description The method which will inject all data which should be logged into @see globalData
     * @param {Object} globalData An object containing all the values and methods the application needs
     * @param {Object} req The request holding all info needed to inject into @see globalData
     */
    addVisits(globalData, req) {
        globalData.visits++;
        if (!globalData.browser[req.useragent.browser])
            globalData.browser[req.useragent.browser] = {visits: 0, versions: {}};

        globalData.browser[req.useragent.browser].versions[req.useragent.version] =
            globalData.browser[req.useragent.browser].versions[req.useragent.version] + 1 || 1;
        
        globalData.browser[req.useragent.browser].visits++;
    }
}

<|start_filename|>api/loggers/StartupLogs.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name StartupLogs
 * @typedef {Object} StartupLogs
 */
module.exports = class StartupLogs {

    /**
     * @private
     * @async
     * @type {Function}
     * @name exec
     */
    exec = require("child_process").exec;

    /**
     * @private
     * @since 0.2.0
     * @version 0.1.0
     * @type {Object}
     * @name dependencies
     * @summary The dependencies used by the program
     */
    dependencies = require("../../package.json").dependencies;

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method init
     * @summary The base analytics constructor service
     * @description The constructing method converting the navigation HTML blocks to the full navigation
     * @param {Object} globalData An object containing all the values and methods the application needs
     */
    async init(globalData) {
        let command = "";
        
        console.log(`\x1b[34mApp started at ${globalData.startDate}\x1b[0m`);
        if (globalData.config.settings.expressWebServer) {
            console.log(`\x1b[32mserver address: http://localhost:${globalData.config.values.webServer.port}\x1b[0m`);
        }
        console.log(`\x1b[33mModules loaded:\n${JSON.stringify(this.dependencies, null, 2)}\x1b[0m`);

        for (let i in Object.keys(this.dependencies)) {
            command += await this.getUpdates(Object.keys(this.dependencies)[i]);
        }
        if (command) {
            console.log(`\x1b[31mRecommended command to run:\n${command}\x1b[0m`);
        }
    }

    /**
     * @async
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method getUpdates
     * @summary The method which checks for updates 
     * @description The command method which checks for all dependencies if there's an update ready
     * @param {String} key The dependency
     */
    getUpdates(key) {
        return new Promise((resolve) => {
            this.exec(`npm outdated ${key}`, (error) => {
                if (error)
                    resolve(`npm update ${key}; `);
                resolve("");
            });
        });
    }
}

<|start_filename|>resources/static/css/endpoints.css<|end_filename|>
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    background-color: #201C20;
    font-family: Gill Sans, sans-serif;
    color: #dcddde;
}

a {
    text-decoration: none;
    color: #dcddde;
}

span {
    background-color: #201C20;
    padding: 3px;
    border-radius: 5px;
    box-sizing: inherit;
}

.geometricbg {
    width: 100%;
    height: 150px;
    background: url("https://smjs.eu/gd/findings/incl/bg.jpg");
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
    filter: brightness(40%);
}

.nav {
    position: fixed;
    top: 0px;
    width: 100%;
    height: 150px;
    background: linear-gradient(to bottom, rgba(210, 137, 95, 0.5), rgba(210, 137, 95, 0));
    transition: 0.5s;
    z-index: 10;
}

.nav .logo {
    display: inline-block;
    vertical-align: middle;
    width: 65px;
    height: 65px;
    margin-top: 10px;
    margin-left: 15px;
    border-radius: 50%;
    background: url("https://smjs.eu/gd/findings/incl/SMJS.png");
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
    transition: 0.5s;
}

.nav a, .nav p {
    color: #dcddde;
    font-weight: bold;
    margin-left: 15px;
    display: inline-block;
    vertical-align: middle;
    user-select: none;
    cursor: pointer;
    text-decoration: none;
}

.navalt {
    background: rgb(165, 100, 90);
    height: 60px;
}

.nav .logoalt {
    height: 40px;
    width: 40px;
}

.content {
    width: calc(100% - 100px);
    margin-left: 50px;
    margin-top: 30px;
    height: auto;
    padding: 20px;
    background-color: #403B3E;
    border-radius: 3px;
    margin-bottom: 50px;
}

.content h2 {
    margin-left: 5px;
    margin-bottom: 0px;
}

.content .block p {
    margin-top: 5px;
    margin-left: 20px;
}

.block {
    background-color: #201C20;
    margin-top: 10px;
    margin-bottom: 10px;
    padding: 10px;
}

<|start_filename|>api/converters/UrlNavigationToResponseEntity.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name UrlNavigationToResponseEntity
 * @typedef {Object} UrlNavigationToResponseEntity
 */
module.exports = class UrlNavigationToResponseEntity {

    /**
     * @private
     * @type {UrlNavigationToResponseEntity}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see UrlToResponseEntity
     */
    constructor() {
        if (!UrlNavigationToResponseEntity.INSTANCE) {
            UrlNavigationToResponseEntity.INSTANCE = this;
        }
        return UrlNavigationToResponseEntity.INSTANCE;
    }

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String[]} params
     * @param {Object} output
     * @returns {[Number, Object]}
     */
    converter(params, output) {
        let objectCheck = {};
        if (params) {
            params.forEach((param) => {
                objectCheck = UrlNavigationToResponseEntity.INSTANCE.caseInsensitiveKeyFind(param, output);
                if (objectCheck) {
                    output = objectCheck;
                } else {
                    if (param) {
                        return [404, {ERROR: "Non existing route"}];
                    }
                }
            });
        }
        return [200, output];
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} key
     * @param {Object} object
     * @returns {Object}
     */
    caseInsensitiveKeyFind(key, object) {
        return object[Object.keys(object).find(keySearch => keySearch.toLowerCase() == key.toLowerCase())];
    }
}

<|start_filename|>api/converters/save/EncodedSaveToXmlString.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name EncodedSaveToXml
 * @typedef {Object} EncodedSaveToXml
 */
module.exports = class EncodedSaveToXml {

    /**
     * @public
     * @type {EncodedSaveToXml}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see EncodedSaveToXml
     */
    constructor() {
        if (!EncodedSaveToXml.INSTANCE) {
            EncodedSaveToXml.INSTANCE = this;
        }
        return EncodedSaveToXml.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name MainCrypto
     */
    MainCrypto = new (require("../../crypto/MainCrypto"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {String} save
     * @returns {String} 
     */
    converter(save) {
        const crypto = EncodedSaveToXml.INSTANCE.MainCrypto;
        return crypto.gzUnzip(
            crypto.base64(
                crypto.xor(
                    crypto.base64(save).toString("utf8"), crypto.keys.saveFileXOR))).toString();
    }
}

<|start_filename|>resources/static/css/docs.css<|end_filename|>
* {
    box-sizing: border-box;
    font-family: Gill Sans, sans-serif;
}

body {
    color: #2e3338;
    margin: 0;
}

.top {
    width: 100%;
    height: 60px;
    background-color: #4f5660;
}

.table {
    width: 100%;
    min-height: calc(100vh - 60px);
    margin: 0 auto 0 auto;
    display: table;
}

.row {
    display: table-row;
}

.nav {
    width: 175px;
    display: table-cell;
    vertical-align: top;
    min-height: calc(100% - 90px);
}

.line {
    width: 1px;
    background-color: #e3e5e8;
    display: table-cell;
}

.section {
    width: 100%;
    border-left: 1px solid transparent;
    transition: 0.5s;
    cursor: pointer;
}

.title {
    margin: 20px 5px 0 0;
    padding-left: 5px;
}

.topic {
    display: inline-block;
}

.title:hover, .topic:hover, .highlight {
    color: #4f5660;
    border-left: 1px solid #4f5660;
    background-color: #f2f3f5;
}

.title:hover, .title.highlight {
    color: #2e3338
}

.topic p {
    margin: 0;
    padding: 5px 10px 0 15px;
}

.leftgra, .rightgra {
    width: 10px;
    display: table-cell;
    background: transparent;
}

#body {
    width: calc(100% - 195px);
    padding: 20px;
    display: table-cell;
}

#body a {
    color: #0000EE;
    text-decoration: none;
}

#body code {
    color: #B65B01;
}

#body pre code {
    color: #2e3338;
}

#body center {
    margin-top: 80px;
}

@media screen and (min-width: 1200px) {
    .leftgra, .rightgra {
        width: calc((100vw - 1200px) / 2);
    }

    .leftgra {
        background: linear-gradient(to left, white, #CDCDCD);
    }
    
    .rightgra {
        background: linear-gradient(to right, white, #CDCDCD);
    }
}

<|start_filename|>resources/static/css/analytics.css<|end_filename|>
* {
    box-sizing: border-box;
}

body {
    color: #DCDDDE;
    background-color: #23232B;
    font-family: Gill Sans, sans-serif;
}

p {
    margin: 4px 0 4px 0;
}

b {
    font-size: 20px;
}

#body {
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    text-align: center;
    position: absolute;
}

#browser {
    width: calc(100% - 10px);
    padding: 30px;
    text-align: left;
}

#browserOut .tr {
    cursor: pointer;
}

#browserOut .tr, #browserOut .ntr {
    display: inline-block;
    margin: 0 10px 0 10px;
}

#browserOut .browserVersions {
    display: none;
}

#chartContainer {
    width: 100%;
    height: 40vh;
    left: 0;
    bottom: 5px;
    padding: 10px;
    position: absolute;
}

#chartTitle {
    left: 20px;
    bottom: 40vh;
    position: absolute;
}

.info {
    width: 32%;
    padding: 20px;
    text-align: left;
    display: inline-block;
    vertical-align: top;
}

.info p, .info small {
    word-wrap: break-word;
}

.progress {
    width: 100%;
    height: 30px;
    background-color: #40404D;
    position: relative;
    border-radius: 5px;
    overflow: hidden;
}

.progress span {
    height: 30px;
    top: 0;
    left: 0;
    background-color: #418E51;
    position: absolute;
    border-radius: 5px;
}

.progress p {
    width: 100%;
    margin: 0;
    text-align: center;
    line-height: 30px;
    position: absolute;
}

@media only screen and (max-height: 350px) {
    #chartContainer {
        display: none;
    }
    #chartTitle {
        display: none;
    }
}

<|start_filename|>api/converters/save/EncodedLevelSaveParametersToJson.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name EncodedLevelSaveParametersToJson
 * @typedef {Object} EncodedLevelSaveParametersToJson
 */
module.exports = class EncodedLevelSaveParametersToJson {

    /**
     * @public
     * @type {EncodedLevelSaveParametersToJson}
     * @name INSTANCE
     */
    static INSTANCE;

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method constructor
     * @summary The class constructor @see EncodedLevelSaveParametersToJson
     */
    constructor() {
        if (!EncodedLevelSaveParametersToJson.INSTANCE) {
            EncodedLevelSaveParametersToJson.INSTANCE = this;
        }
        return EncodedLevelSaveParametersToJson.INSTANCE;
    }

    /**
     * @private
     * @type {Object}
     * @name MainCrypto
     */
    MainCrypto = new (require("../../crypto/MainCrypto"));

    /**
     * @private
     * @type {Object}
     * @name LevelDataToJson
     */
    LevelDataToJson = new (require("../level/LevelDataToJson"));

    /**
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method converter
     * @param {Object} level 
     */
    converter(level) {
        EncodedLevelSaveParametersToJson.INSTANCE.k4(level);
        EncodedLevelSaveParametersToJson.INSTANCE.k34(level);
        EncodedLevelSaveParametersToJson.INSTANCE.k67(level);
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method k4
     * @param {Object} level 
     */
    k4(level) {
        if (level.k4) {
            if (level.k4.startsWith("H4sIAAAAAAAA")) {
                try {
                    EncodedLevelSaveParametersToJson.INSTANCE.LevelDataToJson.converter(
                        level, EncodedLevelSaveParametersToJson.INSTANCE.MainCrypto.gzUnzip(
                            EncodedLevelSaveParametersToJson.INSTANCE.MainCrypto.base64(level.k4)).toString());
                } catch(error) {
                    level.k4 = "Invalid level string";
                    throw error;
                }
            } else {
                EncodedLevelSaveParametersToJson.INSTANCE.LevelDataToJson.converter(level, level.k4);
            }
        }
    }
    
    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method k34
     * @param {Object} level 
     */
    k34(level) {
        let tempArray = [];
        if (level.k34) {
            level.k34 = EncodedLevelSaveParametersToJson.INSTANCE.MainCrypto.gzUnzip(
                EncodedLevelSaveParametersToJson.INSTANCE.MainCrypto.base64(level.k34)).toString();
            if (level.k34) {
                tempArray = level.k34.substring(0, level.k34.length - 1).split(";");
                level.k34 = {};
                for (let i in tempArray) {
                    level.k34[i] = tempArray[i];
                }
            }
        }
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method k67
     * @param {Object} level 
     */
    k67(level) {
        if (level.k67) {
            level.k67 = level.k67.split("_");
        }
    }
}


<|start_filename|>api/services/save/LevelSaveIndexService.js<|end_filename|>
/**
 * @class
 * @public
 * @author SMJS
 * @name LevelsIndexService
 * @typedef {Object} LevelsIndexService
 */
module.exports = class LevelsIndexService {

    /**
     * @private
     * @type {Object}
     * @name fs
     */
    fs = require("fs");

    /**
     * @private
     * @type {String[]}
     * @name songs
     */
    songs = require("../../values/songs.json");

    /**
     * @private
     * @since 0.1.0
     * @version 0.1.0
     * @type {String}
     * @name baseHTML
     * @summary The base HTML block
     */
    baseHTML = `<div class="sideLevelSelect">
        <h3>_k2_</h3>
        <form action="/api/save/level/_i_" method="post">
            <button type="submit" name="data" class="view right" value='_all_'>View</button>
        </form>
        <div class="progress">
            <div class="wrapper">
                <div class="inner" style="width: calc(_k19_% + 5px); z-index: _indexk19_;"></div>
                <div class="inner practice" style="width: calc(_k20_% + 5px); z-index: _indexk20_;"></div>
            </div>
        </div>
        <small class="data">Song ID: _k45_</small>
        <small class="data2">Time spent: _k80_</small>
        <small class="data2 right">Objects: _k48_</small>
    </div>`;

    /**
     * @async
     * @public
     * @since 0.3.0
     * @version 0.1.0
     * @method service
     * @summary The base service to generate the main body
     * @description The index service which parses the save data and makes a menu which links through select buttons the selective service  
     * @param {object} globalData An object containing all the values and methods the application needs
     * @param {Object[]} body The levels array to process
     * @returns {Promise<[Number, String]>} The HTML page and the response status
     */
    service(globalData, body) {
        return new Promise((resolve) => {
            this.fs.readFile("resources/pages/levelSave/base.html", "utf8", (error, contents) => {
                if (error) {
                    globalData.errorFunc(error, "level-save-index", globalData);
                    resolve([500, "There was an error while fetching this file."]);
                }
                try {
                    resolve([200, contents.replace("%BODY%", this.pageConstructor(body))]);
                } catch (error) {
                    globalData.errorFunc(error, "level-save-index", globalData);
                    resolve([400, "There was an error with parsing your data."])
                }
            });
        });
    }

    /**
     * @private
     * @since 0.3.0
     * @version 0.1.0
     * @method pageConstructor
     * @summary The body generator
     * @description The main page body generator method which generates the selection boxes
     * @param {Array.<Object>} levels the levels array to process 
     * @returns {String} the body to draw on the base
     */
    pageConstructor(levels) {
        let out = "";
        let tempTime = "Seconds";
        let tempHTML = [];
        let dataCheck;

        for (let i in Object.keys(levels)) {
            // Splits up all the placeholder values
            tempHTML = this.baseHTML.split("_");
            for (let j = 1; j < tempHTML.length; j += 2) {
                dataCheck = levels[i][tempHTML[j]];

                // View Button inserting all needed data
                if (tempHTML[j] == "all") {
                    dataCheck = JSON.stringify(levels[i]);
                } else {
                    // Default values
                    if (!dataCheck) {
                        switch (tempHTML[j]) {
                            case "k48":
                                dataCheck = "Not found";
                                break;
                            case "k2":
                                dataCheck = "Eeeeh, well then";
                                break;
                            case "k45":
                                dataCheck = this.songs[levels[i].k8] || "Stereo Madness";
                                break;
                            default:
                                dataCheck = 0;
                                break;
                        }
                    }
                }

                // Time format
                if (tempHTML[j] == "k80") {
                    if (dataCheck >= 60) {
                        tempTime = "Minutes";
                        dataCheck /= 60;
                        if (dataCheck >= 60) {
                            tempTime = "Hours";
                            dataCheck /= 60;
                        }
                    }

                    // Because floor refused to work
                    dataCheck = dataCheck.toString().split(".");
                    if (dataCheck[1]) {
                        dataCheck = `${dataCheck[0]}.${dataCheck[1].substring(0, 2)}`;
                    } else {
                        dataCheck = dataCheck[0];
                    }
                    dataCheck += ` ${tempTime}`;
                }

                // Orders the progress bars based on which has the least amount of process and formats them
                if (tempHTML[j] == "k19" || tempHTML[j] == "k20") {
                    if (dataCheck == "100")
                        dataCheck = "5px + 100";
                    tempHTML[j] = dataCheck;
                    j += 2;
                    if (levels[i]["k19"] < levels[i]["k20"]) {
                        if (tempHTML[j] == "indexk19") {
                            tempHTML[j] = "1";
                        } else {
                            tempHTML[j] = "2";
                        }
                    } else {
                        if (tempHTML[j] == "indexk19") {
                            tempHTML[j] = "2";
                        } else {
                            tempHTML[j] = "1";
                        }
                    }
                } else {
                    tempHTML[j] = dataCheck;
                }
            }
            out += tempHTML.join("");
        }
        return out;
    }
}

<|start_filename|>resources/static/scripts/analytics.js<|end_filename|>
const xmlHttp = new XMLHttpRequest();
const ctx = document.getElementById("chart").getContext("2d");
const options = {
    type: "line",
    data: {
        labels: [
            "60 Minutes ago",
            "54 Minutes ago",
            "48 Minutes ago",
            "42 Minutes ago",
            "36 Minutes ago",
            "30 Minutes ago",
            "24 Minutes ago",
            "18 Minutes ago",
            "12 Minutes ago",
            "6 Minutes ago",
            "Now"
        ],
        datasets: []
    },
    options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: false,
        scales: {
            yAxes: [{
                ticks: {
                    max: 1,
                    beginAtZero: true
                }
            }]
        },
        legend: legendOptions
    }
}
const chart = new Chart(ctx, options);
let inVersion = "none";

function getMax(data) {
    let max = 0;
    for (let i in data) {
        if (Math.ceil(Math.max.apply(Math, data[i].data) / timeTestRoundOn) * timeTestRoundOn > max)
            max = Math.ceil(Math.max.apply(Math, data[i].data) / timeTestRoundOn) * timeTestRoundOn;
    }
    return max;
}

function version(id) {
    document.getElementById("browserTitle").innerHTML = "Visits per version from " + id;
    document.getElementById("browserTitle").style.cursor = "pointer";
    Array.from(document.getElementsByClassName("tr")).forEach((list) => list.style.display = "none");
    document.getElementById(id).style.display = "inline-block";
    inVersion = id;
}

function browser() {
    document.getElementById("browserTitle").innerHTML = "Visits per browser";
    document.getElementById("browserTitle").style.cursor = "inherit";
    document.getElementById(inVersion).style.display = "none";
    Array.from(document.getElementsByClassName("tr")).forEach((list) => list.style.display = "inline-block");
    inVersion = "none";
}

function chartgen(data) {
    chart.ctx = document.getElementById("chart").getContext("2d");
    chart.data.datasets = data;
    chart.options.scales.yAxes[0].ticks.max = getMax(data);
    chart.update();
}

function request() {
    let data;
    xmlHttp.open("GET", "/analytics/data");
    xmlHttp.onload = () => {
        data = JSON.parse(xmlHttp.response);
        document.getElementById("memoryOut").innerHTML = data.memoryText + data.memoryProgress;
        document.getElementById("healthOut").innerHTML = data.errorProgress;
        document.getElementById("extraOut").innerHTML = data.extra;
        if (data.browser == "denied") {
            document.getElementById("browser").remove();
        } else {
            document.getElementById("browserOut").innerHTML = data.browser;
        }
        if (inVersion != "none")
            version(inVersion);
        chartgen(data.chartData);
    };
    xmlHttp.send(null);
}

setInterval(() => request(), interval);
request();