├── .gitignore ├── .gitmodules ├── Configuration ├── DefaultGameSettings.json ├── Localization-en.json └── Localization-fr.json ├── Lib ├── CLICommands.js ├── Classes.js ├── ClientData.js ├── Commands.js ├── ConfigMaker.js ├── DatabaseModels.js ├── Entity │ ├── Player.js │ └── Server.js ├── EventDispatcher.js ├── EventLogWatcher.js ├── EventParser.js ├── InitDatabase.js ├── MasterServer.js ├── Models │ ├── NSMAliases.js │ ├── NSMAudit.js │ ├── NSMClients.js │ ├── NSMConnections.js │ ├── NSMKills.js │ ├── NSMMessages.js │ ├── NSMMeta.js │ ├── NSMPenalties.js │ ├── NSMPlayerStatHistory.js │ ├── NSMPlayerStats.js │ ├── NSMReports.js │ ├── NSMSettings.js │ └── NSMTokens.js ├── NodeLogServer.js ├── NodeServerManager.js ├── RconCommandPrefixes │ ├── Default.js │ ├── IW3.js │ ├── IW4.js │ ├── IW5.js │ ├── IW6.js │ ├── T4.js │ └── T6.js ├── RconConnection.js └── ServerLogWatcher.js ├── Plugins ├── AntiVPN.js ├── ClanTag.js ├── DiscordWebhook.js ├── Global │ ├── AutoMessages.js │ ├── DiscordBot.js │ ├── GlobalChat.js │ └── ProfileMeta.js ├── MoreCommands.js ├── NativeCommands.js ├── Penalties.js ├── Routes │ ├── Discord.js │ ├── SocialMedia.js │ ├── ZStats.js │ └── index.js ├── ScriptCommands.js ├── StatLogger.js ├── Voting.js ├── WelcomeMessages.js ├── ZombiesBank.js ├── ZombiesLocker.js └── ZombiesStats.js ├── README.md ├── Scripts └── t6 │ └── mp │ ├── gametypes_zm │ ├── Compiled │ │ ├── README.md │ │ ├── _clientids.gsc │ │ └── _clientids_bundle.gsc │ └── Source │ │ └── _clientids.gsc │ └── zombies │ ├── Compiled │ └── _zm_weapon_locker.gsc │ └── Source │ └── _zm_weapon_locker.gsc ├── StartLogServer.bat ├── StartLogServer.sh ├── StartNSM.bat ├── StartNSM.sh ├── StartNSMUpdate.sh ├── Update.bat ├── Utils ├── Mutex.js └── Utils.js ├── Webfront ├── Public │ ├── android-icon-144x144.png │ ├── apple-icon-180x180.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── css │ │ ├── global.css │ │ └── xbbcode.css │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── fonts │ │ ├── Fira-Mono-Regular-Italic.otf │ │ ├── FiraCode-Bold.ttf │ │ ├── FiraCode-Light.ttf │ │ ├── FiraCode-Medium.ttf │ │ ├── FiraCode-Regular.ttf │ │ ├── FiraCode-Retina.ttf │ │ ├── Titillium_Web │ │ │ ├── OFL.txt │ │ │ ├── TitilliumWeb-Black.ttf │ │ │ ├── TitilliumWeb-Bold.ttf │ │ │ ├── TitilliumWeb-BoldItalic.ttf │ │ │ ├── TitilliumWeb-ExtraLight.ttf │ │ │ ├── TitilliumWeb-ExtraLightItalic.ttf │ │ │ ├── TitilliumWeb-Italic.ttf │ │ │ ├── TitilliumWeb-Light.ttf │ │ │ ├── TitilliumWeb-LightItalic.ttf │ │ │ ├── TitilliumWeb-Regular.ttf │ │ │ ├── TitilliumWeb-SemiBold.ttf │ │ │ └── TitilliumWeb-SemiBoldItalic.ttf │ │ └── iw │ │ │ ├── bankrus.ttf │ │ │ ├── hudsmall.otf │ │ │ └── hudsmall.ttf │ ├── img │ │ └── maps │ │ │ ├── default.png │ │ │ ├── iw3 │ │ │ ├── mp_backlot.jpg │ │ │ ├── mp_bloc.jpg │ │ │ ├── mp_bog.jpg │ │ │ ├── mp_broadcast.jpg │ │ │ ├── mp_carentan.jpg │ │ │ ├── mp_cargoship.jpg │ │ │ ├── mp_citystreets.jpg │ │ │ ├── mp_convoy.jpg │ │ │ ├── mp_countdown.jpg │ │ │ ├── mp_crash.jpg │ │ │ ├── mp_crash_snow.jpg │ │ │ ├── mp_creek.jpg │ │ │ ├── mp_crossfire.jpg │ │ │ ├── mp_dusk.jpg │ │ │ ├── mp_farm.jpg │ │ │ ├── mp_hill.jpg │ │ │ ├── mp_invasion.jpg │ │ │ ├── mp_killhouse.jpg │ │ │ ├── mp_overgrown.jpg │ │ │ ├── mp_pipeline.jpg │ │ │ ├── mp_shipment.jpg │ │ │ ├── mp_showdown.jpg │ │ │ ├── mp_strike.jpg │ │ │ └── mp_vacant.jpg │ │ │ ├── iw4 │ │ │ ├── mp_afghan.jpg │ │ │ ├── mp_bloc.jpg │ │ │ ├── mp_bloc_sh.jpg │ │ │ ├── mp_bog_sh.jpg │ │ │ ├── mp_boneyard.jpg │ │ │ ├── mp_brecourt.jpg │ │ │ ├── mp_cargoship_sh.jpg │ │ │ ├── mp_checkpoint.jpg │ │ │ ├── mp_crash_tropical.jpg │ │ │ ├── mp_derail.jpg │ │ │ ├── mp_estate.jpg │ │ │ ├── mp_estate_tropical.jpg │ │ │ ├── mp_fav_tropical.jpg │ │ │ ├── mp_favela.jpg │ │ │ ├── mp_firingrange.jpg │ │ │ ├── mp_highrise.jpg │ │ │ ├── mp_invasion.jpg │ │ │ ├── mp_killhouse.jpg │ │ │ ├── mp_nightshift.jpg │ │ │ ├── mp_nuked.jpg │ │ │ ├── mp_quarry.jpg │ │ │ ├── mp_rundown.jpg │ │ │ ├── mp_rust.jpg │ │ │ ├── mp_shipment.jpg │ │ │ ├── mp_storm_spring.jpg │ │ │ ├── mp_subbase.jpg │ │ │ ├── mp_terminal.jpg │ │ │ └── mp_underpass.jpg │ │ │ ├── iw5 │ │ │ ├── mp_aground_ss.jpg │ │ │ ├── mp_alpha.jpg │ │ │ ├── mp_boardwalk.jpg │ │ │ ├── mp_bootleg.jpg │ │ │ ├── mp_bravo.jpg │ │ │ ├── mp_burn_ss.jpg │ │ │ ├── mp_carbon.jpg │ │ │ ├── mp_cement.jpg │ │ │ ├── mp_courtyard_ss.jpg │ │ │ ├── mp_crosswalk_ss.jpg │ │ │ ├── mp_dome.jpg │ │ │ ├── mp_exchange.jpg │ │ │ ├── mp_hardhat.jpg │ │ │ ├── mp_highrise.jpg │ │ │ ├── mp_hillside_ss.jpg │ │ │ ├── mp_interchange.jpg │ │ │ ├── mp_italy.jpg │ │ │ ├── mp_lambeth.jpg │ │ │ ├── mp_meteora.jpg │ │ │ ├── mp_moab.jpg │ │ │ ├── mp_mogadishu.jpg │ │ │ ├── mp_morningwood.jpg │ │ │ ├── mp_nola.jpg │ │ │ ├── mp_overgrown.jpg │ │ │ ├── mp_overwatch.jpg │ │ │ ├── mp_paris.jpg │ │ │ ├── mp_park.jpg │ │ │ ├── mp_plaza2.jpg │ │ │ ├── mp_qadeem.jpg │ │ │ ├── mp_radar.jpg │ │ │ ├── mp_restrepo_ss.jpg │ │ │ ├── mp_roughneck.jpg │ │ │ ├── mp_rust.jpg │ │ │ ├── mp_seatown.jpg │ │ │ ├── mp_shipbreaker.jpg │ │ │ ├── mp_six_ss.jpg │ │ │ ├── mp_terminal_cls.jpg │ │ │ ├── mp_underground.jpg │ │ │ └── mp_village.jpg │ │ │ └── t6 │ │ │ ├── mp_bridge.jpg │ │ │ ├── mp_carrier.jpg │ │ │ ├── mp_castaway.jpg │ │ │ ├── mp_concert.jpg │ │ │ ├── mp_dig.jpg │ │ │ ├── mp_dockside.jpg │ │ │ ├── mp_downhill.jpg │ │ │ ├── mp_drone.jpg │ │ │ ├── mp_express.jpg │ │ │ ├── mp_frostbite.jpg │ │ │ ├── mp_hijacked.jpg │ │ │ ├── mp_hydro.jpg │ │ │ ├── mp_la.jpg │ │ │ ├── mp_magma.jpg │ │ │ ├── mp_meltdown.jpg │ │ │ ├── mp_mirage.jpg │ │ │ ├── mp_nightclub.jpg │ │ │ ├── mp_overflow.jpg │ │ │ ├── mp_paintball.jpg │ │ │ ├── mp_podville.jpg │ │ │ ├── mp_raid.jpg │ │ │ ├── mp_skate.jpg │ │ │ ├── mp_slums.jpg │ │ │ ├── mp_socotra.jpg │ │ │ ├── mp_studio.jpg │ │ │ ├── mp_takeoff.jpg │ │ │ ├── mp_turbine.jpg │ │ │ ├── mp_uplink.jpg │ │ │ ├── mp_vertigo.jpg │ │ │ ├── mp_village.jpg │ │ │ ├── zm_buried.jpg │ │ │ ├── zm_factory.jpg │ │ │ ├── zm_highrise.jpg │ │ │ ├── zm_meat.jpg │ │ │ ├── zm_moon.jpg │ │ │ ├── zm_nuked.jpg │ │ │ ├── zm_prison.jpg │ │ │ ├── zm_prototype.jpg │ │ │ ├── zm_tomb.jpg │ │ │ └── zm_transit.jpg │ ├── js │ │ ├── audit.js │ │ ├── authenticator.js │ │ ├── chat.js │ │ ├── font-awesome.js │ │ ├── global.js │ │ ├── info.js │ │ ├── main.js │ │ ├── moment-timezone-with-data-10-year-range.js │ │ ├── moment-with-locales.js │ │ ├── notifications.js │ │ ├── profile.js │ │ ├── settings.js │ │ ├── stats.js │ │ ├── xbbcode.js │ │ └── zstats.js │ ├── manifest.json │ ├── ms-icon-310x310.png │ └── opensearch.xml ├── SessionStore.js ├── Webfront.js ├── api │ └── Auth.js └── html │ ├── audit.ejs │ ├── authenticator.ejs │ ├── chat.ejs │ ├── client.ejs │ ├── error.ejs │ ├── header.ejs │ ├── index.ejs │ ├── info.ejs │ ├── links.ejs │ ├── login.ejs │ ├── search.ejs │ ├── settings.ejs │ ├── stats.ejs │ └── zstats.ejs └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.db 3 | *.txt 4 | node_modules 5 | *.log 6 | *.db-journal 7 | *.save 8 | *.swp 9 | !Configuration/DefaultGameSettings.json 10 | !Configuration/Localization-*.json 11 | Log/* 12 | Log -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Scripts/iw5/plugins/source/iw5-chai-utils"] 2 | path = Scripts/iw5/plugins/source/iw5-chai-utils 3 | url = https://github.com/fedddddd/iw5-chai-utils.git 4 | -------------------------------------------------------------------------------- /Lib/CLICommands.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const readline = require('readline') 3 | const Utils = new (require(path.join(__dirname, '../Utils/Utils.js')))() 4 | const Localization = require(path.join(__dirname, `../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 5 | const Permissions = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)).Permissions 6 | 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | terminal: false 11 | }) 12 | 13 | class CLICommands { 14 | constructor(Manager, Managers) { 15 | this.Managers = Managers 16 | this.Player = { 17 | Name: 'Node Server Manager', 18 | ClientId: 1, 19 | inGame: false, 20 | PermissionLevel: Permissions.Levels['ROLE_MANAGER'], 21 | Tell: (msg) => { 22 | console.log(Utils.COD2BashColor(`^7${msg}^7`)) 23 | } 24 | } 25 | 26 | this.Manager = Manager 27 | this.customCommands = { 28 | 'chat': { 29 | callback: () => { 30 | this.chatEnabled = !this.chatEnabled 31 | this.Player.Tell(`Chat ${this.chatEnabled ? '^2enabled' : '^1disabled'}`) 32 | } 33 | } 34 | } 35 | 36 | this.streamChat() 37 | rl.on('line', this.processCommand.bind(this)) 38 | } 39 | streamChat() { 40 | this.Managers.forEach(Manager => { 41 | Manager.Server.on('message', async (Player, Message) => { 42 | if (this.chatEnabled) { 43 | this.Player.Tell(Utils.formatString(Localization['GLOBALCHAT_FORMAT'], { 44 | Enabled: '', 45 | Name: Player.Name, 46 | Message, 47 | Hostname: Player.Server.HostnameRaw 48 | }, '%')[0]) 49 | } 50 | }) 51 | }) 52 | } 53 | 54 | async processCommand(line) { 55 | var args = line.split(/\s+/) 56 | 57 | if (this.customCommands[args[0].toLocaleLowerCase()]) { 58 | this.customCommands[args[0].toLocaleLowerCase()].callback() 59 | return 60 | } 61 | 62 | var executedMiddleware = await this.Manager.Commands.executeMiddleware(args[0], this.Player, args) 63 | if (await this.Manager.Commands.execute(args[0], this.Player, args)) return 64 | 65 | var command = Utils.getCommand(this.Manager.commands, args[0]) 66 | 67 | switch (true) { 68 | case (!this.Manager.commands[command]): 69 | !executedMiddleware && this.Player.Tell(Localization['COMMAND_NOT_FOUND']) 70 | return 71 | case (this.Manager.commands[command].inGame || this.Manager.commands[command].inGame == undefined): 72 | this.Player.Tell(Localization['COMMAND_ENV_ERROR']) 73 | return 74 | case (args.length - 1 < this.Manager.commands[command].ArgumentLength): 75 | this.Player.Tell(Localization['COMMAND_ARGUMENT_ERROR']) 76 | return 77 | } 78 | 79 | this.Manager.Server.DB.logActivity(`@${this.Player.ClientId}`, Localization['AUDIT_CMD_EXEC'].replace('%NAME%', command), args.join(' ')) 80 | this.Manager.commands[command].callback(this.Player, args) 81 | } 82 | } 83 | 84 | module.exports = CLICommands -------------------------------------------------------------------------------- /Lib/Classes.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Localization = require(path.join(__dirname, `../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 3 | const Permissions = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)).Permissions 4 | 5 | const NodeServerManager = { 6 | ClientId: 1, 7 | Name: 'Node Server Manager', 8 | Guid: 'node' 9 | } 10 | 11 | class Command { 12 | constructor(command = {}) { 13 | this.name = command.name ? command.name : '' 14 | this.alias = command.alias ? command.alias : '' 15 | this.permission = command.permission ? Permissions.Levels[command.permission] : 0 16 | this.inGame = command.Ingame ? command.inGame : false 17 | this.isMiddleware = command.isMiddleware ? command.isMiddleware : false 18 | this.exceptions = command.exceptions ? command.exceptions : [] 19 | this.params = command.params ? command.params : [] 20 | this.callbacks = command.callbacks ? command.callbacks : [] 21 | this.defaultCallback = (Player) => { 22 | Player.Tell(Localization['COMMAND_NOT_SETUP']) 23 | } 24 | } 25 | setName(name) { 26 | this.name = name 27 | return this 28 | } 29 | setMiddleware(bool) { 30 | this.isMiddleware = bool 31 | return this 32 | } 33 | setAlias(alias) { 34 | this.alias = alias 35 | return this 36 | } 37 | setInGame(inGame) { 38 | this.inGame = inGame 39 | return this 40 | } 41 | addException(error, callback) { 42 | this.exceptions.push({ error, callback }) 43 | return this 44 | } 45 | addParams(params) { 46 | this.params = this.params.concat(params) 47 | return this 48 | } 49 | addParam(param) { 50 | this.params.push(param) 51 | return this 52 | } 53 | addCallback(callback) { 54 | this.callbacks.push(callback) 55 | return this 56 | } 57 | setPermission(perm) { 58 | this.permission = Permissions.Levels[perm] 59 | return this 60 | } 61 | } 62 | 63 | module.exports = { Command, NodeServerManager } -------------------------------------------------------------------------------- /Lib/ClientData.js: -------------------------------------------------------------------------------- 1 | class ClientData { 2 | constructor() { 3 | this.clientData = {} 4 | } 5 | getData(ClientId) { 6 | if (this.clientData[ClientId]) { 7 | return this.clientData[ClientId] 8 | } 9 | 10 | this.clientData[ClientId] = {} 11 | 12 | return this.clientData[ClientId] 13 | } 14 | } 15 | 16 | module.exports = ClientData -------------------------------------------------------------------------------- /Lib/Commands.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Localization = require(path.join(__dirname, `../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 3 | 4 | class Commands { 5 | constructor() { 6 | this.Commands = {} 7 | } 8 | add(command) { 9 | this.Commands[command.name] = command 10 | } 11 | findCommand(name) { 12 | var found = false 13 | Object.entries(this.Commands).forEach(command => { 14 | if (command[0].toLocaleLowerCase() == name.toLocaleLowerCase() || (command[1].alias && command[1].alias.toLocaleLowerCase() == name.toLocaleLowerCase())) { 15 | found = this.Commands[command[0]] 16 | } 17 | }) 18 | return found 19 | } 20 | async executeMiddleware (name, Player, args, options = { delay: true, broadcast: false }) { 21 | return new Promise((resolve, reject) => { 22 | var next = () => { 23 | resolve() 24 | } 25 | 26 | Object.entries(this.Commands).forEach(command => { 27 | if (!command[1].isMiddleware) return 28 | 29 | this.execute(command[1].name, Player, args, options, next) 30 | }) 31 | }) 32 | } 33 | async execute (name, Player, args, options = { delay: true, broadcast: false }, next = null) { 34 | var command = this.findCommand(name) 35 | 36 | var funcs = { 37 | Tell: (string) => { 38 | options.broadcast ? (Player.Server.Broadcast(string)) : Player.Tell(string) 39 | } 40 | } 41 | 42 | switch (true) { 43 | case (!next && command.isMiddleware): 44 | case (!command): 45 | return 46 | case (command.inGame && !Player.inGame): 47 | Player.Tell(Localization['COMMAND_ENV_ERROR']) 48 | return 1 49 | case (Player.PermissionLevel < command.permission): 50 | Player.Tell(Localization['COMMAND_FORBIDDEN']) 51 | return 1 52 | } 53 | 54 | var defaultParam = { 55 | join: false, 56 | optional: false, 57 | index: 0, 58 | name: '' 59 | } 60 | 61 | var params = {} 62 | for (var i = 0; i < command.params.length; i++) { 63 | 64 | command.params[i] = {...defaultParam, ...command.params[i]} 65 | 66 | if (!args[command.params[i].index + 1]) { 67 | if (command.params[i].optional) continue 68 | 69 | Player.Tell(Localization['COMMAND_ARGUMENT_ERROR']) 70 | return 1 71 | } 72 | params[command.params[i].name] = command.params[i].join ? args.slice(command.params[i].index + 1).join(' ') : args[command.params[i].index + 1] 73 | } 74 | 75 | for (var i = 0; i < command.exceptions.length; i++) { 76 | if (!command.exceptions[i].callback(Player, params, args)) { 77 | Player.Tell(command.exceptions[i].error) 78 | return 1 79 | } 80 | } 81 | 82 | if (!command.callbacks.length) { 83 | command.defaultCallback(Player, args) 84 | return 1 85 | } 86 | 87 | for (var i = 0; i < command.callbacks.length; i++) { 88 | await command.callbacks[i](Player, params, args, options, funcs, next) 89 | } 90 | 91 | return 1 92 | } 93 | } 94 | 95 | module.exports = Commands -------------------------------------------------------------------------------- /Lib/DatabaseModels.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const sqlite3 = require('sqlite3').verbose(); 4 | const directoryPath = path.join(__dirname, './Models') 5 | const Sequelize = require('sequelize') 6 | var Models = {} 7 | 8 | new sqlite3.Database(path.join(__dirname, '../Database/Database1.db'), (err) => { 9 | var sequelize = new Sequelize({ 10 | host: 'localhost', 11 | dialect: 'sqlite', 12 | pool: { 13 | max: 5, 14 | min: 0, 15 | idle: 10000 16 | }, 17 | logging: false, 18 | storage: path.join(__dirname, '../Database/Database1.db') 19 | }) 20 | 21 | Models.DB = sequelize 22 | 23 | fs.readdir(directoryPath, (err, files) => { 24 | if (err) { 25 | return console.log('Unable to scan directory: ' + err) 26 | } 27 | 28 | files.forEach( (file) => { 29 | file = path.join(__dirname, `./Models/${file}`) 30 | var Model = require(file)(sequelize, Sequelize) 31 | Models[path.basename(file, path.extname(file))] = Model 32 | }) 33 | }) 34 | }) 35 | 36 | module.exports = Models -------------------------------------------------------------------------------- /Lib/Entity/Player.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | const path = require('path') 3 | const { NodeServerManager } = require(path.join(__dirname, '../Classes.js')) 4 | const Utils = new (require(path.join(__dirname, `../../Utils/Utils.js`)))() 5 | const Localization = require(path.join(__dirname, `../../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 6 | 7 | class ePlayer extends EventEmitter { 8 | constructor (Guid, Name, Clientslot, IPAddress, Server) { 9 | super() 10 | this.Guid = Guid 11 | this.Name = Name 12 | this.inGame = true 13 | this.lastSeen = new Date() 14 | this.IPAddress = IPAddress 15 | this.Clientslot = Clientslot 16 | this.Server = Server 17 | this.Server.Clients[Clientslot] = this 18 | } 19 | async build() { 20 | this.ClientId = await this.Server.DB.addClient(this.Guid) 21 | 22 | this.Server.DB.initializeStats(this.ClientId) 23 | 24 | this.PermissionLevel = await this.Server.DB.getClientLevel(this.ClientId) 25 | this.Server.DB.logConnection(this) 26 | 27 | this.matchData = {} 28 | 29 | this.Data = this.Server.clientData.getData(this.ClientId) 30 | 31 | const id = this.IPAddress && this.IPAddress.split(':')[0] 32 | ? this.IPAddress.split(':')[0] 33 | : crypto.randomBytes(8).toString('hex') 34 | 35 | this.Session = this.Server.sessionStore.createSession(id) 36 | this.Session && (this.Session.Data.Authorized = this.Session.Data.Authorized != undefined ? this.Session.Data.Authorized : false) 37 | } 38 | async getPersistentMeta(name, type = '') { 39 | var result = await this.Server.DB.metaService.getPersistentMeta(name, this.ClientId, type) 40 | 41 | return result 42 | } 43 | Report(Reason, Origin = NodeServerManager) { 44 | this.Server.DB.addReport(Origin.ClientId, this.ClientId, Reason) 45 | this.Server.emit('report', Origin, this, Reason) 46 | 47 | this.Server.tellStaffGlobal(Utils.formatString(Localization['COMMAND_REPORT_TELL'], { 48 | Origin: Origin.Name, 49 | Hostname: this.Server.Hostname, 50 | Target: this.Name, 51 | Reason: Reason 52 | }, '%')[0]) 53 | } 54 | Ban (Reason, Origin) { 55 | this.Server.DB.addPenalty({ 56 | TargetId: this.ClientId, 57 | OriginId: Origin.ClientId, 58 | PenaltyType: 'PENALTY_PERMA_BAN', 59 | Duration: 0, 60 | Reason: Reason 61 | }) 62 | 63 | this.Server.emit('penalty', 'PENALTY_PERMA_BAN', this, Reason, Origin) 64 | this.Kick(`You have been permanently banned for: ^5${Reason}`, Origin, false, '') 65 | } 66 | Tempban (Reason, Origin, Duration) { 67 | this.Server.DB.addPenalty({ 68 | TargetId: this.ClientId, 69 | OriginId: Origin.ClientId, 70 | PenaltyType: 'PENALTY_TEMP_BAN', 71 | Duration: Duration, 72 | Reason: Reason 73 | }) 74 | 75 | this.Server.emit('penalty', 'PENALTY_TEMP_BAN', this, Reason, Origin, Duration) 76 | this.Kick(`You have been banned for: ^5${Reason} ${Utils.secondsToDhms(Duration)}^7 left`, Origin, false, '') 77 | } 78 | async Tell (text) { 79 | if (!text) return 80 | 81 | var chunks = Utils.breakString(text, this.Server.Rcon.commandPrefixes.Dvars.maxSayLength, ' ') 82 | 83 | for (var i = 0; i < chunks.length; i++) { 84 | await this.Server.Rcon.executeCommandAsync(this.Server.Rcon.commandPrefixes.Rcon.Tell 85 | .replace('%CLIENT%', this.Clientslot) 86 | .replace('%MESSAGE%', chunks[i])) 87 | } 88 | } 89 | Kick (Message, Origin = NodeServerManager, Log = true, Basemsg = 'You have been kicked: ^5') { 90 | this.Server.DB.addPenalty({ 91 | TargetId: this.ClientId, 92 | OriginId: Origin.ClientId, 93 | PenaltyType: 'PENALTY_KICK', 94 | Duration: 0, 95 | Reason: Message 96 | }) 97 | 98 | Log && this.Server.emit('penalty', 'PENALTY_KICK', this, Message, Origin) 99 | this.Server.Rcon.executeCommandAsync(this.Server.Rcon.commandPrefixes.Rcon.clientKick 100 | .replace('%CLIENT%', this.Clientslot) 101 | .replace('%REASON%', `${Basemsg}${Message}`)) 102 | 103 | //this.Server.Clients[this.Clientslot] = null 104 | } 105 | } 106 | module.exports = ePlayer -------------------------------------------------------------------------------- /Lib/EventLogWatcher.js: -------------------------------------------------------------------------------- 1 | const EventParser = require('./EventParser.js') 2 | const Tail = require('tail').Tail 3 | const path = require('path') 4 | const fs = require('fs') 5 | const _EventDispatcher = require('./EventDispatcher.js') 6 | const spawn = require('child_process').spawn 7 | 8 | class EventLogWatcher extends EventParser { 9 | constructor (logfile, Server, Manager) { 10 | super(Server) 11 | this.previousMD5 = null 12 | this.logfile = logfile 13 | this.Server = Server 14 | this.EventDispatcher = new _EventDispatcher(Server, Manager) 15 | } 16 | 17 | init () { 18 | var filePath = path.resolve(this.logfile) 19 | 20 | if (!fs.existsSync(filePath)) { 21 | console.log(`Warning: log file "${filePath}" doesn't exist\nMake sure you selected the right file in Configuration/NSMConfiguration.json Servers -> LOGFILE\n`) 22 | } 23 | 24 | if (process.platform == 'win32') { 25 | var tail = spawn(`powershell`, ['-command', 'get-content', '-wait', '-Tail 0', `"${filePath}"`]) 26 | tail.stdout.on('data', (data) => { 27 | this.onLine(data.toString()) 28 | }) 29 | return 30 | } 31 | 32 | var tail = new Tail(filePath) 33 | tail.watch() 34 | 35 | tail.on('line', this.onLine.bind(this)) 36 | } 37 | 38 | async onLine(line) { 39 | this.Server.emit('line', line) 40 | this.Server.emit('stripped_line', line.trim().replace(new RegExp(/([0-9]+:[0-9]+)\s+/g), '')) 41 | 42 | const lines = line.split('\n').filter(l => l.length > 0) 43 | 44 | for (var i = 0; i < lines.length; i++) { 45 | const event = this.parseEvent(lines[i].trim()) 46 | 47 | if (!event) return 48 | 49 | this.EventDispatcher.dispatchCallback(event) 50 | } 51 | } 52 | } 53 | 54 | module.exports = EventLogWatcher -------------------------------------------------------------------------------- /Lib/EventParser.js: -------------------------------------------------------------------------------- 1 | class EventParser { 2 | constructor(Server) { 3 | this.Server = Server 4 | } 5 | 6 | parseTimeStamp(timeStamp) { 7 | if (timeStamp.includes(':')) { 8 | const split = timeStamp.split(':') 9 | 10 | return parseInt(split[0]) * 60 + parseInt(split[1]) 11 | } 12 | 13 | if (!this.Server.startTime) { 14 | this.Server.startTime = parseInt(timeStamp) 15 | } 16 | 17 | return parseInt(timeStamp) - this.Server.startTime 18 | } 19 | 20 | getEventData(eventString) { 21 | eventString = eventString.trim() 22 | 23 | var eventRegex = { 24 | say: /^(.+) (say|sayteam);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);([^;]*);(.*)$/g, 25 | join: /^(.+) (J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$/g, 26 | quit: /^(.+) (Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(.*)$/g, 27 | damage: /^(.+) (D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$/g, 28 | kill: /^(.+) (K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$/g, 29 | init: /^( +|)(.+) (InitGame|InitGame(.+))$/g 30 | } 31 | 32 | var eventData = { type: null, data: null } 33 | Object.entries(eventRegex).forEach((r) => { 34 | if (!eventString.match(r[1])) { 35 | return 36 | } 37 | 38 | var eventVars = r[1].exec(eventString) 39 | eventVars[0] = this.parseTimeStamp(eventVars[0]) 40 | 41 | eventData = { type: r[0], vars: eventVars } 42 | }) 43 | 44 | return eventData 45 | } 46 | 47 | parseEvent(eventString) { 48 | var eventData = this.getEventData(eventString) 49 | if (!eventData || eventData.type == null) return 50 | 51 | var parsedEvent = { type: eventData.type, data: null } 52 | switch (eventData.type) { 53 | case 'init': { 54 | parsedEvent.data = { 55 | TimeOffset: eventData.vars[0], 56 | } 57 | } 58 | break 59 | case 'say': 60 | parsedEvent.data = { 61 | TimeOffset: eventData.vars[0], 62 | Origin: this.Server.Clients[eventData.vars[4]], 63 | Message: eventData.vars[6].replace(/[^\x20-\x7E]+/g, '') 64 | } 65 | break 66 | case 'quit': 67 | case 'join': 68 | parsedEvent.data = { 69 | TimeOffset: eventData.vars[0], 70 | Origin: { 71 | Guid: eventData.vars[3].includes('bot') ? eventData.vars[3] : this.Server.Rcon.commandPrefixes.convertGuid(eventData.vars[3]), 72 | Clientslot: eventData.vars[4], 73 | Name: eventData.vars[5].replace(/\[.*\]/g, '') 74 | }, 75 | } 76 | break 77 | case 'kill': 78 | var Weapon = eventData.vars[11] 79 | var BaseWeapon = Weapon 80 | 81 | if (Weapon.indexOf('_mp') > 0) { 82 | BaseWeapon = Weapon.substr(0, Weapon.indexOf('_mp')) 83 | } 84 | 85 | var suicide = (eventData.vars[4] == eventData.vars[8]) || eventData.vars[8] == '-1' 86 | 87 | parsedEvent.data = { 88 | TimeOffset: eventData.vars[0], 89 | Target: this.Server.Clients[eventData.vars[4]], 90 | Origin: suicide ? {ClientId: 1} : this.Server.Clients[eventData.vars[8]], 91 | Attack: { 92 | Weapon: eventData.vars[11], 93 | Damage: eventData.vars[12], 94 | MOD: eventData.vars[13], 95 | HitLoc: eventData.vars[14], 96 | BaseWeapon: BaseWeapon 97 | } 98 | } 99 | break 100 | } 101 | 102 | return parsedEvent 103 | } 104 | } 105 | 106 | module.exports = EventParser -------------------------------------------------------------------------------- /Lib/MasterServer.js: -------------------------------------------------------------------------------- 1 | const ws = require('ws') 2 | const https = require('https') 3 | const { machineId } = require('node-machine-id') 4 | const Utils = new (require('../Utils/Utils.js'))() 5 | 6 | class MasterServer { 7 | constructor(Managers) { 8 | this.Managers = Managers 9 | this.interval = 60 * 1000 * 1 10 | this.hostname = 'master.fed0001.xyz' 11 | } 12 | async init() { 13 | this.DB = this.Managers[0].Server.DB 14 | 15 | this.apikey = await this.getApiKey() 16 | 17 | this.connect() 18 | } 19 | async connect() { 20 | var master = new ws(`wss://${this.hostname}?key=${this.apikey}`) 21 | 22 | var interval = null 23 | var ping = null 24 | 25 | master.onopen = async () => { 26 | console.log(`Connected to master server \x1b[32m${this.hostname}\x1b[0m`) 27 | 28 | interval = setInterval(() => { 29 | try { 30 | this.heartbeat(master) 31 | } 32 | catch (e) { 33 | console.log(e) 34 | } 35 | }, this.interval) 36 | 37 | ping = setInterval(() => { 38 | master.send() 39 | }, 5000) 40 | } 41 | 42 | master.onerror = async (e) => { 43 | master.close() 44 | } 45 | 46 | master.onclose = async (e) => { 47 | clearInterval(interval) 48 | clearInterval(ping) 49 | 50 | console.log('Connection to master server lost, reconnecting...') 51 | 52 | setTimeout(() => { 53 | this.connect(this.hostname, this.apikey) 54 | }, 5000) 55 | } 56 | 57 | master.onmessage = async (msg) => { 58 | try { 59 | if (!Utils.isJson(msg)) { 60 | switch (msg) { 61 | 62 | } 63 | 64 | return 65 | } 66 | 67 | var event = JSON.parse(msg) 68 | 69 | switch (event.type) { 70 | case 'broadcast': 71 | this.Managers.forEach(Manager => { 72 | Manager.Server.Broadcast(event.message) 73 | }) 74 | break 75 | } 76 | } 77 | catch (e) {} 78 | } 79 | } 80 | async makeRequest(method, hostname, path, port, data) { 81 | return new Promise((resolve, reject) => { 82 | let options = { 83 | host: hostname, 84 | port: port, 85 | path: path, 86 | method: method, 87 | headers: { 88 | 'Content-Type': 'application/json' 89 | } 90 | } 91 | const req = https.request(options, res => { 92 | res.on('data', data => { 93 | resolve(data.toString()) 94 | }) 95 | }) 96 | 97 | req.on('error', error => { 98 | reject(error) 99 | }) 100 | 101 | req.write(JSON.stringify(data)) 102 | req.end() 103 | }) 104 | } 105 | 106 | async getApiKey() { 107 | let id = await machineId() 108 | let endpoint = '/api/key' 109 | let key = JSON.parse(await this.makeRequest('POST', this.hostname, endpoint, 443, {action: 'get', id })) 110 | 111 | return key.key 112 | } 113 | 114 | heartbeat(webSocket) { 115 | var servers = [] 116 | this.Managers.filter(Manager => Manager.Server.Rcon.isRunning).forEach(Manager => { 117 | if (!Manager.Server.Rcon.isRunning) return 118 | 119 | var server = {} 120 | var clients = [] 121 | Manager.Server.Clients.forEach(Client => { 122 | if (!Client) return 123 | clients.push({ 124 | name: Client.Name, 125 | guid: Client.Guid 126 | }) 127 | }) 128 | 129 | server.dvars = { 130 | mapname: Manager.Server.Mapname, 131 | gametype: Manager.Server.Gametype, 132 | gamename: Manager.Server.Gamename, 133 | hostname: Manager.Server.HostnameRaw, 134 | maxclients: Manager.Server.MaxClients 135 | } 136 | 137 | server.ip = Manager.Server.externalIP 138 | server.port = Manager.Server.PORT 139 | server.clients = clients 140 | 141 | servers.push(server) 142 | }) 143 | 144 | var heartbeat = { type: 'heartbeat', servers } 145 | 146 | servers.length && webSocket.send(JSON.stringify(heartbeat)) 147 | } 148 | } 149 | 150 | module.exports = MasterServer -------------------------------------------------------------------------------- /Lib/Models/NSMAliases.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMAliases = sequelize.define('NSMAliases', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | OriginId: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | references: { 22 | model: 'NSMClients', 23 | key: 'ClientId' 24 | } 25 | } 26 | }, { 27 | timestamps: false, 28 | uniqueKeys: { 29 | AliasUnique: { 30 | fields: ['ClientId', 'OriginId'] 31 | } 32 | } 33 | }) 34 | 35 | NSMAliases.sync() 36 | return NSMAliases 37 | } -------------------------------------------------------------------------------- /Lib/Models/NSMAudit.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMAudit = sequelize.define('NSMAudit', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | Origin: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | }, 14 | Type: { 15 | type: DataTypes.TEXT, 16 | allowNull: true, 17 | defaultValue: null, 18 | }, 19 | Description: { 20 | type: DataTypes.TEXT, 21 | allowNull: true, 22 | defaultValue: null, 23 | }, 24 | Date: { 25 | type: DataTypes.DATE, 26 | allowNull: false, 27 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 28 | } 29 | }, { 30 | timestamps: false 31 | }) 32 | NSMAudit.sync() 33 | 34 | return NSMAudit 35 | } -------------------------------------------------------------------------------- /Lib/Models/NSMClients.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMClients = sequelize.define('NSMClients', 3 | { 4 | ClientId: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | Description: { 11 | type: DataTypes.TEXT, 12 | allowNull: true, 13 | defaultValue: null, 14 | }, 15 | Password: { 16 | type: DataTypes.TEXT, 17 | allowNull: true, 18 | defaultValue: null, 19 | }, 20 | Secret: { 21 | type: DataTypes.TEXT, 22 | allowNull: true, 23 | defaultValue: null, 24 | }, 25 | Guid: { 26 | type: DataTypes.TEXT, 27 | allowNull: false, 28 | unique: true 29 | }, 30 | PermissionLevel: { 31 | type: DataTypes.INTEGER, 32 | allowNull: false, 33 | defaultValue: 0, 34 | }, 35 | FirstConnection: { 36 | type: DataTypes.DATE, 37 | allowNull: false, 38 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 39 | }, 40 | LastConnection: { 41 | type: DataTypes.DATE, 42 | allowNull: false, 43 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 44 | } 45 | }, { 46 | timestamps: false 47 | }) 48 | NSMClients.sync() 49 | 50 | return NSMClients 51 | } -------------------------------------------------------------------------------- /Lib/Models/NSMConnections.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMConnections = sequelize.define('NSMConnections', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | Name: { 19 | type: DataTypes.TEXT, 20 | allowNull: false, 21 | }, 22 | Guid: { 23 | type: DataTypes.TEXT, 24 | allowNull: false, 25 | }, 26 | IPAddress: { 27 | type: DataTypes.TEXT, 28 | allowNull: true, 29 | }, 30 | Date: { 31 | type: DataTypes.DATE, 32 | allowNull: false, 33 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 34 | } 35 | }, { 36 | timestamps: false 37 | }) 38 | 39 | NSMConnections.sync() 40 | return NSMConnections 41 | } -------------------------------------------------------------------------------- /Lib/Models/NSMKills.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMKills = sequelize.define('NSMKills', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | TargetId: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | references: { 22 | model: 'NSMClients', 23 | key: 'ClientId' 24 | } 25 | }, 26 | BaseWeapon: { 27 | type: DataTypes.TEXT, 28 | allowNull: true, 29 | }, 30 | Weapon: { 31 | type: DataTypes.TEXT, 32 | allowNull: true, 33 | }, 34 | MOD: { 35 | type: DataTypes.TEXT, 36 | allowNull: true, 37 | }, 38 | Damage: { 39 | type: DataTypes.INTEGER, 40 | allowNull: true, 41 | }, 42 | HitLoc: { 43 | type: DataTypes.TEXT, 44 | allowNull: true, 45 | }, 46 | Date: { 47 | type: DataTypes.DATE, 48 | allowNull: false, 49 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 50 | } 51 | }, { 52 | timestamps: false 53 | }) 54 | NSMKills.sync() 55 | return NSMKills 56 | } -------------------------------------------------------------------------------- /Lib/Models/NSMMessages.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMKills = sequelize.define('NSMMessages', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | Name: { 11 | type: DataTypes.TEXT, 12 | allowNull: true, 13 | }, 14 | OriginId: { 15 | type: DataTypes.INTEGER, 16 | allowNull: false, 17 | references: { 18 | model: 'NSMClients', 19 | key: 'ClientId' 20 | } 21 | }, 22 | Message: { 23 | type: DataTypes.TEXT, 24 | allowNull: true, 25 | }, 26 | Hostname: { 27 | type: DataTypes.TEXT, 28 | allowNull: true, 29 | }, 30 | Date: { 31 | type: DataTypes.DATE, 32 | allowNull: false, 33 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 34 | } 35 | }, { 36 | timestamps: false 37 | }) 38 | NSMKills.sync() 39 | return NSMKills 40 | } -------------------------------------------------------------------------------- /Lib/Models/NSMMeta.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMMeta = sequelize.define('NSMMeta', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | Key: { 19 | type: DataTypes.TEXT, 20 | allowNull: false, 21 | }, 22 | Value: { 23 | type: DataTypes.TEXT, 24 | allowNull: false, 25 | }, 26 | Date: { 27 | type: DataTypes.DATE, 28 | allowNull: false, 29 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 30 | } 31 | }, { 32 | timestamps: false 33 | }) 34 | NSMMeta.sync() 35 | 36 | return NSMMeta 37 | } -------------------------------------------------------------------------------- /Lib/Models/NSMPenalties.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMPenalties = sequelize.define('NSMPenalties', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | TargetId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | OriginId: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | references: { 22 | model: 'NSMClients', 23 | key: 'ClientId' 24 | } 25 | }, 26 | PenaltyType: { 27 | type: DataTypes.TEXT, 28 | allowNull: false 29 | }, 30 | Date: { 31 | type: DataTypes.DATE, 32 | allowNull: false, 33 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP') 34 | }, 35 | Active: { 36 | type: DataTypes.BOOLEAN, 37 | allowNull: false, 38 | defaultValue: true 39 | }, 40 | Duration: { 41 | type: DataTypes.INTEGER, 42 | allowNull: false 43 | }, 44 | Reason: { 45 | type: DataTypes.TEXT, 46 | allowNull: false 47 | } 48 | }, { 49 | timestamps: false 50 | }) 51 | NSMPenalties.sync() 52 | return NSMPenalties 53 | } -------------------------------------------------------------------------------- /Lib/Models/NSMPlayerStatHistory.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMPlayerStatHistory = sequelize.define('NSMPlayerStatHistory', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | TotalPerformance: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | defaultValue: 100, 22 | }, 23 | Performance: { 24 | type: DataTypes.INTEGER, 25 | allowNull: false, 26 | defaultValue: 100, 27 | }, 28 | Date: { 29 | type: DataTypes.DATE, 30 | allowNull: false, 31 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 32 | } 33 | }, { 34 | timestamps: false, 35 | freezeTableName: true 36 | }) 37 | NSMPlayerStatHistory.sync() 38 | return NSMPlayerStatHistory 39 | } -------------------------------------------------------------------------------- /Lib/Models/NSMPlayerStats.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMPlayerStats = sequelize.define('NSMPlayerStats', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | PlayedTime: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | defaultValue: 0 22 | }, 23 | Kills: { 24 | type: DataTypes.INTEGER, 25 | allowNull: false, 26 | defaultValue: 0, 27 | }, 28 | Deaths: { 29 | type: DataTypes.INTEGER, 30 | allowNull: false, 31 | defaultValue: 0, 32 | }, 33 | TotalPerformance: { 34 | type: DataTypes.INTEGER, 35 | allowNull: false, 36 | defaultValue: 100, 37 | }, 38 | Performance: { 39 | type: DataTypes.INTEGER, 40 | allowNull: false, 41 | defaultValue: 100, 42 | } 43 | }, { 44 | timestamps: false 45 | }) 46 | NSMPlayerStats.sync() 47 | return NSMPlayerStats 48 | } -------------------------------------------------------------------------------- /Lib/Models/NSMReports.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMReports = sequelize.define('NSMReports', 3 | { 4 | Id: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | OriginId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'NSMClients', 15 | key: 'ClientId' 16 | } 17 | }, 18 | TargetId: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | references: { 22 | model: 'NSMClients', 23 | key: 'ClientId' 24 | } 25 | }, 26 | Reason: { 27 | type: DataTypes.TEXT, 28 | allowNull: false, 29 | }, 30 | Active: { 31 | type: DataTypes.BOOLEAN, 32 | allowNull: false, 33 | defaultValue: true 34 | }, 35 | Date: { 36 | type: DataTypes.DATE, 37 | allowNull: false, 38 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 39 | } 40 | }, { 41 | timestamps: false 42 | }) 43 | NSMReports.sync() 44 | return NSMReports 45 | } -------------------------------------------------------------------------------- /Lib/Models/NSMSettings.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMSettings = sequelize.define('NSMSettings', 3 | { 4 | ClientId: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true, 9 | references: { 10 | model: 'NSMClients', 11 | key: 'ClientId' 12 | } 13 | }, 14 | TwoFactor: { 15 | type: DataTypes.BOOLEAN, 16 | allowNull: false, 17 | defaultValue: false 18 | }, 19 | InGameLogin: { 20 | type: DataTypes.BOOLEAN, 21 | allowNull: false, 22 | defaultValue: false 23 | }, 24 | TokenLogin: { 25 | type: DataTypes.BOOLEAN, 26 | allowNull: false, 27 | defaultValue: true 28 | } 29 | }, { 30 | timestamps: false 31 | }) 32 | NSMSettings.sync() 33 | 34 | return NSMSettings 35 | } -------------------------------------------------------------------------------- /Lib/Models/NSMTokens.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const NSMTokens = sequelize.define('NSMTokens', 3 | { 4 | TokenId: { 5 | type: DataTypes.INTEGER, 6 | autoIncrement: true, 7 | allowNull: false, 8 | primaryKey: true 9 | }, 10 | ClientId: { 11 | type: DataTypes.INTEGER, 12 | allowNull: false, 13 | }, 14 | Token: { 15 | type: DataTypes.TEXT, 16 | allowNull: false, 17 | }, 18 | Date: { 19 | type: DataTypes.DATE, 20 | allowNull: false, 21 | defaultValue: DataTypes.literal('CURRENT_TIMESTAMP'), 22 | } 23 | }, { 24 | timestamps: false 25 | }) 26 | NSMTokens.sync() 27 | 28 | return NSMTokens 29 | } -------------------------------------------------------------------------------- /Lib/NodeLogServer.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const configuration = require(path.join(__dirname, `../Configuration/NLSConfiguration.json`).toString()) 3 | const ws = require('ws') 4 | const fs = require('fs') 5 | const https = require('https') 6 | const http = require('http') 7 | const spawn = require('child_process').spawn 8 | const Tail = require('tail').Tail 9 | 10 | class NodeLogServer { 11 | constructor(config) { 12 | this.logFile = config.logFile 13 | this.bindPort = config.bindPort 14 | try { 15 | this.ssl = { 16 | key: fs.readFileSync(config.ssl.key), 17 | cert: fs.readFileSync(config.ssl.cert) 18 | } 19 | } catch (e) { 20 | this.ssl = null 21 | console.warn('Unable to load SSL certificate from configuration, starting server without SSL is not recommended, provide a valid certificate if possible') 22 | } 23 | 24 | this.key = config.key 25 | 26 | this.init() 27 | } 28 | init() { 29 | try { 30 | const server = this.ssl ? https.createServer(this.ssl) : http.createServer() 31 | const socket = new ws.Server({ server }) 32 | 33 | var getParams = (url) => { 34 | var queryDict = {} 35 | url.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]}) 36 | return queryDict; 37 | } 38 | 39 | var filePath = path.resolve(this.logFile) 40 | 41 | if (!fs.existsSync(filePath)) { 42 | console.log(`Warning: log file "${filePath}" doesn't exist\nMake sure you selected the right file in Configuration/NLSConfiguration.json Servers -> LOGFILE\n`) 43 | return 44 | } 45 | 46 | server.listen(this.bindPort, () => { 47 | console.log(`Server listening on port ${this.bindPort}`) 48 | }) 49 | 50 | socket.authorizedClients = [] 51 | 52 | socket.Broadcast = (msg) => { 53 | socket.authorizedClients.forEach(client => { 54 | client.send(msg) 55 | }) 56 | } 57 | 58 | socket.on('connection', (conn, req) => { 59 | var params = getParams(req.url.substr(1)) 60 | 61 | if (params.key != this.key) { 62 | console.log(`Rejecting connection from ${req.socket.remoteAddress}`) 63 | conn.close() 64 | return 65 | } 66 | 67 | console.log(`Accepting connection from ${req.socket.remoteAddress}`) 68 | socket.authorizedClients.push(conn) 69 | }) 70 | 71 | if (process.platform == 'win32') { 72 | var tail = spawn(`powershell`, ['-command', 'get-content', '-wait', '-Tail 0', `"${filePath}"`]) 73 | tail.stdout.on('data', (data) => { 74 | socket.Broadcast(data.toString()) 75 | }) 76 | return 77 | } 78 | 79 | var tail = new Tail(filePath) 80 | tail.watch() 81 | 82 | tail.on('line', async (data) => { 83 | socket.Broadcast(data) 84 | }) 85 | } 86 | catch (e) { 87 | console.log(`Log server failed to start: ${e.toString()}`) 88 | } 89 | } 90 | } 91 | 92 | configuration.Servers.forEach(config => { 93 | new NodeLogServer(config) 94 | }) -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/Default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: 'get %DVAR%', 6 | setDvar: 'set %DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tell %CLIENT% "%MESSAGE%"`, 9 | Say: 'say "%MESSAGE%"', 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+){0,1} +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) +([0-9]+) *$/g, 11 | dvarRegex: /(.*?) +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | return { 14 | num: match[1], 15 | score: match[2], 16 | bot: match[3], 17 | ping: match[4], 18 | guid: match[5], 19 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 20 | lastmgs: match[7], 21 | address: match[8], 22 | qport: match[9], 23 | rate: match[10] 24 | } 25 | } 26 | }, 27 | convertGuid: (guid) => { 28 | return guid 29 | }, 30 | getInfo: '\xff\xff\xff\xffgetinfo', 31 | getStatus: '\xff\xff\xff\xffgetstatus', 32 | Dvars: { 33 | maxclients: 'sv_maxclients', 34 | mapname: 'mapname', 35 | hostname: 'sv_hostname', 36 | gamename: 'gamename', 37 | maprotation: 'sv_mapRotation', 38 | gametype: 'g_gametype', 39 | messagelength: 999999999, 40 | maxSayLength: 100 41 | } 42 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/IW3.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: '%DVAR%', 6 | setDvar: '%DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tell %CLIENT% "%MESSAGE%"`, 9 | Say: `say "%MESSAGE%"`, 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) +([0-9]+) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) +([0-9]+) *$/g, 11 | dvarRegex: /\"(.*?)\" +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | return { 14 | num: match[1], 15 | score: match[2], 16 | bot: '0', 17 | ping: match[3], 18 | guid: match[4], 19 | steamid: match[5], 20 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 21 | lastmgs: match[7], 22 | address: match[8], 23 | qport: match[9], 24 | rate: match[10] 25 | } 26 | } 27 | }, 28 | getInfo: '\xff\xff\xff\xffgetinfo', 29 | getStatus: '\xff\xff\xff\xffgetstatus', 30 | Dvars: { 31 | maxclients: 'sv_maxClients', 32 | mapname: 'mapname', 33 | hostname: 'sv_hostname', 34 | gametype: 'g_gametype', 35 | gamename: 'gamename', 36 | maprotation: 'sv_mapRotation', 37 | messagelength: 999999999, 38 | maxSayLength: 100 39 | } 40 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/IW4.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: '%DVAR%', 6 | setDvar: '%DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tellraw %CLIENT% "%MESSAGE%"`, 9 | Say: `sayraw "%MESSAGE%"`, 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) +([0-9]+) *$/g, 11 | dvarRegex: /\"(.*?)\" +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | return { 14 | num: match[1], 15 | score: match[2], 16 | bot: match[3], 17 | ping: match[4], 18 | guid: match[5], 19 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 20 | lastmgs: match[7], 21 | address: match[8], 22 | qport: match[9], 23 | rate: match[10] 24 | } 25 | } 26 | }, 27 | getInfo: '\xff\xff\xff\xffgetinfo', 28 | getStatus: '\xff\xff\xff\xffgetstatus', 29 | Dvars: { 30 | maxclients: 'sv_maxClients', 31 | mapname: 'mapname', 32 | hostname: 'sv_hostname', 33 | gametype: 'g_gametype', 34 | gamename: 'gamename', 35 | maprotation: 'sv_mapRotation', 36 | messagelength: 999999999, 37 | maxSayLength: 100 38 | } 39 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/IW5.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: 'get %DVAR%', 6 | setDvar: 'set %DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tell %CLIENT% "%MESSAGE%"`, 9 | Say: 'say "%MESSAGE%"', 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) *(.{0,32}) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +([0-9]+) *$/g, 11 | dvarRegex: /(.*?) +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | const bot = match[3] == '1' 14 | 15 | return { 16 | num: match[1], 17 | score: match[2], 18 | bot, 19 | ping: match[4], 20 | guid: bot ? match[5] : parseInt(match[5].substr(8), 16).toString(), 21 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 22 | address: bot ? 'localhost:27016' : match[7], 23 | qport: match[8], 24 | } 25 | }, 26 | retries: 3 27 | }, 28 | convertGuid: (guid) => { 29 | return parseInt(guid.substr(8), 16).toString() 30 | }, 31 | getInfo: '\xff\xff\xff\xffgetinfo', 32 | getStatus: '\xff\xff\xff\xffgetstatus', 33 | Dvars: { 34 | maxclients: 'sv_maxclients', 35 | mapname: 'mapname', 36 | hostname: 'sv_hostname', 37 | gamename: 'gamename', 38 | maprotation: 'sv_mapRotation', 39 | gametype: 'g_gametype', 40 | messagelength: 999999999, 41 | maxSayLength: 120 42 | } 43 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/IW6.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: '%DVAR%', 6 | setDvar: '%DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tellraw %CLIENT% "%MESSAGE%"`, 9 | Say: `sayraw "%MESSAGE%"`, 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +(Yes|No) +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) *(.{0,32}) +() +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +([0-9]+) *$/g, 11 | dvarRegex: /\"(.*?)\" +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | return { 14 | num: match[1], 15 | score: match[2], 16 | bot: match[3] == 'Yes', 17 | ping: match[4], 18 | guid: match[5], 19 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 20 | lastmgs: match[7], 21 | address: match[8], 22 | qport: match[9], 23 | rate: match[10] 24 | } 25 | } 26 | }, 27 | getInfo: '\xff\xff\xff\xffgetinfo', 28 | getStatus: '\xff\xff\xff\xffgetstatus', 29 | Dvars: { 30 | maxclients: 'sv_maxClients', 31 | mapname: 'mapname', 32 | hostname: 'sv_hostname', 33 | gametype: 'g_gametype', 34 | gamename: 'gamename', 35 | maprotation: 'sv_mapRotation', 36 | messagelength: 999999999, 37 | maxSayLength: 100 38 | } 39 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/T4.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: '%DVAR%', 6 | setDvar: 'set %DVAR% %VALUE%', 7 | clientKick: `clientkick %CLIENT% "%REASON%"`, 8 | Tell: `tell %CLIENT% "%MESSAGE%"`, 9 | Say: `say "%MESSAGE%"`, 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) +([0-9]+) *$/g, 11 | dvarRegex: /\"(.*?)\" +(is:|is) +\"(.*?)\"/g, 12 | commandDelay: 500, 13 | parseStatus: (match) => { 14 | return { 15 | num: match[1], 16 | score: match[2], 17 | bot: '0', 18 | ping: match[3], 19 | guid: match[4], 20 | name: match[5].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 21 | lastmgs: match[6], 22 | address: match[7], 23 | qport: match[8], 24 | rate: match[9] 25 | } 26 | } 27 | }, 28 | getInfo: '\xff\xff\xff\xffgetinfo', 29 | getStatus: '\xff\xff\xff\xffgetstatus', 30 | Dvars: { 31 | maxclients: 'sv_maxClients', 32 | mapname: 'mapname', 33 | hostname: 'sv_hostname', 34 | gametype: 'g_gametype', 35 | gamename: 'gamename', 36 | maprotation: 'sv_mapRotation', 37 | messagelength: 999999999, 38 | maxSayLength: 150 39 | } 40 | } -------------------------------------------------------------------------------- /Lib/RconCommandPrefixes/T6.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Rcon: { 3 | prefix: '\xff\xff\xff\xffrcon %PASSWORD% %COMMAND%', 4 | status: 'status', 5 | getDvar: 'get %DVAR%', 6 | setDvar: 'set %DVAR% %VALUE%', 7 | clientKick: `clientkick_for_reason %CLIENT% "%REASON%"`, 8 | Tell: `tell %CLIENT% "%MESSAGE%"`, 9 | Say: `say "%MESSAGE%"`, 10 | statusRegex: /^ +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +((?:[A-Za-z0-9]){8,32}|(?:[A-Za-z0-9]){8,32}|bot[0-9]+|(?:[[A-Za-z0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) +([0-9]+) *$/g, 11 | dvarRegex: /(.*?) +(is:|is) +\"(.*?)\"/g, 12 | parseStatus: (match) => { 13 | return { 14 | num: match[1], 15 | score: match[2], 16 | bot: match[3], 17 | ping: match[4], 18 | guid: parseInt(match[5], 16).toString(), 19 | name: match[6].replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), ``), 20 | lastmgs: match[7], 21 | address: match[8], 22 | qport: match[9], 23 | rate: match[10] 24 | } 25 | } 26 | }, 27 | getInfo: '\xff\xff\xff\xffgetinfo', 28 | getStatus: '\xff\xff\xff\xffgetstatus', 29 | Dvars: { 30 | maxclients: 'sv_maxclients', 31 | mapname: 'mapname', 32 | hostname: 'sv_hostname', 33 | gametype: 'g_gametype', 34 | gamename: 'gamename', 35 | maprotation: 'sv_mapRotation', 36 | messagelength: 104, 37 | maxSayLength: 100 38 | } 39 | } -------------------------------------------------------------------------------- /Lib/ServerLogWatcher.js: -------------------------------------------------------------------------------- 1 | const EventParser = require('./EventParser.js') 2 | const ws = require('ws') 3 | const _EventDispatcher = require('./EventDispatcher.js') 4 | 5 | class EventLogWatcher extends EventParser { 6 | constructor (logServerURI, Server, Manager) { 7 | super(Server) 8 | this.logServerURI = logServerURI 9 | this.Server = Server 10 | this.Manager = Manager 11 | this.EventDispatcher = new _EventDispatcher(Server, Manager) 12 | } 13 | async init () { 14 | try { 15 | var socket = new ws(this.logServerURI) 16 | 17 | socket.onmessage = async (msg) => { 18 | this.onLine(msg.data) 19 | } 20 | 21 | socket.on('error', (error) => { 22 | console.log(`Server Log Watcher: ${error}`) 23 | }) 24 | 25 | socket.onclose = async () => { 26 | console.log(`Connection to log server (${this.logServerURI}) lost, reconnecting in 15 seconds...`) 27 | 28 | setTimeout(() => { 29 | this.init() 30 | }, 15 * 1000) 31 | } 32 | } 33 | catch (e) { 34 | this.Manager.logger.writeLn(`Remote log server generated an error: ${e.toString()}`) 35 | } 36 | } 37 | async onLine(line) { 38 | line = line.replace(/[^\x20-\x7E]+/g, '') 39 | 40 | this.Server.Rcon.isRunning = true 41 | this.Server.emit('line', line) 42 | this.Server.emit('stripped_line', line.trim().replace(new RegExp(/([0-9]+:[0-9]+)\s+/g), '')) 43 | 44 | const lines = line.split('\n').filter(l => l.length > 0) 45 | 46 | for (var i = 0; i < lines.length; i++) { 47 | const event = this.parseEvent(lines[i].trim()) 48 | 49 | if (!event) return 50 | 51 | this.EventDispatcher.dispatchCallback(event) 52 | } 53 | } 54 | } 55 | 56 | module.exports = EventLogWatcher -------------------------------------------------------------------------------- /Plugins/DiscordWebhook.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const config = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)) 3 | const { Webhook, MessageBuilder } = require('discord-webhook-node') 4 | const hook = new Webhook({ url: config.discordHookUrl, throwErrors: false, retryOnLimit: false,}) 5 | const fetch = require('node-fetch') 6 | const Utils = new (require(path.join(__dirname, '../Utils/Utils.js')))() 7 | const https = require('https') 8 | 9 | hook.setUsername('NSM Bot') 10 | 11 | class Plugin { 12 | constructor(Server, Manager) { 13 | this.Server = Server 14 | this.Manager = Manager 15 | this.Url = null 16 | this.Server.on('connect', this.onPlayerConnect.bind(this)) 17 | this.Server.on('disconnect', this.onPlayerDisconnect.bind(this)) 18 | this.Server.on('penalty', this.onPlayerPenalty.bind(this)) 19 | } 20 | async onPlayerConnect (Player) { 21 | this.sendHook(`:inbox_tray: ${Player.Name}`, ' ' ,`${await this.getUrl()}/id/${Player.ClientId}`) 22 | Player.on('message', async (Message) => { 23 | this.sendHook(`:envelope_with_arrow: ${Player.Name}`, Message, `${await this.getUrl()}/id/${Player.ClientId}`) 24 | }) 25 | } 26 | async onPlayerDisconnect (Player) { 27 | this.sendHook(`:outbox_tray: ${Player.Name}`, ' ' ,`${await this.getUrl()}/id/${Player.ClientId}`) 28 | } 29 | async getUrl() { 30 | if (this.Url) return this.Url 31 | 32 | try { 33 | var result = (await fetch(`${config.WebfrontSSL ? 'https://' : 'http://'}${config.webfrontHostname}/api/verify`)) 34 | var hostname = result ? config.webfrontHostname : `${(await fetch('https://api.ipify.org/?format=json')).json().ip}:${config.WebfrontPort}` 35 | this.Url = `${config.WebfrontSSL ? 'https://' : 'http://'}${hostname}` 36 | 37 | this.Url = this.Url 38 | } 39 | catch (e) { 40 | try { 41 | var hostname = (await (await fetch('https://api.ipify.org/?format=json')).json()).ip 42 | this.Url = `${config.WebfrontSSL ? 'https://' : 'http://'}${hostname}:${config.WebfrontPort}` 43 | } 44 | catch (e) { 45 | return null 46 | } 47 | } 48 | 49 | return this.Url 50 | 51 | } 52 | async getFlag (IPAddress) { 53 | return (await (await fetch(`https://extreme-ip-lookup.com/json/${IPAddress.split(':')[0]}?key=demo`)).json()).countryCode.toLocaleLowerCase() 54 | } 55 | async onPlayerPenalty(Type, Target, Reason, Origin, Duration = -1) { 56 | var translation = { 57 | 'PENALTY_TEMP_BAN': 'Temp ban', 58 | 'PENALTY_PERMA_BAN': 'Perma ban', 59 | 'PENALTY_KICK': 'Kick', 60 | 'PENALTY_MUTE': 'Mute' 61 | } 62 | this.sendHookPenalty(`:hammer: ${Target.Name}`, ' ', `${await this.getUrl()}/id/${Target.ClientId}`, translation[Type], Reason, Origin, Duration) 63 | } 64 | async sendHookPenalty(Title, Description, Url, Type, Reason, Origin, Duration) { 65 | var messageEmbed = new MessageBuilder() 66 | .setTitle(Title) 67 | .setDescription(Description) 68 | .setURL(Url) 69 | .setColor('#00b0f4') 70 | .addField('Type', Type, true) 71 | .addField('Origin', Origin.Name, true) 72 | .addField('Reason', `\`${this.stripColorCodes(Reason)}\``, true) 73 | .setFooter('Node Server Manager') 74 | .setTimestamp() 75 | Duration > 0 && messageEmbed.addField('Duration', Utils.time2str(Duration), true) 76 | hook.send(messageEmbed) 77 | } 78 | stripColorCodes(string) { 79 | return string.replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), '') 80 | } 81 | async sendHook(Title, Description, Url) { 82 | try { 83 | var messageEmbed = new MessageBuilder() 84 | .setTitle(Title) 85 | .setDescription(Description) 86 | .setURL(Url) 87 | .setColor('#00b0f4') 88 | .addField('Hostname', `\`${this.Server.HostnameRaw.replace(new RegExp(/\^([0-9]|\:|\;)/g, 'g'), '')}\``, true) 89 | .addField('Map', `\`${this.Server.Mapname}\``, true) 90 | .addField('Players', `\`${this.Server.Clients.filter((value) => {return value}).length} / ${this.Server.MaxClients}\``, true) 91 | .setFooter('Node Server Manager') 92 | .setTimestamp(); 93 | hook.send(messageEmbed) 94 | } 95 | catch (e) {} 96 | } 97 | } 98 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/Global/AutoMessages.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const Utils = new (require(path.join(__dirname, '../../Utils/Utils.js')))() 4 | const configName = path.join(__dirname, `../../Configuration/NSMConfiguration.json`) 5 | var config = require(configName) 6 | 7 | fs.watch(path.join(__dirname, `../../Configuration/NSMConfiguration.json`), async (filename) => { 8 | if (filename) { 9 | try { var newData = require(configName) } 10 | catch (e) { 11 | console.log(`Failed to reload config file ${configName}: ${e.toString()}`); return } 12 | 13 | config = newData 14 | console.log(`Reloaded config file ${configName}`) 15 | } 16 | }) 17 | 18 | class Plugin { 19 | constructor(Managers) { 20 | this.Managers = Managers 21 | this.autoMessages() 22 | } 23 | autoMessages() { 24 | setInterval(async () => { 25 | var index = Utils.getRandomInt(0, config.autoMessages.length) 26 | var Message = await this.replacePlaceholders(config.autoMessages[index]) 27 | this.Managers.forEach(Manager => { 28 | Manager.Server.Broadcast(Message) 29 | }) 30 | }, config.autoMessagesInterval * 1000) 31 | } 32 | async replacePlaceholders(text) { 33 | var placeholders = { 34 | 'TOTALCLIENTS' : { 35 | async get() { 36 | return (await placeholders.Managers[0].Server.DB.getAllClients()) 37 | } 38 | }, 39 | 'PLAYERCOUNT': { 40 | async get() { 41 | var count = 0; 42 | var Managers = placeholders.Managers.concat() 43 | Managers.forEach(Manager => { 44 | count += Manager.Server.Clients.filter((x) => { return x }).length 45 | }) 46 | return count 47 | } 48 | }, 49 | 'SERVERCOUNT': { 50 | async get() { 51 | var Managers = placeholders.Managers.concat() 52 | return Managers.filter((Manager) => { return Manager.Server.Mapname} ).length 53 | } 54 | }, 55 | 'TOTALKILLS': { 56 | async get() { 57 | return (await placeholders.Managers[0].Server.DB.getGlobalStats()).totalKills 58 | } 59 | }, 60 | 'TOTALPLAYEDTIME': { 61 | async get() { 62 | return parseInt(((await placeholders.Managers[0].Server.DB.getGlobalStats()).totalPlayedTime) / 60) 63 | } 64 | } 65 | } 66 | 67 | placeholders.Managers = this.Managers 68 | var entries = Object.entries(placeholders) 69 | 70 | text = text.split(/\s+/g) 71 | 72 | for (var i = 0; i < text.length; i++) { 73 | for (var o = 0; o < entries.length; o++) { 74 | if (text[i].includes(`{${entries[o][0]}}`)) { 75 | text[i] = text[i].replace(`{${entries[o][0]}}`, (await entries[o][1].get())) 76 | } 77 | } 78 | } 79 | 80 | return text.join(' ') 81 | } 82 | } 83 | 84 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/Global/GlobalChat.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Localization = require(path.join(__dirname, `../../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 3 | const Utils = new (require(path.join(__dirname, '../../Utils/Utils.js')))() 4 | 5 | class Plugin { 6 | constructor(Managers) { 7 | this.Managers = Managers 8 | this.init() 9 | } 10 | init() { 11 | this.Managers.forEach(Manager => { 12 | Manager.Server.on('message', this.playerMessage.bind(this)) 13 | }) 14 | } 15 | playerMessage(Player, Message) { 16 | if (Player.Session && Player.Session.Data.serverChat) { 17 | Player.Session.Data.serverChat.Broadcast(Utils.formatString(Localization['GLOBALCHAT_FORMAT'], { 18 | Enabled: '', 19 | Name: Player.Name, 20 | Message, 21 | Hostname: Player.Server.Hostname 22 | })) 23 | } 24 | 25 | this.Managers.forEach(async Manager => { 26 | Manager.Server.Clients.forEach(Client => { 27 | if (!Client || !Client.Session) return 28 | 29 | if (Client.Session.Data.serverChat 30 | && Client.Session.Data.serverChat.Id == Player.Server.Id 31 | && (!Player.Session.Data.serverChat || Player.Session.Data.serverChat && Player.Session.Data.serverChat.Id != Client.Server.Id)) { 32 | Client.Tell(Utils.formatString(Localization['SOCKET_MSG_FORMAT'], { 33 | Name: Player.Name, 34 | Message, 35 | }, '%')[0]) 36 | return 37 | } 38 | 39 | if (!Client.Session.Data.globalChat 40 | || Client.Server.Id == Player.Server.Id) return 41 | 42 | Client.Tell(Utils.formatString(Localization['GLOBALCHAT_FORMAT'], { 43 | Enabled: (Player.Session && Player.Session.Data.globalChat) ? '[^1G^7]' : '', 44 | Name: Player.Name, 45 | Message, 46 | Hostname: Player.Server.HostnameRaw 47 | }, '%')[0]) 48 | }) 49 | }) 50 | } 51 | } 52 | 53 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/Global/ProfileMeta.js: -------------------------------------------------------------------------------- 1 | class Plugin { 2 | constructor(Managers) { 3 | this.DB = Managers[0].Server.DB 4 | this.addClientMeta() 5 | } 6 | async getZStats(ClientId) { 7 | var Stats = (await this.DB.Models.NSMZStats.findAll({where: ClientId})).map(x => x = x.dataValues) 8 | return Stats.length > 0 ? Stats[0] : false 9 | } 10 | async addClientMeta() { 11 | this.DB.clientProfileMeta.push(async (ClientId) => { 12 | var stats = await this.getZStats(ClientId) 13 | 14 | if (!stats || stats.Score <= 500) return {} 15 | 16 | return { 17 | name: 'Zombies Stats', 18 | data: { 19 | 'Kills': stats.Kills, 20 | 'Downs': stats.Downs, 21 | 'Revives': stats.Revives, 22 | 'Highest Round': stats.HighestRound, 23 | 'Headshots': stats.Headshots, 24 | 'Score': stats.Score, 25 | } 26 | } 27 | }) 28 | } 29 | } 30 | 31 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/Penalties.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { Command, NodeServerManager } = require(path.join(__dirname, `../Lib/Classes.js`)) 3 | const Utils = new (require(path.join(__dirname, '../Utils/Utils.js')))() 4 | const Permissions = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)).Permissions 5 | const Localization = require(path.join(__dirname, `../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 6 | 7 | class Plugin { 8 | constructor(Server, Manager) { 9 | this.Server = Server 10 | this.Manager = Manager 11 | this.Server.on('connect', this.onPlayerConnected.bind(this)) 12 | } 13 | async onPlayerConnected(Player) { 14 | if (this.Server.getClients().length + this.Server.reservedSlots > this.Server.MaxClients && Player.PermissionLevel < Permissions.Levels['ROLE_MODERATOR']) { 15 | Player.Kick(Localization['KICK_CLIENTSLOT_RESERVED']) 16 | } 17 | 18 | try { 19 | var playerPenalties = await this.Server.DB.getAllPenalties(Player.ClientId) 20 | 21 | for (var i = 0; i < playerPenalties.length; i++) { 22 | switch (playerPenalties[i].PenaltyType) { 23 | case 'PENALTY_PERMA_BAN': 24 | if (playerPenalties[i].Active) { 25 | Player.Kick(`Banned for: ^5${playerPenalties[i].Reason}`, NodeServerManager) 26 | return 27 | } 28 | break 29 | case 'PENALTY_TEMP_BAN': 30 | var dateDiff = (new Date(playerPenalties[i].Date) - new Date()) / 1000 31 | 32 | if (dateDiff + playerPenalties[i].Duration > 0) { 33 | if (playerPenalties[i].Active) { 34 | Player.Kick(`Banned for: ^5${playerPenalties[i].Reason}^7 ${Utils.secondsToDhms(dateDiff + playerPenalties[i].Duration)} left`, NodeServerManager) 35 | return 36 | } 37 | } 38 | break 39 | } 40 | } 41 | } 42 | catch (e) { } 43 | } 44 | 45 | } 46 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/Routes/Discord.js: -------------------------------------------------------------------------------- 1 | const ejs = require('ejs') 2 | const config = JSON.parse(process.env.config) 3 | const btoa = require('btoa') 4 | const DiscordOauth2 = require("discord-oauth2") 5 | const oauth = new DiscordOauth2() 6 | 7 | module.exports = (app, db, Webfront) => { 8 | if (!config.discordOAuth2Url || !config.discordClientId || !config.discordSecret) return 9 | 10 | const redirect = config.discordOAuth2Url 11 | 12 | app.get('/api/discord/callback', async (req, res, next) => { 13 | if (!req.session.ClientId || !req.query.code) { 14 | res.redirect('/') 15 | return 16 | } 17 | 18 | var response = await oauth.tokenRequest({ 19 | clientId: config.discordClientId, 20 | clientSecret: config.discordSecret, 21 | 22 | code: req.query.code, 23 | scope: ['identify', 'guilds'], 24 | grantType: 'authorization_code', 25 | 26 | redirectUri: redirect, 27 | }) 28 | 29 | var user = await oauth.getUser(response.access_token) 30 | Webfront.db.metaService.addPersistentMeta('discord_user', JSON.stringify(user), req.session.ClientId) 31 | Webfront.db.metaService.addPersistentMeta('discord_id', user.id, req.session.ClientId) 32 | 33 | res.redirect('/settings') 34 | }) 35 | 36 | app.get('/api/discord/disconnect', async (req, res, next) => { 37 | if (!req.session.ClientId) { 38 | res.redirect('/') 39 | return 40 | } 41 | 42 | Webfront.db.metaService.deletePersistentMeta('discord_user', req.session.ClientId) 43 | Webfront.db.metaService.deletePersistentMeta('discord_id', req.session.ClientId) 44 | res.redirect('/settings') 45 | }) 46 | 47 | app.get('/api/discord/login', async (req, res, next) => { 48 | res.redirect(`https://discordapp.com/api/oauth2/authorize?client_id=${config.discordClientId}&scope=identify&response_type=code&redirect_uri=${encodeURIComponent(redirect)}`) 49 | }) 50 | } -------------------------------------------------------------------------------- /Plugins/Routes/SocialMedia.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ejs = require('ejs') 3 | const config = require(path.join(__dirname, `../../Configuration/NSMConfiguration.json`)) 4 | 5 | module.exports = (app, db, Webfront) => { 6 | if (!config.socialMedia) return 7 | 8 | var validLinks = config.socialMedia.filter(link => link[1].toString().match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g)) 9 | 10 | app.get('/links', async (req, res, next) => { 11 | var header = await Webfront.renderDynamicHTML(req) 12 | res.setHeader('Content-type', 'html') 13 | 14 | ejs.renderFile(path.join(__dirname, '../../Webfront/html/links.ejs'), { 15 | header, validLinks 16 | }, (err, str) => { 17 | res.end(str) 18 | }) 19 | }) 20 | 21 | validLinks.forEach(link => { 22 | app.get(`/${link[0].toString()}`, async (req, res, next) => { 23 | res.status(301).redirect(link[1].toString()) 24 | }) 25 | }) 26 | 27 | Webfront.addHeaderHtml(``, 5) 28 | } -------------------------------------------------------------------------------- /Plugins/Routes/ZStats.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ejs = require('ejs') 3 | const Permissions = require(path.join(__dirname, `../../Configuration/NSMConfiguration.json`)).Permissions 4 | const config = require(path.join(__dirname, `../../Configuration/NSMConfiguration.json`)) 5 | const jsdom = new require('jsdom') 6 | 7 | module.exports = (app, db, Webfront) => { 8 | app.get('/api/zstats', async (req, res, next) => { 9 | var page = req.query.page ? req.query.page : 0 10 | var limit = 10 11 | var Stats = await db.getTopZStats(page, limit) 12 | for (var i = 0; i < Stats.length; i++) { 13 | delete Stats[i].Id 14 | } 15 | res.end(JSON.stringify(Stats)) 16 | }) 17 | 18 | app.get('/zstats', async (req, res, next) => { 19 | var Client = req.session.ClientId ? await db.getClient(req.session.ClientId) : {Name: 'Guest', ClientId: 0} 20 | var Motd = config.MOTD ? config.MOTD.replace('{USERNAME}', Client.Name) 21 | .replace('{CLIENTID}', Client.ClientId) : null 22 | 23 | res.setHeader('Content-type', 'text/html') 24 | var Stats = await db.getTopZStats(0, 10) 25 | var header = null 26 | 27 | ejs.renderFile(path.join(__dirname, '../../Webfront/html/header.ejs'), {session: req.session, Permissions: Permissions, Motd: Motd, Client: Client, config: config}, (err, str) => { 28 | var dom = new jsdom.JSDOM(str) 29 | for (var i = 0; i < Webfront.headerExtraHtml.length; i++) { 30 | var el = dom.window.document.createElement('div') 31 | el.innerHTML = Webfront.headerExtraHtml[i].html 32 | dom.window.document.getElementById('header-btns').insertBefore(el.firstChild, dom.window.document.getElementById('header-btns').children[Webfront.headerExtraHtml[i].index]) 33 | } 34 | header = dom.window.document.getElementById('wf-header').outerHTML 35 | }) 36 | 37 | ejs.renderFile(path.join(__dirname, '../../Webfront/html/zstats.ejs'), {header: header, Stats: Stats}, (err, str) => { 38 | res.end(str) 39 | }) 40 | }) 41 | } -------------------------------------------------------------------------------- /Plugins/Routes/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | 3 | module.exports = (app, db, Webfront) => { 4 | fs.readdirSync(__dirname).forEach(function(file) { 5 | if (file == "index.js") return 6 | 7 | var name = file.substr(0, file.indexOf('.')) 8 | require('./' + name)(app, db, Webfront) 9 | }) 10 | } -------------------------------------------------------------------------------- /Plugins/StatLogger.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const config = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)) 3 | 4 | class Plugin { 5 | constructor(Server, Manager) { 6 | this.Server = Server 7 | this.Manager = Manager 8 | this.Buffer = { Stats: {}, previousStats: {} } 9 | setInterval(this.updateStats.bind(this), 300 * 1000) 10 | this.init() 11 | } 12 | async playerConnected (Player) { 13 | Player.lastSeen = new Date() 14 | 15 | Player.on('death', async (Attacker, Attack) => { 16 | this.Buffer.Stats[Player.ClientId] = this.Buffer.Stats[Player.ClientId] 17 | ? this.Buffer.Stats[Player.ClientId] 18 | : await this.Server.DB.getPlayerStatsTotal(Player.ClientId) 19 | 20 | this.Buffer.Stats[Attacker.ClientId] = this.Buffer.Stats[Attacker.ClientId] 21 | ? this.Buffer.Stats[Attacker.ClientId] 22 | : await this.Server.DB.getPlayerStatsTotal(Attacker.ClientId) 23 | 24 | !this.Buffer.previousStats[Player.ClientId] && (this.Buffer.previousStats[Player.ClientId] = {}, Object.assign(this.Buffer.previousStats[Player.ClientId], this.Buffer.Stats[Player.ClientId])) 25 | 26 | !this.Buffer.previousStats[Attacker.ClientId] && (this.Buffer.previousStats[Attacker.ClientId] = {}, Object.assign(this.Buffer.previousStats[Attacker.ClientId], this.Buffer.Stats[Attacker.ClientId])) 27 | 28 | this.Buffer.Stats[Player.ClientId].Deaths++ 29 | 30 | if (Attacker.Clientslot != Player.Clientslot) { 31 | this.Buffer.Stats[Attacker.ClientId].Kills++ 32 | 33 | this.Buffer.Stats[Player.ClientId].TotalPerformance += this.Buffer.Stats[Attacker.ClientId].Performance - 400 34 | this.Buffer.Stats[Attacker.ClientId].TotalPerformance += this.Buffer.Stats[Player.ClientId].Performance + 400 35 | 36 | this.Buffer.Stats[Player.ClientId].Performance = (this.Buffer.Stats[Player.ClientId].TotalPerformance 37 | + (this.Buffer.Stats[Attacker.ClientId].Performance - 400)) 38 | / (this.Buffer.Stats[Player.ClientId].Kills 39 | + this.Buffer.Stats[Player.ClientId].Deaths) 40 | 41 | this.Buffer.Stats[Attacker.ClientId].Performance = (this.Buffer.Stats[Attacker.ClientId].TotalPerformance 42 | + (this.Buffer.Stats[Player.ClientId].Performance + 400)) 43 | / (this.Buffer.Stats[Attacker.ClientId].Kills 44 | + this.Buffer.Stats[Attacker.ClientId].Deaths) 45 | } 46 | }) 47 | 48 | Player.on('message', async (Message) => { 49 | if (Message.startsWith(config.commandPrefix)) return 50 | await this.Server.DB.logMessage(Player.ClientId, Player.Name, Player.Server.HostnameRaw, Message) 51 | }) 52 | } 53 | async updateStats() { 54 | Object.entries(this.Buffer.Stats).forEach(async Stats => { 55 | 56 | if (!this.Buffer.previousStats[Stats[0]] || (Stats[1].Kills <= this.Buffer.previousStats[Stats[0]].Kills && Stats[1].Deaths <= this.Buffer.previousStats[Stats[0]].Deaths)) return 57 | 58 | this.Server.DB.editStats(Stats[0], Stats[1]) 59 | 60 | this.Buffer.previousStats[Stats[0]] = {} 61 | Object.assign(this.Buffer.previousStats[Stats[0]], Stats[1]) 62 | }) 63 | } 64 | init () { 65 | this.Server.on('connect', this.playerConnected.bind(this)) 66 | this.playedTimeLogger() 67 | } 68 | playedTimeLogger() { 69 | setInterval(async () => { 70 | if (!this.Server.Rcon.isRunning) return 71 | 72 | this.Server.Clients.forEach(async Client => { 73 | if (!Client || !Client.ClientId) return 74 | 75 | this.Server.DB.incrementStat(Client.ClientId, (new Date() - Client.lastSeen) / 1000 / 60, 'PlayedTime') 76 | Client.lastSeen = new Date() 77 | 78 | var Stats = this.Buffer.Stats[Client.ClientId] ? this.Buffer.Stats[Client.ClientId] : await this.Server.DB.getPlayerStatsTotal(Client.ClientId) 79 | this.Server.DB.addStatRecord(Client.ClientId, Stats.TotalPerformance, Math.max(0, Stats.Performance)) 80 | }) 81 | }, 60000) 82 | } 83 | } 84 | 85 | module.exports = Plugin -------------------------------------------------------------------------------- /Plugins/WelcomeMessages.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fetch = require('node-fetch') 3 | const Utils = new (require(path.join(__dirname, '../Utils/Utils.js')))() 4 | const Localization = require(path.join(__dirname, `../Configuration/Localization-${process.env.LOCALE}.json`)).lookup 5 | const Permissions = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)).Permissions 6 | 7 | class Plugin { 8 | constructor(Server, Manager, Managers) { 9 | this.Server = Server 10 | this.Manager = Manager 11 | this.Managers = Managers 12 | this.joinMessages() 13 | } 14 | async joinMessages() { 15 | this.Server.on('disconnect', async (Player) => { 16 | this.Server.Broadcast(Utils.formatString(Localization['QUIT_PLAYER_BROADCAST'], {Name: Player.Name}, '%')[0]) 17 | }) 18 | 19 | this.Server.on('penalty', async (Type, Target, Reason, Origin, Duration = -1) => { 20 | if (Origin == 1) return 21 | 22 | Duration = Duration > 0 ? Utils.time2str(Duration) : '' 23 | this.Server.globalBroadcast(Utils.formatString(Localization[`${Type}_MESSAGE`], {Name: Target.Name, Reason, Origin: Origin.Name, Duration}, '%')[0]) 24 | }) 25 | 26 | this.Server.on('connect', async (Player) => { 27 | if (Player.IPAddress && Player.IPAddress.match(/(unknown|loopback|bot)/g)) return 28 | 29 | Player.IPAddress = Player.IPAddress ? Player.IPAddress : (await this.Server.DB.getClient(Player.ClientId)).IPAddress 30 | 31 | if (Player.PermissionLevel >= Permissions.Levels['ROLE_MODERATOR']) { 32 | Player.Tell(Utils.formatString(Localization['AUTO_RECENT_REPORTS'], { count: (await this.Server.DB.getActiveReports()).length }, '%')[0]) 33 | } 34 | 35 | if (process.env.NODE_ENV && process.env.NODE_ENV.toLocaleLowerCase() == 'dev') return 36 | 37 | var connections = await this.Server.DB.getAllConnections(Player.ClientId) 38 | 39 | Player.Tell(Localization['WELCOME_PLAYER'] 40 | .replace('%PLAYER%', Player.Name) 41 | .replace('%CONNECTIONS%', Utils.ordinalSuffix(connections.length | 1))) 42 | 43 | if (Player.Session && Player.Session.Data.Authorized) { 44 | Player.Tell('Logged in through previous session') 45 | } 46 | 47 | var setting = await this.Server.DB.metaService.getPersistentMeta('location', Player.ClientId) 48 | 49 | var role = Utils.getRoleFrom(Player.PermissionLevel, 1).Name 50 | 51 | var customTag = await this.Server.DB.metaService.getPersistentMeta('custom_tag', Player.ClientId) 52 | role = customTag ? `^7${customTag.Value}` : role 53 | 54 | if (!Player.IPAddress) { 55 | this.Server.Broadcast(Utils.formatString(Localization['WELCOME_PLAYER_BROADCAST'], { 56 | player: Player.Name, 57 | location: Localization['STRING_UNKNOWN'], 58 | level: Player.PermissionLevel, 59 | role 60 | }, '%')[0]) 61 | 62 | return 63 | } 64 | 65 | var info = !(setting && setting.Value == '1') 66 | ? await this.getInfo(Player.IPAddress.match(/(localhost|127\.0\.0\.1)/g) 67 | ? this.Server.externalIP 68 | : Player.IPAddress) 69 | : { country: Localization['STRING_HIDDEN'] } 70 | 71 | this.Server.Broadcast(Utils.formatString(Localization['WELCOME_PLAYER_BROADCAST'], { 72 | player: Player.Name, 73 | location: info ? info.country : Localization['STRING_UNKNOWN'], 74 | level: Player.PermissionLevel, 75 | role 76 | }, '%')[0]) 77 | }) 78 | } 79 | async getInfo(IPAddress) { 80 | var result = await fetch(`https://extreme-ip-lookup.com/json/${IPAddress.split(':')[0]}?key=demo`) 81 | if (result) { 82 | return await result.json() 83 | } 84 | return false 85 | } 86 | } 87 | 88 | module.exports = Plugin -------------------------------------------------------------------------------- /Scripts/t6/mp/gametypes_zm/Compiled/README.md: -------------------------------------------------------------------------------- 1 | # _clientids_bundle.gsc 2 | Contains: 3 | * Zombies Bank 4 | * Zombies stats 5 | * Zombies++ (https://github.com/Paintball/BO2-GSC-Releases/tree/master/Zombies%20Mods/Zombies%2B%2B%20v1.2) 6 | -------------------------------------------------------------------------------- /Scripts/t6/mp/gametypes_zm/Compiled/_clientids.gsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Scripts/t6/mp/gametypes_zm/Compiled/_clientids.gsc -------------------------------------------------------------------------------- /Scripts/t6/mp/gametypes_zm/Compiled/_clientids_bundle.gsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Scripts/t6/mp/gametypes_zm/Compiled/_clientids_bundle.gsc -------------------------------------------------------------------------------- /Scripts/t6/mp/zombies/Compiled/_zm_weapon_locker.gsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Scripts/t6/mp/zombies/Compiled/_zm_weapon_locker.gsc -------------------------------------------------------------------------------- /StartLogServer.bat: -------------------------------------------------------------------------------- 1 | node Lib/NodeLogServer.js -------------------------------------------------------------------------------- /StartLogServer.sh: -------------------------------------------------------------------------------- 1 | node Lib/NodeLogServer.js -------------------------------------------------------------------------------- /StartNSM.bat: -------------------------------------------------------------------------------- 1 | node Lib/NodeServerManager.js -------------------------------------------------------------------------------- /StartNSM.sh: -------------------------------------------------------------------------------- 1 | curl https://raw.githubusercontent.com/creationix/nvm/v0.11.1/install.sh | bash 2 | source ~/.nvm/nvm.sh 3 | source ~/.profile 4 | source ~/.bashrc 5 | 6 | nvm use 14.15.0 7 | mkdir -p Database 8 | node ./Lib/NodeServerManager.js --trace-warnings 9 | read -p 'Press any key to continue' 10 | -------------------------------------------------------------------------------- /StartNSMUpdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Checking for updates..." 4 | latest=$(git ls-remote https://github.com/fedddddd/node-server-manager.git HEAD | awk '{print $1}') 5 | local=$(git rev-parse HEAD) 6 | 7 | if [[ "$latest" != "$local" ]]; then 8 | read -p "An update is available, update? " update 9 | if [[ "$update" == *"y"* ]]; then 10 | git pull 11 | fi 12 | fi 13 | 14 | echo "Running node-server-manager v$local" 15 | 16 | echo "Checking for nodejs updates..." 17 | 18 | curl https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh --silent | bash > /dev/null 19 | 20 | source ~/.nvm/nvm.sh 21 | source ~/.profile 22 | source ~/.bashrc 23 | 24 | nvm install node 25 | 26 | mkdir -p Database 27 | node ./Lib/NodeServerManager.js 28 | read -p 'Press any key to continue' -------------------------------------------------------------------------------- /Update.bat: -------------------------------------------------------------------------------- 1 | git pull -------------------------------------------------------------------------------- /Utils/Mutex.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | 3 | class Mutex extends EventEmitter { 4 | constructor() { 5 | super() 6 | this.locked = false 7 | } 8 | 9 | lock() { 10 | if (!this.locked) { 11 | this.locked = true 12 | return 13 | } 14 | 15 | return new Promise((resolve, reject) => { 16 | const onRelease = () => { 17 | if (this.locked) { 18 | return 19 | } 20 | 21 | this.locked = true 22 | resolve() 23 | 24 | this.removeListener('release', onRelease) 25 | } 26 | 27 | this.on('release', onRelease) 28 | }) 29 | } 30 | 31 | unlock() { 32 | this.locked = false 33 | this.emit('release') 34 | } 35 | } 36 | 37 | module.exports = Mutex -------------------------------------------------------------------------------- /Webfront/Public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/android-icon-144x144.png -------------------------------------------------------------------------------- /Webfront/Public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/apple-icon-180x180.png -------------------------------------------------------------------------------- /Webfront/Public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /Webfront/Public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/apple-icon.png -------------------------------------------------------------------------------- /Webfront/Public/css/xbbcode.css: -------------------------------------------------------------------------------- 1 | .xbbcode-b { 2 | font-family:codefb; 3 | } 4 | 5 | .xbbcode-blockquote { 6 | 7 | } 8 | 9 | .xbbcode-center { 10 | margin-left:auto; 11 | margin-right:auto; 12 | display: block; 13 | text-align: center; 14 | } 15 | 16 | .xbbcode-code { 17 | white-space: pre-wrap; 18 | user-select:text; 19 | border-radius:5px; 20 | padding:5px; 21 | background-color: var(--color-2); 22 | font-family: monospace; 23 | } 24 | 25 | .xbbcode-i { 26 | font-family:codefi; 27 | } 28 | 29 | .xbbcode-justify { 30 | display: block; 31 | text-align: justify; 32 | } 33 | 34 | .xbbcode-colorcode * { 35 | display:inline-block; 36 | } 37 | 38 | .xbbcode-colorcode { 39 | display: inline-block; 40 | } 41 | 42 | .xbbcode-line { 43 | display:block; 44 | height: 1em; 45 | } 46 | 47 | .xbbcode-radius * { 48 | border-radius:inherit; 49 | } 50 | 51 | .xbbcode-left { 52 | display: block; 53 | text-align: left; 54 | } 55 | 56 | .xbbcode-right { 57 | display: block; 58 | text-align: right; 59 | } 60 | 61 | .xbbcode-s { 62 | text-decoration: line-through; 63 | } 64 | 65 | .xbbcode-size-4 {font-size:4px;} 66 | .xbbcode-size-5 {font-size:5px;} 67 | .xbbcode-size-6 {font-size:6px;} 68 | .xbbcode-size-7 {font-size:7px;} 69 | .xbbcode-size-8 {font-size:8px;} 70 | .xbbcode-size-9 {font-size:9px;} 71 | .xbbcode-size-10 {font-size:10px;} 72 | .xbbcode-size-11 {font-size:11px;} 73 | .xbbcode-size-12 {font-size:12px;} 74 | .xbbcode-size-13 {font-size:13px;} 75 | .xbbcode-size-14 {font-size:14px;} 76 | .xbbcode-size-15 {font-size:15px;} 77 | .xbbcode-size-16 {font-size:16px;} 78 | .xbbcode-size-17 {font-size:17px;} 79 | .xbbcode-size-18{font-size:18px;} 80 | .xbbcode-size-19 {font-size:19px;} 81 | .xbbcode-size-20 {font-size:20px;} 82 | .xbbcode-size-21 {font-size:21px;} 83 | .xbbcode-size-22 {font-size:22px;} 84 | .xbbcode-size-23 {font-size:23px;} 85 | .xbbcode-size-24 {font-size:24px;} 86 | .xbbcode-size-25 {font-size:25px;} 87 | .xbbcode-size-26 {font-size:26px;} 88 | .xbbcode-size-27 {font-size:27px;} 89 | .xbbcode-size-28 {font-size:28px;} 90 | .xbbcode-size-29 {font-size:29px;} 91 | .xbbcode-size-30 {font-size:30px;} 92 | .xbbcode-size-31 {font-size:31px;} 93 | .xbbcode-size-32 {font-size:32px;} 94 | .xbbcode-size-33 {font-size:33px;} 95 | .xbbcode-size-34 {font-size:34px;} 96 | .xbbcode-size-35 {font-size:35px;} 97 | .xbbcode-size-36 {font-size:36px;} 98 | .xbbcode-size-37 {font-size:37px;} 99 | .xbbcode-size-38 {font-size:38px;} 100 | .xbbcode-size-39 {font-size:39px;} 101 | .xbbcode-size-40 {font-size:40px;} 102 | 103 | .xbbcode-u { 104 | text-decoration: underline; 105 | } 106 | 107 | .xbbcode-table { 108 | border-collapse:collapse; 109 | } 110 | 111 | .xbbcode-tr { 112 | 113 | } 114 | 115 | .xbbcode-table , .xbbcode-th, .xbbcode-td { 116 | border: 1px solid var(--color-text); 117 | } 118 | -------------------------------------------------------------------------------- /Webfront/Public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/favicon-16x16.png -------------------------------------------------------------------------------- /Webfront/Public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/favicon-32x32.png -------------------------------------------------------------------------------- /Webfront/Public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/favicon-96x96.png -------------------------------------------------------------------------------- /Webfront/Public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/favicon.ico -------------------------------------------------------------------------------- /Webfront/Public/fonts/Fira-Mono-Regular-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Fira-Mono-Regular-Italic.otf -------------------------------------------------------------------------------- /Webfront/Public/fonts/FiraCode-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/FiraCode-Bold.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/FiraCode-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/FiraCode-Light.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/FiraCode-Retina.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/FiraCode-Retina.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2011 by Accademia di Belle Arti di Urbino and students of MA course of Visual design. Some rights reserved. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Black.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Bold.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Italic.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Light.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-Regular.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/iw/bankrus.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/iw/bankrus.ttf -------------------------------------------------------------------------------- /Webfront/Public/fonts/iw/hudsmall.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/iw/hudsmall.otf -------------------------------------------------------------------------------- /Webfront/Public/fonts/iw/hudsmall.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/fonts/iw/hudsmall.ttf -------------------------------------------------------------------------------- /Webfront/Public/img/maps/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/default.png -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_backlot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_backlot.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_bloc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_bloc.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_bog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_bog.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_broadcast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_broadcast.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_carentan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_carentan.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_cargoship.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_cargoship.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_citystreets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_citystreets.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_convoy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_convoy.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_countdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_countdown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_crash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_crash.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_crash_snow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_crash_snow.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_creek.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_creek.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_crossfire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_crossfire.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_dusk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_dusk.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_farm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_farm.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_hill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_hill.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_invasion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_invasion.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_killhouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_killhouse.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_overgrown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_overgrown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_pipeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_pipeline.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_shipment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_shipment.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_showdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_showdown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_strike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_strike.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw3/mp_vacant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw3/mp_vacant.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_afghan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_afghan.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_bloc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_bloc.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_bloc_sh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_bloc_sh.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_bog_sh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_bog_sh.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_boneyard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_boneyard.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_brecourt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_brecourt.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_cargoship_sh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_cargoship_sh.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_checkpoint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_checkpoint.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_crash_tropical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_crash_tropical.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_derail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_derail.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_estate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_estate.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_estate_tropical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_estate_tropical.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_fav_tropical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_fav_tropical.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_favela.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_favela.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_firingrange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_firingrange.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_highrise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_highrise.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_invasion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_invasion.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_killhouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_killhouse.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_nightshift.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_nightshift.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_nuked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_nuked.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_quarry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_quarry.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_rundown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_rundown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_rust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_rust.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_shipment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_shipment.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_storm_spring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_storm_spring.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_subbase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_subbase.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_terminal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_terminal.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw4/mp_underpass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw4/mp_underpass.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_aground_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_aground_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_alpha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_alpha.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_boardwalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_boardwalk.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_bootleg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_bootleg.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_bravo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_bravo.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_burn_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_burn_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_carbon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_carbon.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_cement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_cement.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_courtyard_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_courtyard_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_crosswalk_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_crosswalk_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_dome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_dome.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_exchange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_exchange.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_hardhat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_hardhat.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_highrise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_highrise.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_hillside_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_hillside_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_interchange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_interchange.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_italy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_italy.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_lambeth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_lambeth.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_meteora.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_meteora.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_moab.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_moab.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_mogadishu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_mogadishu.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_morningwood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_morningwood.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_nola.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_nola.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_overgrown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_overgrown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_overwatch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_overwatch.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_paris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_paris.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_park.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_park.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_plaza2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_plaza2.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_qadeem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_qadeem.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_radar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_radar.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_restrepo_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_restrepo_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_roughneck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_roughneck.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_rust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_rust.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_seatown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_seatown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_shipbreaker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_shipbreaker.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_six_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_six_ss.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_terminal_cls.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_terminal_cls.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_underground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_underground.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/iw5/mp_village.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/iw5/mp_village.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_bridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_bridge.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_carrier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_carrier.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_castaway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_castaway.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_concert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_concert.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_dig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_dig.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_dockside.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_dockside.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_downhill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_downhill.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_drone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_drone.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_express.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_express.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_frostbite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_frostbite.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_hijacked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_hijacked.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_hydro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_hydro.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_la.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_la.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_magma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_magma.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_meltdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_meltdown.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_mirage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_mirage.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_nightclub.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_nightclub.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_overflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_overflow.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_paintball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_paintball.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_podville.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_podville.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_raid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_raid.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_skate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_skate.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_slums.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_slums.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_socotra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_socotra.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_studio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_studio.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_takeoff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_takeoff.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_turbine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_turbine.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_uplink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_uplink.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_vertigo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_vertigo.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/mp_village.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/mp_village.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_buried.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_buried.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_factory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_factory.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_highrise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_highrise.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_meat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_meat.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_moon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_moon.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_nuked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_nuked.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_prison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_prison.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_prototype.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_prototype.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_tomb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_tomb.jpg -------------------------------------------------------------------------------- /Webfront/Public/img/maps/t6/zm_transit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/img/maps/t6/zm_transit.jpg -------------------------------------------------------------------------------- /Webfront/Public/js/audit.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', async () => { 2 | var nextPage = 1 3 | var pageLoaded = true 4 | var maxPage = false 5 | window.addEventListener('scroll', async () => { 6 | if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight && pageLoaded && !maxPage) { 7 | 8 | pageLoaded = false 9 | var nextMessages = JSON.parse(await makeRequest('GET', `/api/audit?page=${nextPage}&limit=25`)) 10 | nextMessages.forEach(log => { 11 | logAudit(log) 12 | }) 13 | pageLoaded = true 14 | nextPage++ 15 | maxPage = (nextMessages.length + 1 < 25) 16 | } 17 | }) 18 | }) 19 | 20 | function logAudit(log) { 21 | if (!log) return 22 | document.getElementById('audit-cont').appendChild(createElementFromHTML(` 23 |
24 | ${log.Origin.ClientId ? `${log.Origin.Name}` : `
${log.Origin.Name}
`} 25 |
${log.Type}
26 |
${log.Description}
27 |
${moment(new Date(log.Date).toISOString()).calendar()}
28 |
29 | `)) 30 | } -------------------------------------------------------------------------------- /Webfront/Public/js/authenticator.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | document.querySelectorAll('*[data-allow]').forEach(allow => { 3 | allow.addEventListener('click', async (e) => { 4 | await makeRequest('GET', `/api/authenticator?action=allow&session=${e.target.getAttribute('data-allow')}`) 5 | window.location.reload() 6 | }) 7 | }) 8 | document.querySelectorAll('*[data-kick]').forEach(kick => { 9 | kick.addEventListener('click', async (e) => { 10 | await makeRequest('GET', `/api/authenticator?action=kick&session=${e.target.getAttribute('data-kick')}`) 11 | }) 12 | }) 13 | }) -------------------------------------------------------------------------------- /Webfront/Public/js/chat.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws' 3 | var socket = new WebSocket(`${wsProtocol}://${window.location.host}/?action=socket_listen_messages`) 4 | socket.onopen = () => { 5 | setInterval(() => { 6 | socket.send(JSON.stringify({action: 'heartbeat'})) 7 | }, 5000) 8 | } 9 | socket.addEventListener('message', (e) => { 10 | var msg = JSON.parse(e.data) 11 | if (msg.event == 'event_client_message') { 12 | logMessage(msg.Message, msg.Client.Name, msg.Client.ClientId, msg.Hostname, new Date(), false) 13 | } 14 | 15 | }) 16 | var nextPage = 1 17 | var pageLoaded = true 18 | var maxPage = false 19 | var previousDate = new Date() 20 | document.getElementById('message-log') && document.getElementById('message-log').addEventListener('scroll', async (e) => { 21 | var log = document.getElementById('message-log') 22 | if (log.scrollTop + 50 >= (log.scrollHeight - log.offsetHeight) && pageLoaded && !maxPage) { 23 | pageLoaded = false 24 | var nextMessages = JSON.parse(await makeRequest('GET', `/api/messages?&page=${nextPage}&limit=50`)) 25 | console.log(nextMessages) 26 | nextMessages.forEach(Message => { 27 | if ( ( previousDate - new Date(Message.Date) ) / 1000 / 60 / 30 >= 1) { 28 | previousDate = new Date(Message.Date) 29 | addSeparator(new Date(Message.Date)) 30 | } 31 | logMessage(Message.Message, Message.Name, Message.OriginId, Message.Hostname, new Date(Message.Date), true) 32 | }) 33 | pageLoaded = true 34 | nextPage++ 35 | maxPage = (nextMessages.length + 1 < 50) 36 | } 37 | }) 38 | }) 39 | 40 | function addSeparator(Date) { 41 | var msg = createElementFromHTML( 42 | `
43 |
44 | ${moment(Date).calendar()} 45 |
46 |
` 47 | ) 48 | document.getElementById('message-log').appendChild(msg) 49 | } 50 | 51 | function escapeHtml(text) { 52 | var map = { 53 | '&': '&', 54 | '<': '<', 55 | '>': '>', 56 | '"': '"', 57 | "'": ''' 58 | }; 59 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 60 | } 61 | 62 | function logMessage(Message, Name, ClientId, Hostname, Date, Append) { 63 | Message = escapeHtml(Message) 64 | var msg = createElementFromHTML( 65 | `
66 |
67 | ${Name} @ ${COD2HTML(Hostname, '')}:
68 |
${COD2HTML(Message, '')}
69 |
${moment(Date).calendar()}
70 |
` 71 | ) 72 | profileHover(msg.querySelector('a'), ClientId) 73 | Append ? document.getElementById('message-log').appendChild(msg) : document.getElementById('message-log').prepend(msg) 74 | } 75 | 76 | function createElementFromHTML(htmlString) { 77 | var div = document.createElement('div'); 78 | div.innerHTML = htmlString.trim(); 79 | return div.firstChild; 80 | } -------------------------------------------------------------------------------- /Webfront/Public/js/font-awesome.js: -------------------------------------------------------------------------------- 1 | window.FontAwesomeKitConfig = {"asyncLoading":{"enabled":false},"autoA11y":{"enabled":true},"baseUrl":"https://kit-pro.fontawesome.com","detectConflictsUntil":null,"license":"pro","method":"css","minify":{"enabled":true},"v4FontFaceShim":{"enabled":true},"v4shim":{"enabled":true},"version":"latest"}; 2 | !function(){function r(e){var t,n=[],i=document,o=i.documentElement.doScroll,r="DOMContentLoaded",a=(o?/^loaded|^c/:/^loaded|^i|^c/).test(i.readyState);a||i.addEventListener(r,t=function(){for(i.removeEventListener(r,t),a=1;t=n.shift();)t()}),a?setTimeout(e,0):n.push(e)}!function(){if(!(void 0===window.Element||"classList"in document.documentElement)){var e,t,n,i=Array.prototype,o=i.push,r=i.splice,a=i.join;d.prototype={add:function(e){this.contains(e)||(o.call(this,e),this.el.className=this.toString())},contains:function(e){return-1!=this.el.className.indexOf(e)},item:function(e){return this[e]||null},remove:function(e){if(this.contains(e)){for(var t=0;t { 2 | var info = document.querySelector('.wf-info-text') 3 | info.addEventListener("paste", function(e) { 4 | e.preventDefault() 5 | var text = (e.originalEvent || e).clipboardData.getData('text/plain') 6 | document.execCommand("insertHTML", false, escapeHtml(text)) 7 | }) 8 | }) 9 | 10 | function infoEdit() { 11 | var info = document.querySelector('.wf-info-text') 12 | if (info.getAttribute('contenteditable') == 'true') { 13 | submitInfo(info) 14 | return 15 | } 16 | info.innerHTML = info.getAttribute('data-raw-text') 17 | info.setAttribute('contenteditable', true) 18 | } 19 | 20 | async function xbbFormat(el) { 21 | var rawText = await replacePlaceholders(el.textContent.trim()) 22 | var result = XBBCODE.process({ 23 | text: rawText, 24 | removeMisalignedTags: true, 25 | addInLineBreaks: false 26 | }) 27 | el.innerHTML = result.html 28 | } 29 | 30 | var escapeHtml = (text) => { 31 | var map = { 32 | '&': '&', 33 | '<': '<', 34 | '>': '>', 35 | '"': '"', 36 | "'": ''' 37 | }; 38 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 39 | } 40 | 41 | function submitInfo(info) { 42 | info.innerHTML = info.innerHTML.replace(new RegExp(/
/g, 'g'), `\n`).replace(/\uFFFD/g, '').replace(/ /g, ' ') 43 | info.setAttribute('data-raw-text', info.textContent.trim()) 44 | xbbFormat(info) 45 | info.setAttribute('contenteditable', false) 46 | makeRequest('GET', `/api/admin?command=COMMAND_CHANGE_INFO&value=${JSON.stringify({value: btoa(info.getAttribute('data-raw-text'))})}`, null) 47 | } -------------------------------------------------------------------------------- /Webfront/Public/js/notifications.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws' 3 | var socket = new WebSocket(`${wsProtocol}://${window.location.host}/?action=socket_listen_messages`) 4 | 5 | socket.addEventListener('message', (e) => { 6 | var msg = JSON.parse(e.data) 7 | if (msg.event == 'event_client_message') { 8 | notifyMe(MessageBuilder) 9 | } 10 | }) 11 | document.body.appendChild(parseHTML(`
`)) 12 | }) 13 | 14 | function parseHTML(html) { 15 | var t = document.createElement('template'); 16 | t.innerHTML = html; 17 | return t.content.cloneNode(true); 18 | } 19 | 20 | async function notifyMe(msg) { 21 | const notifications = document.getElementById("notifications-cont") 22 | var n = document.createDocumentFragment() 23 | var Message = escapeHtml(msg.Message) 24 | var notif = createElementFromHTML(` 25 |
26 |
27 |
28 |
${msg.Client.Name} @
${parseCODColorCodes(msg.Hostname, 'var(--color-text)').outerHTML}
29 |
${Message}
30 |
31 |
32 | 33 |
34 |
`) 35 | 36 | n.appendChild(notif) 37 | 38 | var elements = notif.children 39 | var notifyText = elements[1].children 40 | 41 | notif.addEventListener("click", function() { 42 | window.location.href = '/chat' 43 | }) 44 | 45 | var notifTimeout = setTimeout(() => { 46 | notif.classList.remove("notifFadeIn") 47 | notif.style.opacity = "1" 48 | notif.style.transform = "translateX(0px)" 49 | setTimeout(() => { 50 | notif.remove() 51 | }, 300) 52 | }, 3000) 53 | 54 | notif.addEventListener("mouseover", function() { 55 | clearTimeout(notifTimeout); 56 | }) 57 | 58 | notif.addEventListener("mouseout", function() { 59 | notifTimeout = setTimeout(() => { 60 | notif.classList.remove("notifFadeIn"); 61 | notif.style.opacity = "1"; 62 | notif.style.transform = "translateX(0px)"; 63 | setTimeout(() => { 64 | notif.remove() 65 | }, 300) 66 | }, 3000) 67 | }) 68 | 69 | notif.querySelector("*[notification-dismiss]").addEventListener("click", function() { 70 | clearTimeout(notifTimeout) 71 | notif.classList.remove("notifFadeIn") 72 | notif.style.opacity = "1" 73 | notif.style.transform = "translateX(0px)" 74 | setTimeout(() => { 75 | notif.remove() 76 | }, 300) 77 | }) 78 | 79 | notifications.appendChild(n) 80 | } 81 | 82 | function escapeHtml(text) { 83 | var map = { 84 | '&': '&', 85 | '<': '<', 86 | '>': '>', 87 | '"': '"', 88 | "'": ''' 89 | }; 90 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 91 | } 92 | function makeRequest (method, url, data, contentType = null) { 93 | return new Promise(function (resolve, reject) { 94 | var xhr = new XMLHttpRequest() 95 | xhr.open(method, url, true) 96 | contentType && xhr.setRequestHeader('Content-type', contentType) 97 | xhr.onload = function () { 98 | if (this.status >= 200 && this.status < 300) { 99 | resolve(xhr.response) 100 | } else { 101 | reject({ 102 | status: this.status, 103 | statusText: xhr.statusText 104 | }); 105 | } 106 | }; 107 | xhr.onerror = function () { 108 | reject({ 109 | status: this.status, 110 | statusText: xhr.statusText 111 | }); 112 | }; 113 | xhr.send(data) 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /Webfront/Public/js/settings.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | var Password = null 3 | document.querySelectorAll('*[data-setting]').forEach(setting => { 4 | if (setting.hasAttribute('disabled')) return 5 | setting.addEventListener('click', async (e) => { 6 | if (setting.getAttribute('data-setting') != 'auth-2fa' || 7 | (setting.getAttribute('data-setting') == 'auth-2fa' && !clientSettings.Settings.TwoFactor)) { 8 | Password = Password ? Password : await askPassword() 9 | } 10 | 11 | switch (setting.getAttribute('data-setting')) { 12 | case 'auth-token': 13 | await makeFormRequest('POST', `/auth/changesetting?setting=TokenLogin&value=${!clientSettings.Settings.TokenLogin}`, `password=${Password}`) 14 | window.location.reload() 15 | break 16 | case 'auth-2fa': 17 | if (!clientSettings.Settings.TwoFactor) { 18 | var enable = document.querySelector('*[data-2fa-enable]') 19 | var secret = JSON.parse(await makeFormRequest('GET', '/auth/2fa?action=request', null)) 20 | enable.querySelector('*[data-2fauth-link]').src = secret.secret.qr 21 | enable.querySelector('*[data-2fauth-code]').innerHTML = secret.secret.secret 22 | enable.style.display = 'block' 23 | var button = enable.querySelector('*[data-2fa-confirm]') 24 | button.onclick = () => { 25 | messageBox('Enter the code generated by your authenticator:', [{ 26 | type: 'password', 27 | name: 'token', 28 | placeholder: '2FA Code' 29 | }], 'Cancel', 'Ok', async (params, messageBox, closeMessagebox) => { 30 | var result = JSON.parse(await makeFormRequest('POST', '/auth/2fa?action=enable', `password=${Password}&token=${params.token}`)) 31 | result.success ? (closeMessagebox(), window.location.reload()) : (messageBox.setText('Incorrect code')) 32 | }) 33 | } 34 | } else { 35 | messageBox('Enter your password and the code generated by your authenticator:', [ 36 | { type: 'password', name: 'password', placeholder: 'Password' }, 37 | { type: 'password', name: 'token', placeholder: '2FA Code' } 38 | ], 'Cancel', 'Ok', async (params, messageBox, closeMessagebox) => { 39 | var result = JSON.parse(await makeFormRequest('POST', '/auth/2fa?action=disable', `password=${params.password}&token=${params.token}`)) 40 | result.success ? (closeMessagebox(), window.location.reload()) : (messageBox.setText('Incorrect code')) 41 | }) 42 | } 43 | break 44 | case 'auth-ingame': 45 | await makeFormRequest('POST', `/auth/changesetting?setting=InGameLogin&value=${!clientSettings.Settings.InGameLogin}`, `password=${Password}`) 46 | window.location.reload() 47 | break 48 | } 49 | }) 50 | }) 51 | }) 52 | 53 | function askPassword() { 54 | return new Promise((resolve, reject) => { 55 | messageBox('Please enter your password:', [{ 56 | type: 'password', 57 | name: 'password', 58 | placeholder: 'Password' 59 | }], 'Cancel', 'Ok', async (params, messageBox, closeMessagebox) => { 60 | var result = JSON.parse(await makeFormRequest('POST', '/auth/auth?_=password', `password=${params.password}`)) 61 | result.success ? (closeMessagebox(), resolve(params.password)) : (messageBox.setText('Incorrect password')) 62 | }) 63 | }) 64 | } -------------------------------------------------------------------------------- /Webfront/Public/js/stats.js: -------------------------------------------------------------------------------- 1 | window.onscroll = async (ev) => { 2 | if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight && !maxPage && pageLoaded) { 3 | pageLoaded = false 4 | var nextStats = JSON.parse(await makeRequest('GET', `/api/stats?&page=${nextPage}`)) 5 | appendStats(nextStats) 6 | pageLoaded = true 7 | nextPage++ 8 | maxPage = (nextStats.length + 1 < 10) 9 | } 10 | } 11 | 12 | var nextPage = 1 13 | var pageLoaded = true 14 | var maxPage = false 15 | 16 | function makeRequest (method, url, data) { 17 | return new Promise(function (resolve, reject) { 18 | var xhr = new XMLHttpRequest() 19 | xhr.open(method, url, true) 20 | xhr.onload = function () { 21 | if (this.status >= 200 && this.status < 300) { 22 | resolve(xhr.response) 23 | } else { 24 | reject({ 25 | status: this.status, 26 | statusText: xhr.statusText 27 | }); 28 | } 29 | }; 30 | xhr.onerror = function () { 31 | reject({ 32 | status: this.status, 33 | statusText: xhr.statusText 34 | }); 35 | }; 36 | xhr.send(data) 37 | }); 38 | } 39 | 40 | var appendStats = (Stats) => { 41 | Stats.forEach(Stat => { 42 | document.getElementById('stat-list').appendChild((parseHTML(` 43 |
44 |
45 |
#${Stat.Rank} - ${Stat.Name}
46 |
${Stat.Kills} ${Stat.Kills == 1 ? 'Kill' : 'Kills'}
47 |
${Stat.Deaths} ${Stat.Deaths == 1 ? 'Death' : 'Deaths'}
48 |
${(Stat.Kills / Math.max(Stat.Deaths, 1)).toFixed(2)} KDR
49 |
${Stat.Performance.toFixed(1)} Performance
50 |
Played for ${Stat.PlayedTimeString}
51 |
52 |
53 |
54 | `))) 55 | 56 | for (var i = 0; i < Stat.History.length; i++) { 57 | Stat.History[i].x = i 58 | } 59 | 60 | renderPerformanceChart(`${Stat.ClientId}_history`, Stat.History, true, '#D5D0C7') 61 | }) 62 | } 63 | 64 | var parseHTML = (html) => { 65 | var t = document.createElement('template'); 66 | t.innerHTML = html; 67 | return t.content.cloneNode(true); 68 | } 69 | 70 | window.addEventListener('load', async () => { 71 | document.querySelectorAll('*[data-canvas]').forEach(canvas => { 72 | var data = JSON.parse(canvas.getAttribute('data-canvas')) 73 | 74 | for (var i = 0; i < data.length; i++) { 75 | data[i].x = i 76 | } 77 | 78 | console.log(data) 79 | 80 | parseInt(canvas.getAttribute('data-rank')) > 3 ? renderPerformanceChart(canvas.id, data, true, '#D5D0C7') : renderPerformanceChart(canvas.id, data, true, '#0C0D0E') 81 | }) 82 | }) 83 | 84 | function renderPerformanceChart(id, data, animation, color) { 85 | var chart = new CanvasJS.Chart(id, { 86 | theme: "dark1", // "light1", "light2", "dark1", "dark2" 87 | defaultFontFamily: "codef", 88 | animationEnabled: animation, 89 | backgroundColor: "transparent", 90 | zoomEnabled: false, 91 | height:150, 92 | title: { 93 | text: 'Performance History', 94 | fontFamily: "codef", 95 | fontColor: color, 96 | }, 97 | fontFamily: "codef", 98 | xValueType: "number", 99 | toolTip: { 100 | cornerRadius: 5, 101 | fontFamily: "codef", 102 | contentFormatter: function (e) { 103 | return e.entries[0].dataPoint.y.toFixed(1) 104 | } 105 | }, 106 | axisX: { 107 | interval: 1, 108 | gridThickness: 0, 109 | lineThickness: 1, 110 | tickThickness: 0, 111 | margin: 0, 112 | valueFormatString: " " 113 | }, 114 | axisY: { 115 | gridThickness: 0, 116 | lineThickness: 0, 117 | tickThickness: 0, 118 | margin: 0, 119 | valueFormatString: "", 120 | labelMaxWidth: 100, 121 | labelFontColor: color, 122 | labelFontFamily: 'codef', 123 | }, 124 | fontColor: color, 125 | data: [{ 126 | showInLegend: false, 127 | type: "splineArea", 128 | color: color, 129 | markerSize: 0, 130 | dataPoints: data 131 | }] 132 | }); 133 | chart.render() 134 | document.getElementById(id).offsetWidth; 135 | } -------------------------------------------------------------------------------- /Webfront/Public/js/zstats.js: -------------------------------------------------------------------------------- 1 | window.onscroll = async (ev) => { 2 | if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight && !maxPage && pageLoaded) { 3 | pageLoaded = false 4 | var nextStats = JSON.parse(await makeRequest('GET', `/api/zstats?&page=${nextPage}`)) 5 | appendStats(nextStats) 6 | pageLoaded = true 7 | nextPage++ 8 | maxPage = (nextStats.length + 1 < 10) 9 | } 10 | } 11 | 12 | var nextPage = 1 13 | var pageLoaded = true 14 | var maxPage = false 15 | 16 | function makeRequest (method, url, data) { 17 | return new Promise(function (resolve, reject) { 18 | var xhr = new XMLHttpRequest() 19 | xhr.open(method, url, true) 20 | xhr.onload = function () { 21 | if (this.status >= 200 && this.status < 300) { 22 | resolve(xhr.response) 23 | } else { 24 | reject({ 25 | status: this.status, 26 | statusText: xhr.statusText 27 | }); 28 | } 29 | }; 30 | xhr.onerror = function () { 31 | reject({ 32 | status: this.status, 33 | statusText: xhr.statusText 34 | }); 35 | }; 36 | xhr.send(data) 37 | }); 38 | } 39 | 40 | var appendStats = (Stats) => { 41 | Stats.forEach(Stat => { 42 | document.getElementById('stat-list').appendChild((parseHTML(` 43 |
44 |
45 |
#${Stat.Rank} - ${Stat.Name}
46 |
${Stat.Kills} ${Stat.Kills == 1 ? 'Kill' : 'Kills'}
47 |
${Stat.Downs} ${Stat.Downs == 1 ? 'Down' : 'Downs'}
48 |
${Stat.Revives} ${Stat.Revives == 1 ? 'Revive' : 'Revives'}
49 |
${Stat.Headshots} ${Stat.Headshots == 1 ? 'Headshot' : 'Headshots'}
50 |
${Stat.Score} Score
51 |
52 |
${Stat.HighestRound}
53 |
54 | `))) 55 | }) 56 | } 57 | 58 | var parseHTML = (html) => { 59 | var t = document.createElement('template'); 60 | t.innerHTML = html; 61 | return t.content.cloneNode(true); 62 | } -------------------------------------------------------------------------------- /Webfront/Public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Webfront/Public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alicealys/node-server-manager/d22ebfbf4cedc22f5d8d3a78d3f2adc250215926/Webfront/Public/ms-icon-310x310.png -------------------------------------------------------------------------------- /Webfront/Public/opensearch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Search 4 | Node Server Manager Search 5 | Search clients 6 | 7 | UTF-8 8 | UTF-8 9 | false 10 | en-us 11 | open 12 | github.com/fedddddd 13 | tag1,tag2 14 | /favicon.ico 15 | 16 | 17 | -------------------------------------------------------------------------------- /Webfront/SessionStore.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const config = require(path.join(__dirname, `../Configuration/NSMConfiguration.json`)) 3 | const crypto = require('crypto') 4 | 5 | class SessionStore { 6 | constructor() { 7 | this.Sessions = [] 8 | } 9 | createSession(IPAddress) { 10 | var newSession = null 11 | this.Sessions.forEach(Session => { 12 | if (newSession) return 13 | if (Session.IPAddress == IPAddress && (new Date() - Session.Date) < config.sessionDuration * 1000 * 60 ) { 14 | newSession = { 15 | ID: crypto.randomBytes(16).toString('hex'), 16 | Date: new Date(), 17 | IPAddress: IPAddress, 18 | Data: Session.Data 19 | } 20 | } 21 | }) 22 | if (!newSession) { 23 | newSession = { 24 | ID: crypto.randomBytes(16).toString('hex'), 25 | Date: new Date(), 26 | IPAddress: IPAddress, 27 | Data: {} 28 | } 29 | } 30 | this.Sessions.push(newSession) 31 | return newSession 32 | } 33 | getSession(ID) { 34 | var found = false 35 | this.Sessions.forEach(Session => { 36 | if (Session.ID == ID) { 37 | found = Session 38 | } 39 | }) 40 | return found 41 | } 42 | } 43 | 44 | module.exports = SessionStore -------------------------------------------------------------------------------- /Webfront/api/Auth.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt') 2 | const twoFact = require('node-2fa') 3 | 4 | class Auth { 5 | constructor(db) { 6 | this.db = db 7 | } 8 | getSettings(ClientId) { 9 | 10 | } 11 | async Password(ClientId, Password) { 12 | //var tokenHash = await db.getTokenHash(req.session.ClientId) 13 | var passwordHash = await this.db.getClientField(ClientId, 'Password') 14 | if (!passwordHash) return false 15 | return new Promise((resolve, reject) => { 16 | bcrypt.compare(Password, passwordHash, (err, same) => { 17 | resolve(same) 18 | }) 19 | }) 20 | } 21 | async Token(ClientId, Token) { 22 | var Client = await this.db.getClient(ClientId) 23 | var tokenHash = await this.db.getTokenHash(ClientId) 24 | if (!tokenHash || !Client.Settings.TokenLogin) return false 25 | return new Promise((resolve, reject) => { 26 | bcrypt.compare(Token, tokenHash.Token, (err, same) => { 27 | if (!same) { 28 | resolve(false) 29 | return 30 | } 31 | resolve (((new Date() - new Date(tokenHash.Date)) / 1000 < 120)) 32 | }) 33 | }) 34 | } 35 | changePassword(newPassword) { 36 | bcrypt.hash(newPassword, 10, async (err, hash) => { 37 | await this.db.setClientField(ClientId, 'Password', hash) 38 | }) 39 | } 40 | async twoFactor(ClientId, Token) { 41 | var Secret = await this.db.getClientField(ClientId, 'Secret') 42 | if (!Secret) return false 43 | 44 | var result = twoFact.verifyToken(Secret, Token) 45 | 46 | if (result) 47 | return result.delta == 0 48 | else { 49 | return false 50 | } 51 | } 52 | 53 | } 54 | 55 | module.exports = Auth -------------------------------------------------------------------------------- /Webfront/html/audit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Server Overview | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <%- header%> 23 | 48 | 49 | -------------------------------------------------------------------------------- /Webfront/html/authenticator.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= Client.Name %> | Profile Overview 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <%- header%> 25 |
26 |
27 |
Name
28 |
Server
29 |
Clientslot
30 |
IP Address
31 |
Actions
32 |
33 | <%if (clientsToAuth.length > 0) {%> 34 | <% clientsToAuth.forEach(clientToAuth => { %> 35 |
36 |
<%= clientToAuth.Name %>
37 |
<%= clientToAuth.Server.HostnameRaw %>
38 |
<%= clientToAuth.Clientslot %>
39 |
<%= clientToAuth.IPAddress %>
40 |
41 |
Allow
42 |
Kick
43 |
44 | 45 |
46 | <% }) %> 47 | <% } else {%> 48 | No in-game logins to authorize 49 | <% } %> 50 |
51 | 52 | -------------------------------------------------------------------------------- /Webfront/html/chat.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Server Overview | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <%- header %> 23 |
24 | <% var previousDate = new Date() 25 | var i = 0; Messages.forEach(Message => { %> 26 | <% if (i++ < Messages.length - 5) { %> 27 | <% if ( ( previousDate - new Date(Message.Date) ) / 1000 / 60 / 30 >= 1) { 28 | previousDate = new Date(Message.Date) %> 29 |
30 |
31 | <%= Message.Date %> 32 |
33 |
34 | <% } } %> 35 |
36 |
37 | <%= Message.Name %> @ <%=Message.Hostname%> :
38 |
<%= Message.Message %>
39 |
<%= Message.Date %>
40 |
41 | <% }) %>%> 42 |
43 | 44 | -------------------------------------------------------------------------------- /Webfront/html/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Server Overview | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <%- header%> 20 |
21 |
<%= error.Code %>
22 |
<%= error.Description%>
23 |
24 | 25 | -------------------------------------------------------------------------------- /Webfront/html/header.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% if (session.ClientId) { %> 3 | 9 |
10 | Profile 11 | Settings 12 | Authenticator 13 |
Change Password
14 | <% if (Client.PermissionLevel >= Permissions.Levels.ROLE_MODERATOR) {%> 15 |
Remote Console
16 | <% }%> 17 | <% if (Client.PermissionLevel >= Permissions.Levels['ROLE_ADMIN']) {%> 18 | Audit log 19 | <% }%> 20 |
Logout
21 |
22 | <% }%> 23 |
24 | NODE SERVER MANAGER 25 | 26 | 27 | 28 | <% if (config.Info && config.Info.length > 0) {%> 29 | 30 | <% }%> 31 | 32 | <% if (!session.ClientId) {%> 33 | Login 34 | <% } else { %> 35 | <%= Client.Name %> 36 | <% }%> 37 |
38 |
39 |
40 | 41 |
42 |
43 | <% if (Motd && Motd.length > 0) {%> 44 | 49 | <%}%> 50 |
51 | -------------------------------------------------------------------------------- /Webfront/html/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server Overview | Node Server Manager 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | <%- header %> 34 |
35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /Webfront/html/info.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Info | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <%- header %> 22 |
23 |
24 |
25 | 28 |
29 | <% if ( Client && Client.PermissionLevel >= Permissions.Levels[Permissions.Commands.COMMAND_CHANGE_INFO] ) {%> 30 |
31 | Edit 32 |
33 | <%}%> 34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /Webfront/html/links.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Links | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <%- header %> 24 | 37 | 38 | -------------------------------------------------------------------------------- /Webfront/html/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | NODE SERVER MANAGER 12 | Home 13 | Chat 14 | Stats 15 | Login 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /Webfront/html/search.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Server Overview | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <%- header%> 20 | 45 | 46 | -------------------------------------------------------------------------------- /Webfront/html/stats.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multiplayer Stats | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <%- header %> 25 | 45 | 46 | -------------------------------------------------------------------------------- /Webfront/html/zstats.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Zombies Stats | Node Server Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <%- header %> 25 | 45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "main": "NodeServerManager.js", 5 | "dependencies": { 6 | "bcrypt": "^5.0.0", 7 | "body-parser": "^1.19.0", 8 | "btoa": "^1.2.1", 9 | "connect-redis": "^5.0.0", 10 | "cookie-parser": "^1.4.5", 11 | "crypto": "^1.0.1", 12 | "delay": "^4.4.0", 13 | "discord-oauth2": "^2.5.0", 14 | "discord-webhook-node": "^1.1.8", 15 | "discord.js": "^12.2.0", 16 | "discordjs-colors": "^1.2.3", 17 | "dom-parser": "^0.1.6", 18 | "dotenv": "^8.2.0", 19 | "ejs": "^3.1.3", 20 | "express": "^4.17.1", 21 | "express-rate-limit": "^5.1.3", 22 | "express-session": "^1.17.1", 23 | "fs": "^0.0.1-security", 24 | "ip-range-check": "^0.2.0", 25 | "jsdom": "^16.4.0", 26 | "jshtml": "^0.3.0", 27 | "limiting-middleware": "^1.3.2", 28 | "log-timestamp": "^0.3.0", 29 | "mathjs": "^7.3.0", 30 | "md5": "^2.3.0", 31 | "moment": "^2.27.0", 32 | "node-2fa": "^1.1.2", 33 | "node-fetch": "^2.6.1", 34 | "node-machine-id": "^1.1.12", 35 | "package.json": "^2.0.1", 36 | "public-ip": "^4.0.2", 37 | "read-last-lines": "^1.7.2", 38 | "readline": "^1.3.0", 39 | "request": "^2.88.2", 40 | "sequelize": "^6.3.5", 41 | "sqlite3": "^5.0.0", 42 | "stream": "^0.0.2", 43 | "tail": "^2.0.4", 44 | "websocket": "^1.0.31", 45 | "ws": "^7.3.1", 46 | "xmlhttprequest": "^1.8.0" 47 | }, 48 | "scripts": { 49 | "start": "node Lib/NodeServerManager.js" 50 | }, 51 | "keywords": [], 52 | "author": "fed", 53 | "license": "ISC", 54 | "repository": { 55 | "type": "git", 56 | "url": "git+https://github.com/fedddddd/node-server-manager.git" 57 | }, 58 | "bugs": { 59 | "url": "https://github.com/fedddddd/node-server-manager/issues" 60 | }, 61 | "homepage": "https://github.com/fedddddd/node-server-manager#readme", 62 | "description": "Server Manager for Plutonium IW5 Servers", 63 | "pkg": { 64 | "scripts": [ 65 | "Lib/**/*.js", 66 | "Lib/*.js", 67 | "Webfront/*.js", 68 | "Utils/*.js", 69 | "Plugins/*.js" 70 | ] 71 | } 72 | } 73 | --------------------------------------------------------------------------------