* use ES Modules
* Fix some bugs
* add Novice Gather
This commit is contained in:
Absurdon 2023-03-05 17:18:43 +00:00
parent 68cf41c388
commit ddb6c460ff
56 changed files with 1257 additions and 1951 deletions

View file

@ -36,7 +36,7 @@ class SelectPlayerButton extends React.Component {
onClick={this.selectPlayer}
value={this.props.gatherer.id}
className="btn btn-xs btn-primary team-label"> Select
</button>;
</button>;
}
return button;
}
@ -87,7 +87,7 @@ class GatherTeams extends React.Component {
<div className="panel panel-primary panel-light-background team-marines">
<div className="panel-heading">
Marines
</div>
</div>
<GathererList gather={this.props.gather} team="marine" />
</div>
</div>
@ -95,7 +95,7 @@ class GatherTeams extends React.Component {
<div className="panel panel-primary panel-light-background team-aliens">
<div className="panel-heading">
Aliens
</div>
</div>
<GathererList gather={this.props.gather} team="alien" />
</div>
</div>
@ -317,7 +317,7 @@ class CooloffButton extends React.Component {
disabled="true"
className="btn btn-success">
Leaver Cooloff ({this.timeRemaining()})
</button>
</button>
}
}
@ -371,15 +371,15 @@ class GatherActions extends React.Component {
{gather.pickingPattern.map((team, index) => {
if (team === 'alien') {
if (index <= pickIndex) {
return <li className="padding-y-1"><div className="pick-pattern-box alien-box-active"></div></li>
return <li key={index} className="padding-y-1"><div className="pick-pattern-box alien-box-active"></div></li>
} else {
return <li className="padding-y-1"><div className="pick-pattern-box alien-box"></div></li>
return <li key={index} className="padding-y-1"><div className="pick-pattern-box alien-box"></div></li>
}
} else {
if (index <= pickIndex) {
return <li className="padding-y-1"><div className="pick-pattern-box marine-box-active"></div></li>
return <li key={index} className="padding-y-1"><div className="pick-pattern-box marine-box-active"></div></li>
} else {
return <li className="padding-y-1"><div className="pick-pattern-box marine-box"></div></li>
return <li key={index} className="padding-y-1"><div className="pick-pattern-box marine-box"></div></li>
}
}
})}
@ -390,15 +390,15 @@ class GatherActions extends React.Component {
<div>
<div className="text-right">
<ul className="list-inline no-bottom content-center">
<li>
<li key="picking">
{pickPatternIndicator}
</li>
<ul className='list-inline no-bottom'>
<li className='padding-right-0'>
<li key="join" className='padding-right-0'>
<JoinGatherButton gather={gather} thisGatherer={thisGatherer}
user={user} socket={socket} />
</li>
<li className='padding-right-0'>
<li key="regather" className='padding-right-0'>
{regatherButton}
</li>
</ul>
@ -909,8 +909,8 @@ class GathererListItem extends React.Component {
value={gatherer.user.id}
onClick={this.bootGatherer}>
Boot from Gather
</button>&nbsp;
<AssumeUserIdButton socket={socket}
</button>&nbsp;
<AssumeUserIdButton socket={socket}
gatherer={gatherer} currentUser={user} />
</dd>
]
@ -977,7 +977,7 @@ class GathererListItem extends React.Component {
<a href={enslUrl(gatherer)}
className="btn btn-xs btn-primary"
target="_blank">ENSL Profile</a>&nbsp;
<a href={obsUrl(gatherer)}
<a href={obsUrl(gatherer)}
className="btn btn-xs btn-primary"
target="_blank">Observatory Profile</a>
</dd>
@ -1145,7 +1145,7 @@ class GatherVotingResults extends React.Component {
<div className="panel panel-primary">
<div className="panel-heading">
Game Information
</div>
</div>
<div className="panel-body">
<div className="row">
<div className="col-md-4">

View file

@ -27,24 +27,19 @@ class InfoButton extends React.Component {
dropdown = (
<ul className="treeview-menu menu-open" style={{ display: "block" }}>
<li>
<a href="https://github.com/cblanc/sws_gathers" target="_blank">
<a href="https://github.com/ENSL/ensl_gathers" target="_blank">
<i className="fa fa-github">&nbsp;</i>&nbsp;Github
</a>
</li>
<li>
<a href="http://steamcommunity.com/id/nslgathers" target="_blank">
<i className="fa fa-steam-square">&nbsp;</i>&nbsp;Steam Bot
</a>
</a>
</li>
<li>
<a href="https://www.ensl.org/gatherre" target="_blank">
<i className="fa fa-legal">&nbsp;</i>&nbsp;Gather Rules
</a>
</a>
</li>
<li>
<a href="/messages" target="_blank">
<i className="fa fa-comments">&nbsp;</i>&nbsp;Message Archive
</a>
</a>
</li>
</ul>
);

View file

@ -144,10 +144,10 @@ class GatherPage extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.state = this.getInitialState(props);
}
getInitialState = () => {
getInitialState = (props) => {
let updateTitle = true;
let showEventsPanel = true;
@ -175,7 +175,7 @@ class GatherPage extends React.Component {
user: null,
servers: [],
archive: [],
socket: null,
socket: props.socket,
events: [],
updateTitle: updateTitle,
showEventsPanel: showEventsPanel,
@ -491,7 +491,7 @@ class GatherPage extends React.Component {
<ul className="sidebar-menu">
<li className="header">
<span className="badge">{this.state.users.length}</span> Players Online
</li>
</li>
</ul>
<UserMenu users={this.state.users} user={this.state.user}
socket={socket} mountModal={this.mountModal} />

View file

@ -185,7 +185,6 @@ class MusicSelector extends React.Component {
<label>Music</label>
<select
className="form-control"
defaultValue={this.state.music}
onChange={this.setMusic}
value={this.state.music}>
{options}
@ -246,14 +245,14 @@ class SoundPanel extends MenubarMixin(React.Component) {
mutedButton = <li>
<a href="#" onClick={this.unMute}>
{mutedIcon}&nbsp;Muted
</a>
</a>
</li>;
} else {
mutedIcon = <i className="fa fa-volume-up fa-fw"></i>;
mutedButton = <li>
<a href="#" onClick={this.mute}>
{mutedIcon}&nbsp;Unmuted
</a>
</a>
</li>;
}
return (
@ -266,12 +265,12 @@ class SoundPanel extends MenubarMixin(React.Component) {
<li>
<a href='#' onClick={this.play}>
<i className="fa fa-play"></i>&nbsp;Play
</a>
</a>
</li>
<li>
<a href='#' onClick={this.stop}>
<i className="fa fa-stop"></i>&nbsp;Stop
</a>
</a>
</li>
<hr />
<li>

View file

@ -39,7 +39,7 @@ class UserModal extends React.Component {
const currentUser = this.props.currentUser;
const user = this.props.user;
let hiveStats;
if (user.hive.id) {
if (user.hive) {
hiveStats = [
<tr key="stats"><td><strong>Hive Stats</strong></td><td></td></tr>,
<tr key="elo">
@ -86,7 +86,7 @@ class UserModal extends React.Component {
className={"flag flag-" + ((user.country === null) ? "eu" :
user.country.toLowerCase())}
alt={user.country} />&nbsp;
{user.username}
{user.username}
</h4>
</div>
<div className="modal-body">
@ -107,7 +107,7 @@ class UserModal extends React.Component {
<a href={enslUrl(user)}
className="btn btn-xs btn-primary"
target="_blank">ENSL Profile</a>&nbsp;
<a href={obsUrl({ user: user })}
<a href={obsUrl({ user: user })}
className="btn btn-xs btn-primary"
target="_blank">Observatory Profile</a>
</td>
@ -282,7 +282,7 @@ class ProfileModal extends React.Component {
<p className="add-top"><small>
Try to give an accurate representation of your skill to raise
the quality of your gathers
</small></p>
</small></p>
</div>
<hr />
<div className="form-group">
@ -290,12 +290,12 @@ class ProfileModal extends React.Component {
{abilitiesForm}
<p><small>
Specify which lifeforms you'd like to play in the gather
</small></p>
</small></p>
</div>
<hr />
<p className="small">
You will need to rejoin the gather to see your updated profile
</p>
</p>
<div className="form-group">
<button
type="submit"
@ -320,7 +320,7 @@ class CurrentUser extends React.Component {
<li>
<a href="#" data-toggle="modal" data-target="#adminmodal">
<i className="fa fa-magic fa-fw"></i> Administration
</a>
</a>
</li>
)
}

View file

@ -1,49 +0,0 @@
"use strict";
var env = process.env.NODE_ENV || "development";
var test = env === "test";
var fs = require("fs");
var path = require("path");
var baseConfig = require(path.join(__dirname, path.join("environments/" + env.toLowerCase())));
baseConfig.steamBot = {};
baseConfig.discordBot = {};
if (!test) {
if (process.env.PORT) {
baseConfig.port = parseInt(process.env.PORT, 10);
}
if (process.env.MONGOLAB_URI) {
baseConfig.mongo.uri = process.env.MONGOLAB_URI;
}
if (process.env.RAILS_SECRET) {
baseConfig.secret_token = process.env.RAILS_SECRET;
}
if (process.env.GATHER_STEAM_ACCOUNT) {
baseConfig.steam.bot.account_name = process.env.GATHER_STEAM_ACCOUNT;
}
if (process.env.GATHER_STEAM_PASSWORD) {
baseConfig.steam.bot.password = process.env.GATHER_STEAM_PASSWORD;
}
if (process.env.STEAM_API_KEY) {
baseConfig.steam.api_key = process.env.STEAM_API_KEY;
}
if (process.env.GATHER_DISCORD_HOOK_ID) {
baseConfig.discordBot.hook_id = process.env.GATHER_DISCORD_HOOK_ID;
}
if (process.env.GATHER_DISCORD_HOOK_TOKEN) {
baseConfig.discordBot.hook_token = process.env.GATHER_DISCORD_HOOK_TOKEN;
}
}
module.exports = baseConfig;

24
config/config.mjs Normal file
View file

@ -0,0 +1,24 @@
var env = process.env.NODE_ENV || "development";
var test = env === "test";
var config = (await import(`./environments/${env.toLowerCase()}.mjs`)).default;
if (!test) {
if (process.env.PORT) {
config.port = parseInt(process.env.PORT, 10);
}
if (process.env.MONGOLAB_URI) {
config.mongo.uri = process.env.MONGOLAB_URI;
}
if (process.env.RAILS_SECRET) {
config.secret_token = process.env.RAILS_SECRET;
}
if (process.env.STEAM_API_KEY) {
config.steam.api_key = process.env.STEAM_API_KEY;
}
}
export default config;

View file

@ -1,22 +0,0 @@
"use strict";
var config = {
port: 8000,
mongo: {
uri: "mongodb://db/swsgather_development"
},
secret_token: "",
session_store_name: "_ENSL_session_key",
ensl_url: "http://www.ensl.org/",
ensl_rules_url: "http://www.ensl.org/articles/464",
steam: {
bot: {
link: "http://steamcommunity.com/id/nslgathers",
account_name: '',
password: '',
},
api_key: '',
}
};
module.exports = config;

View file

@ -0,0 +1,15 @@
var config = {
port: 8000,
mongo: {
uri: "mongodb://db/swsgather_development"
},
secret_token: "",
session_store_name: "_ENSL_session_key",
ensl_url: "https://www.ensl.org",
ensl_rules_url: "https://www.ensl.org/articles/464",
steam: {
api_key: '',
}
};
export default config;

View file

@ -1,5 +1,3 @@
"use strict";
var config = {
port: 80,
mongo: {
@ -7,16 +5,11 @@ var config = {
},
secret_token: "",
session_store_name: "_ENSL_session_key",
ensl_url: "https://www.ensl.org/",
ensl_url: "https://www.ensl.org",
ensl_rules_url: "https://www.ensl.org/articles/464",
steam: {
bot: {
link: "http://steamcommunity.com/id/nslgathers",
account_name: '',
password: '',
},
api_key: '',
}
};
module.exports = config;
export default config;

View file

@ -1,5 +1,3 @@
"use strict";
var config = {
port: 80,
mongo: {
@ -7,16 +5,11 @@ var config = {
},
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://staging.ensl.org/",
ensl_url: "http://staging.ensl.org",
ensl_rules_url: "http://www.ensl.org/articles/464",
steam: {
bot: {
link: "http://steamcommunity.com/id/nslgathers",
account_name: '',
password: '',
},
api_key: '',
}
};
module.exports = config;
export default config;

View file

@ -1,5 +1,3 @@
"use strict";
var config = {
port: 9000,
mongo: {
@ -8,15 +6,10 @@ var config = {
secret_token: "SUPERSECRETFOO",
session_store_name: "_ENSL_session_key_staging",
ensl_rules_url: "http://www.ensl.org/articles/464",
ensl_url: "http://staging.ensl.org/",
ensl_url: "http://staging.ensl.org",
steam: {
bot: {
link: "http://steamcommunity.com/id/nslgathers",
account_name: '',
password: '',
},
api_key: '',
}
};
module.exports = config;
export default config;

View file

@ -1,54 +0,0 @@
"use strict";
var fs = require("fs");
var path = require("path");
var morgan = require("morgan");
var express = require("express");
var winston = require("winston");
var config = require("./config.js");
var favicon = require("serve-favicon");
var exphbs = require("express-handlebars").engine;
var cookieParser = require("cookie-parser");
var env = process.env.NODE_ENV || "development";
var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json")));
module.exports = app => {
app.use((req, res, next) => {
res.setHeader('X-GNU', 'Michael J Blanchard');
next();
});
// Enforce HTTPS in production
if (env === 'production') {
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=2592000; includeSubdomains'); // Enforce usage of HTTPS; max-age = 30 days
next();
});
}
app.use(express.static(path.join(__dirname, '../public')));
app.use(cookieParser());
app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
// // Use winston on production
// var log;
// if (env !== 'development') {
// log = {
// stream: {
// write: (message, encoding) => {
// winston.info(message);
// }
// }
// };
// } else {
// log = 'dev';
// }
// if (env !== 'test') app.use(morgan(log));
var hbs = exphbs({
defaultLayout: 'main',
extname: '.hbs'
});
app.engine('.hbs', hbs);
app.set('view engine', '.hbs');
};

32
config/express.mjs Normal file
View file

@ -0,0 +1,32 @@
import { resolve } from "path";
import express from "express"
import favicon from "serve-favicon"
import { engine as exphbs } from "express-handlebars";
import cookieParser from "cookie-parser";
var env = process.env.NODE_ENV || "development";
export default function configureExpress(app) {
app.use((req, res, next) => {
res.setHeader('X-GNU', 'Michael J Blanchard');
next();
});
// Enforce HTTPS in production
if (env === 'production') {
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=2592000; includeSubdomains'); // Enforce usage of HTTPS; max-age = 30 days
next();
});
}
app.use(express.static(resolve('public')));
app.use(cookieParser());
app.use(favicon(resolve('public/favicon.ico')));
var hbs = exphbs({
defaultLayout: 'main',
extname: '.hbs'
});
app.engine('.hbs', hbs);
app.set('view engine', '.hbs');
};

View file

@ -1,14 +1,12 @@
"use strict";
import winston from "winston";
import config from "./config.mjs";
import GatherPool from "../lib/gather/gather_pool.mjs";
import mongoose from "mongoose";
import cors from "cors"
const path = require("path");
const winston = require("winston");
const config = require("./config.js");
const GatherPool = require("../lib/gather/gather_pool");
const mongoose = require("mongoose");
const Message = mongoose.model("Message");
const cors = require("cors");
module.exports = app => {
export default function configureRoutes(app) {
app.use(cors());
app.get("/", (request, response, next) => {

View file

@ -1,18 +1,16 @@
"use strict";
import winston from "winston";
import User from "../lib/user/user.mjs";
import config from "./config.mjs";
import EnslClient from "../lib/ensl/client.mjs";
import chatController from "../lib/chat/controller.mjs"
import gatherController from "../lib/gather/controller.mjs"
import userController from "../lib/user/controller.mjs";
import eventController from "../lib/event/controller.mjs";
import usersHelper from "../lib/user/helper.mjs";
var winston = require("winston");
var User = require("../lib/user/user");
var config = require("./config");
var EnslClient = require("../lib/ensl/client");
var chatController = require("../lib/chat/controller");
var gatherController = require("../lib/gather/controller");
var userController = require("../lib/user/controller");
var eventController = require("../lib/event/controller");
var usersHelper = require("../lib/user/helper");
var env = process.env.NODE_ENV || "development";
var parseCookies = EnslClient.parseCookies;
const parseCookies = EnslClient.parseCookies;
var assignRandomUser = (socket, next) => {
const assignRandomUser = (socket, next) => {
usersHelper.getRandomUser(function (error, user) {
if (error) {
winston.error(error);
@ -23,7 +21,7 @@ var assignRandomUser = (socket, next) => {
});
};
var assignFixedUser = (socket, next, userId) => {
const assignFixedUser = (socket, next, userId) => {
usersHelper.getFixedUser(userId, function (error, user) {
if (error) {
winston.error(error);
@ -34,17 +32,17 @@ var assignFixedUser = (socket, next, userId) => {
});
};
var handleFailedAuth = (socket, next) => {
const handleFailedAuth = (socket, next) => {
if (process.env.RANDOM_USER) {
return assignRandomUser(socket, next);
} else if (process.env.FIXED_USER) {
return assignFixedUser(socket, next, process.env.FIXED_USER);
} else {
} else if (process.env.FIXED_USER) {
return assignFixedUser(socket, next, process.env.FIXED_USER);
} else {
return next(new Error("Authentication Failed"));
}
};
module.exports = io => {
export default function configureSockerIO(io) {
var rootNamespace = io.of('/')
// Authentication
@ -58,7 +56,7 @@ module.exports = io => {
let session = cookies[config.session_store_name];
if (!session) {
return handleFailedAuth(socket, next);
return handleFailedAuth(socket, next);
}
EnslClient.decodeSession(session, function (error, userId) {

View file

@ -1,9 +1,6 @@
"use strict";
var path = require("path");
var winston = require("winston");
var mongoose = require("mongoose");
var config = require(path.join(__dirname, "../config/config.js"));
import winston from "winston"
import mongoose from "mongoose";
import config from "../config/config.mjs";
mongoose.set('strictQuery', true);
@ -25,11 +22,9 @@ mongoose.connection.on("reconnectFailed", () => winston.error("MongoDB: Reconnec
mongoose.connection.on("reconnected", () => winston.info("MongoDB: Connection established"));
// Load models
require(path.join(__dirname, "/models/event"));
require(path.join(__dirname, "/models/message"));
require(path.join(__dirname, "/models/session"));
require(path.join(__dirname, "/models/profile"));
require(path.join(__dirname, "/models/archivedGather"));
module.exports = mongoose;
// Load Models
import "./models/event.mjs";
import "./models/message.mjs";
import "./models/session.mjs";
import "./models/profile.mjs";
import "./models/archivedGather.mjs";

View file

@ -1,11 +1,8 @@
"use strict";
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
import { Schema, model } from "mongoose";
var archivedGatherSchema = new Schema({
createdAt: { type: Date, default: Date.now },
gather: { type: Schema.Types.Mixed, required: true }
gather: { type: Schema.Types.Mixed, required: true }
});
archivedGatherSchema.index({ createdAt: -1 });
@ -14,7 +11,7 @@ archivedGatherSchema.static({
recent: function (callback) {
this
.where({})
.sort({createdAt: -1})
.sort({ createdAt: -1 })
.limit(5)
.exec(callback);
},
@ -28,4 +25,4 @@ archivedGatherSchema.static({
}
});
module.exports = mongoose.model("ArchivedGather", archivedGatherSchema);
model("ArchivedGather", archivedGatherSchema);

View file

@ -1,10 +1,7 @@
"use strict";
import { Schema, model } from "mongoose";
import winston from "winston";
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const path = require("path");
const pubsub = require(path.join(__dirname, "../../lib/event/pubsub.js"));
const winston = require("winston");
import pubsub from "../../lib/event/pubsub.mjs"
const eventSchema = new Schema({
eventType: { type: String, required: true },
@ -41,7 +38,7 @@ eventSchema.statics.leaver = function (user) {
eventSchema.statics.playerSelected = function (user, data, gather) {
winston.info("Selection Data", JSON.stringify(user), JSON.stringify(data));
const gatherer = gather.getGatherer({id: data.player});
const gatherer = gather.getGatherer({ id: data.player });
const description = `${user.username} selected ${gatherer.user.username} into ${gatherer.team} team`;
this.create({
eventType: "gather:select",
@ -99,4 +96,4 @@ eventSchema.statics.serverVote = function (user, data, gather, servers) {
}
};
module.exports = mongoose.model('Event', eventSchema);
model('Event', eventSchema);

View file

@ -1,7 +1,4 @@
"use strict";
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
import { Schema, model } from "mongoose";
var messageSchema = new Schema({
author: {
@ -27,7 +24,7 @@ messageSchema.methods.toJson = function () {
};
messageSchema.statics.list = function (options, callback) {
let query = this.find({deleted: false})
let query = this.find({ deleted: false })
if (options.before) {
query.where({
@ -37,7 +34,7 @@ messageSchema.statics.list = function (options, callback) {
});
}
return query.sort({createdAt: -1})
return query.sort({ createdAt: -1 })
.limit(30)
.exec((error, messages) => {
if (error) return callback(error);
@ -45,4 +42,4 @@ messageSchema.statics.list = function (options, callback) {
});
};
module.exports = mongoose.model('Message', messageSchema);
model('Message', messageSchema);

View file

@ -1,7 +1,4 @@
"use strict";
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
import { model, Schema } from "mongoose";
var profileSchema = new Schema({
userId: { type: Number, required: true },
@ -25,10 +22,10 @@ profileSchema.static({
findOrCreate: function (user, callback) {
if (!user || typeof user.id !== 'number') return callback(new Error("Invalid user"));
let self = this;
self.findOne({userId: user.id}, (error, profile) => {
self.findOne({ userId: user.id }, (error, profile) => {
if (error) return callback(error);
if (profile) return callback(null, profile);
self.create({userId: user.id}, (error, result) => {
self.create({ userId: user.id }, (error, result) => {
if (error) return callback(error);
return callback(null, result);
});
@ -45,4 +42,4 @@ profileSchema.method({
}
});
module.exports = mongoose.model("Profile", profileSchema);
model("Profile", profileSchema);

View file

@ -1,18 +0,0 @@
"use strict";
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var crypto = require('crypto');
var keyGenerator = () => {
return crypto.randomBytes(20).toString('hex');
};
var sessionSchema = new Schema({
userId: { type: Number, required: true },
key: { type: String, required: true, default: keyGenerator }
});
sessionSchema.index({ userId: 1 });
module.exports = mongoose.model("Session", sessionSchema);

15
db/models/session.mjs Normal file
View file

@ -0,0 +1,15 @@
import { model, Schema } from "mongoose";
import { randomBytes } from "crypto";
var keyGenerator = () => {
return randomBytes(20).toString('hex');
};
var sessionSchema = new Schema({
userId: { type: Number, required: true },
key: { type: String, required: true, default: keyGenerator }
});
sessionSchema.index({ userId: 1 });
model("Session", sessionSchema);

View file

@ -1,43 +0,0 @@
"use strict";
const env = process.env.NODE_ENV || "development";
const fs = require("fs");
const path = require("path");
const express = require("express");
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const config = require(path.join(__dirname, "config/config.js"));
if (env === "production") require("newrelic");
// Load Models
require(path.join(__dirname, "db/index"));
// Initialise Steam Bot
//if (env !== "test") {
// require(path.join(__dirname, "lib/steam/bot"))(config.steamBot);
//}
//Initialise Discord Bot
// if (env !== "test") {
// require(path.join(__dirname, "lib/discord/bot"))(config.discordBot);
// }
// Configure express
require(path.join(__dirname, "config/express"))(app);
// Add routes
require(path.join(__dirname, "config/routes"))(app);
// Configure socket.io server
require(path.join(__dirname, "config/socketio"))(io);
server.listen(config.port);
console.log("Listening on port", config.port);
module.exports = {
app: app,
server: server,
io: io
};

34
index.mjs Normal file
View file

@ -0,0 +1,34 @@
"use strict";
import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
import config from "./config/config.mjs";
import "./db/index.mjs";
import configureExpress from "./config/express.mjs";
import addRoutes from "./config/routes.mjs"
import configureSocketIO from "./config/socketio.mjs";
const env = process.env.NODE_ENV || "development";
const app = express();
const server = createServer(app);
const io = new Server(server);
// Configure express
configureExpress(app);
// Add routes
addRoutes(app);
// Configure socket.io server
configureSocketIO(io);
server.listen(config.port);
console.log("Listening on port", config.port);
export default {
app: app,
server: server,
io: io
};

View file

@ -1,5 +1,3 @@
"use strict";
/*
* Chatroom Controller
*
@ -14,13 +12,13 @@
*
*/
var mongoose = require("mongoose");
var Message = mongoose.model("Message");
var winston = require("winston");
import { model } from "mongoose";
import winston from "winston";
const Message = model("Message");
module.exports = namespace => {
export default namespace => {
var broadcastUpdate = message => {
namespace.emit("message:append", { messages: [message.toJson()]});
namespace.emit("message:append", { messages: [message.toJson()] });
};
var refreshMessages = socket => {
@ -60,7 +58,7 @@ module.exports = namespace => {
username: socket._user.username,
avatar: socket._user.avatar
},
content: data.content.slice(0,255)
content: data.content.slice(0, 255)
}, (error, newMessage) => {
if (error) {
winston.error("Unable to store message. Error:", error);
@ -75,7 +73,7 @@ module.exports = namespace => {
var id = data.id;
if (id === undefined || !socket._user.isChatAdmin()) return;
Message.update({_id: id}, {deleted: true}, (error, message) => {
Message.deleteOne({ _id: id }, (error, message) => {
if (error) {
winston.error("An error occurred when trying to delete message:", error);
return;

View file

@ -1,27 +0,0 @@
"use strict"
// Import the discord.js module
const Discord = require('discord.js');
const winston = require('winston');
function DiscordBot(config) {
this.hook = new Discord.WebhookClient(config.hook_id,config.hook_token);
this.spamProtection = {
fillStatus: null,
};
}
DiscordBot.prototype.notifyChannel = function(message) {
this.hook.send(message);
};
var bot;
module.exports = (config) => {
if (bot) return bot;
if (!config) throw new Error("No credentials provided for Discord Gather Bot");
bot = new DiscordBot(config);
bot.notifyChannel('Gather restarted');
return bot;
};

View file

@ -1,123 +0,0 @@
"use strict";
var path = require("path");
var crypto = require("crypto");
var request = require("request");
var logger = require("winston");
var querystring = require('querystring');
var config = require(path.join(__dirname, "../../config/config"));
const SECRET_TOKEN = config.secret_token;
const Marshal = require('marshal');
const MAP_CATEGORY = 45;
const SERVER_CATEGORY = 45;
function EnslClient(options) {
if (!(this instanceof EnslClient)) {
return new EnslClient(options);
}
this.baseUrl = config.ensl_url;
}
EnslClient.prototype.getUserById = function (options, callback) {
var id = options.id;
var url = this.baseUrl + "api/v1/users/" + id;
return request({
url: url,
json: true
}, callback);
};
EnslClient.prototype.getTeamById = function (options, callback) {
const id = options.id;
const url = `${this.baseUrl}api/v1/teams/${id}`;
return request({
url: url,
json: true
}, callback);
};
EnslClient.prototype.getServers = function (callback) {
const url = this.baseUrl + "api/v1/servers";
return request({
url: url,
json: true
}, (error, response, data) => {
if (error) return callback(error);
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
return callback(null, {
servers: data.servers.filter(function (server) {
return server.category_id === SERVER_CATEGORY;
})
});
});
};
EnslClient.prototype.getMaps = function (callback) {
const url = this.baseUrl + "api/v1/maps";
return request({
url: url,
json: true
}, (error, response, data) => {
if (error) return callback(error);
if (response.statusCode !== 200) return callback(new Error("Non-200 status code received"));
return callback(null, {
maps: data.maps.filter(map => {
return map.category_id === MAP_CATEGORY;
})
});
});
};
EnslClient.prototype.getFullAvatarUri = function (url) {
return this.baseUrl + url.replace(/^\//, "");
};
EnslClient.parseCookies = function (socket) {
let cookieString = socket.request.headers.cookie;
if (typeof cookieString !== 'string') return null;
let cookies = socket.request.headers.cookie.split(";")
.map(cookie => cookie.trim())
.reduce((acc, cookie) => {
let values = cookie.split("=");
let attr = values[0];
let val = values[1];
if (attr && val) acc[attr] = val;
return acc;
}, {})
return cookies;
};
EnslClient.decodeSession = function (sessionCookie, callback) {
if (typeof sessionCookie !== 'string') {
return callback(new Error("Invalid cookie"), null);
}
var session = sessionCookie.split("--");
if (session.length !== 2) {
return callback(new Error("Invalid cookie: No signature provided"), null);
}
// Separate text and signature
var text = querystring.unescape(session[0]);
var signature = session[1];
// Verify signature
if (crypto.createHmac("sha1", SECRET_TOKEN).update(text).digest('hex') !== signature) {
return callback(new Error("Invalid cookie signature"), null);
}
let railsSession = new Marshal(text, 'base64').toJSON();
if (isNaN(railsSession.user)) {
return callback(new Error("Invalid cookie: User ID not found"), null);
} else {
return callback(null, railsSession.user);
}
};
module.exports = EnslClient;

118
lib/ensl/client.mjs Normal file
View file

@ -0,0 +1,118 @@
import { createHmac } from "crypto";
import request from "request";
import { unescape } from "querystring";
import config from "../../config/config.mjs";
import Marshal from "marshal";
const SECRET_TOKEN = config.secret_token;
const MAP_CATEGORY = 45;
const SERVER_CATEGORY = 45;
class EnslClient {
constructor(options) {
if (!(this instanceof EnslClient)) {
return new EnslClient(options);
}
this.baseUrl = config.ensl_url;
}
static parseCookies(socket) {
let cookieString = socket.request.headers.cookie;
if (typeof cookieString !== 'string')
return null;
let cookies = socket.request.headers.cookie.split(";")
.map(cookie => cookie.trim())
.reduce((acc, cookie) => {
let values = cookie.split("=");
let attr = values[0];
let val = values[1];
if (attr && val)
acc[attr] = val;
return acc;
}, {});
return cookies;
}
static decodeSession(sessionCookie, callback) {
if (typeof sessionCookie !== 'string') {
return callback(new Error("Invalid cookie"), null);
}
var session = sessionCookie.split("--");
if (session.length !== 2) {
return callback(new Error("Invalid cookie: No signature provided"), null);
}
// Separate text and signature
var text = unescape(session[0]);
var signature = session[1];
// Verify signature
if (createHmac("sha1", SECRET_TOKEN).update(text).digest('hex') !== signature) {
return callback(new Error("Invalid cookie signature"), null);
}
let railsSession = new Marshal(text, 'base64').toJSON();
if (isNaN(railsSession.user)) {
return callback(new Error("Invalid cookie: User ID not found"), null);
} else {
return callback(null, railsSession.user);
}
}
getUserById(options, callback) {
var id = options.id;
var url = this.baseUrl + "/api/v1/users/" + id;
return request({
url: url,
json: true
}, callback);
}
getTeamById(options, callback) {
const id = options.id;
const url = `${this.baseUrl}/api/v1/teams/${id}`;
return request({
url: url,
json: true
}, callback);
}
getServers(callback) {
const url = this.baseUrl + "/api/v1/servers";
return request({
url: url,
json: true
}, (error, response, data) => {
if (error)
return callback(error);
if (response.statusCode !== 200)
return callback(new Error("Non-200 status code received"));
return callback(null, {
servers: data.servers.filter(function (server) {
return server.category_id === SERVER_CATEGORY;
})
});
});
}
getMaps(callback) {
const url = this.baseUrl + "/api/v1/maps";
return request({
url: url,
json: true
}, (error, response, data) => {
if (error)
return callback(error);
if (response.statusCode !== 200)
return callback(new Error("Non-200 status code received"));
return callback(null, {
maps: data.maps.filter(map => {
return map.category_id === MAP_CATEGORY;
})
});
});
}
getFullAvatarUri(url) {
return this.baseUrl + url;
}
}
export default EnslClient;

View file

@ -6,10 +6,9 @@
*
*/
const winston = require("winston");
const pubsub = require("./pubsub.js");
import pubsub from "./pubsub.mjs";
module.exports = namespace => {
export default namespace => {
pubsub.on("newEvent", event => {
if (!event.public) return;
namespace.emit("event:append", {

View file

@ -1,3 +0,0 @@
const EventEmitter = require("events").EventEmitter;
module.exports = new EventEmitter();

5
lib/event/pubsub.mjs Normal file
View file

@ -0,0 +1,5 @@
import { EventEmitter } from "events";
const pubsub = new EventEmitter()
export default pubsub;

View file

@ -17,57 +17,61 @@
*
*/
const Map = require("./map");
const Server = require("./server");
const mongoose = require("mongoose");
const GatherPool = require("./gather_pool");
import _ from "lodash";
import winston from "winston";
import Map from "./map.mjs";
import Server from "./server.mjs";
import mongoose from "mongoose";
import GatherPool from "./gather_pool.mjs";
const ArchivedGather = mongoose.model("ArchivedGather");
const Event = mongoose.model("Event");
const _ = require("lodash");
const winston = require("winston");
const kickTimeout = 600 // sec
module.exports = function (namespace) {
export default function (namespace) {
const emitGather = (socket, gather) => {
socket.emit("gather:refresh", {
gather: gather ? gather.toJson() : null,
type: gather ? gather.type : null,
maps: Map.list,
});
};
const removeGatherer = (gather, user) => {
let gatherLeaver = gather.getGatherer(user);
});
};
if (!gatherLeaver) {
winston.info(`${user.username} ${user.id} attempted to leave ${gather.type} gather, but was not in gather.`);
return;
}
const removeGatherer = (gather, user) => {
let gatherLeaver = gather.getGatherer(user);
if (gather.can("removeGatherer")) {
gather.removeGatherer(user);
}
if (user.cooldown) gather.applyCooldown(user);
if (!gatherLeaver) {
winston.warn(`${user.username} ${user.id} attempted to leave ${gather.type} gather, but was not in gather.`);
return;
}
Event.leaver(gatherLeaver.user);
refreshGather(gather.type);
}
if (gather.can("removeGatherer")) {
gather.removeGatherer(user);
}
if (user.cooldown) gather.applyCooldown(user);
const removeAfkGathererTimer = (gather, user) => {
const interval = setInterval(() => {
console.log(gather.current)
if (gather.getGatherer(user).user.online || gather.current !== "gathering") {
clearInterval(interval);
}
const now = Date.now();
const awayDuration = (now / 1000) - (user.lastSeen / 1000);
if (awayDuration >= kickTimeout) {
console.log("kicked from being afk : ");
removeGatherer(gather, user);
clearInterval(interval);
}
}, 1000)
}
Event.leaver(gatherLeaver.user);
refreshGather(gather.type);
}
const removeAfkGathererTimer = (gather, user) => {
const interval = setInterval(() => {
console.log(gather.current)
if (gather.getGatherer(user).user.online || gather.current !== "gathering") {
clearInterval(interval);
}
const now = Date.now();
const awayDuration = (now / 1000) - (user.lastSeen / 1000);
if (awayDuration >= kickTimeout) {
console.log("kicked from being afk : ");
removeGatherer(gather, user);
clearInterval(interval);
}
}, 1000)
}
const refreshArchive = () => {
ArchivedGather.recent((error, recentGathers) => {
@ -87,7 +91,7 @@ module.exports = function (namespace) {
} else {
const refresh = gatherRefreshers[type];
if (refresh) refresh();
}
}
}
const updateUserAsOnlineInGather = (userId) => {
@ -105,15 +109,15 @@ module.exports = function (namespace) {
for (var gatherer of gatherManager.current.gatherers) {
if (gatherer.user.id == userId && gatherer.user.online) {
gatherer.user.online = false;
gatherer.user.lastSeen = Date.now();
removeAfkGathererTimer(gatherManager.current, { ...gatherer.user, cooldown: false });
gatherer.user.lastSeen = Date.now();
removeAfkGathererTimer(gatherManager.current, { ...gatherer.user, cooldown: false });
refreshGather(type);
}
}
});
};
};
const gatherRefreshers = {}; // Stores debounced procedures to refresh gathers
@ -152,7 +156,7 @@ module.exports = function (namespace) {
// ***** Generate Test Users *****
if (process.env.POPULATE_GATHER) {
let helper = require("./helper");
let helper = require("./helper.mjs");
GatherPool.forEach(gatherManager => {
helper.createTestUsers({
@ -160,7 +164,7 @@ module.exports = function (namespace) {
}, refreshGather());
});
}
namespace.on("connection", function (socket) {
const connectedUserId = socket._user && socket._user.id;
@ -173,7 +177,7 @@ module.exports = function (namespace) {
maps: Map.list
});
});
socket.on("disconnect", () => {
const onlineIds = Object.values(namespace.sockets).map(s => s._user.id);
@ -216,7 +220,7 @@ module.exports = function (namespace) {
emitGather(socket, gatherManager.current)
});
socket.on("gather:leave", function (data) {
if (!data) data = {};
@ -226,7 +230,7 @@ module.exports = function (namespace) {
if (data.gatherer) {
// Remove gatherer defined by ID (admins only)
if (!socket._user.isGatherAdmin()) return;
removeGatherer(gather, { id: data.gatherer, cooldown: true });
let adminName = socket._user.username;
let playerId = data.gatherer;
@ -253,7 +257,7 @@ module.exports = function (namespace) {
}
// Cancel if id belongs to a leader
let selectedPlayer = gather.getGatherer({id: playerId});
let selectedPlayer = gather.getGatherer({ id: playerId });
if (selectedPlayer === null || selectedPlayer.leader) {
return null;

View file

@ -1,471 +0,0 @@
"use strict";
/*
* Implements Gather Model
*
* Gather States
* - Gathering
* - Election (Electing leaders)
* - Selection (Selecting teams)
* - Done
*
*/
const Gatherer = require("./gatherer");
const StateMachine = require("javascript-state-machine");
const Server = require("./server");
// const discordBot = require("../discord/bot")();
function Gather (options) {
if (options === undefined) options = {};
if (!(this instanceof Gather)) {
return new Gather(options);
}
this.gatherers = [];
let noop = () => {};
this.onDone = (typeof options.onDone === 'function') ?
options.onDone : noop;
this.onEvent = (typeof options.onEvent === 'function') ?
options.onEvent : noop;
this.done = {
time: null
};
this.teamSize = options.teamSize || 6;
// Store cooldown times for gather leaves
this.cooldown = {};
this.COOLDOWN_TIME = 60 * 3;// 3 Minutes
this.REGATHER_THRESHOLD = this.teamSize + 2;
this.type = options.type || "classic";
this.icon = options.icon || "gather icon";
this.name = options.name || "Classic Gather";
this.description = options.description || "No player requirements";
this.election = {
INTERVAL: 60000, // 1 Minute
startTime: null,
timer: null
};
if (typeof options.membershipTest === 'function') {
this.membershipTest = options.membershipTest.bind(this);
}
if (typeof options.serverMembershipTest === 'function') {
this.serverMembershipTest = options.serverMembershipTest.bind(this);
}
this.initState();
}
StateMachine.create({
target: Gather.prototype,
events: [
{ name: "initState", from: "none", to: "gathering" },
{ name: "addGatherer", from: "gathering", to: "election" },
{ name: "selectLeader", from: "election", to: "selection" },
{ name: "electionTimeout", from: "election", to: "selection" },
{ name: "confirmSelection", from: "selection", to: "done" },
{
name: "removeGatherer",
from: ["gathering", "election", "selection"],
to: "gathering"
},
{
name: "regather",
from: ["gathering", "election", "selection"],
to: "gathering"
}
],
callbacks: {
// Callbacks for events
onafterevent: function () {
this.onEvent.apply(this, [].slice.call(arguments));
},
// Gathering State
onbeforeaddGatherer: function (event, from, to, user) {
if (this.needsToCoolOff(user)) return false;
if (this.failsTest(user)) return false;
this.addUser(user);
if (!this.lobbyFull()) {
// if(this.gatherers.length > this.teamSize &&
// (null === discordBot.spamProtection.fillStatus ||
// ((new Date()).getTime() - discordBot.spamProtection.fillStatus.getTime())/1000 > 180)) {
// discordBot.notifyChannel("Join the gather at https://gathers.ensl.org | " + this.gatherers.length + " players are already waiting!");
// discordBot.spamProtection.fillStatus = new Date();
// }
return false;
}
},
// Election State
onbeforeselectLeader: function (event, from, to, voter, candidate) {
this.voteForLeader(voter, candidate);
if (!this.leaderVotesFull()) return false;
},
onenterelection: function () {
// discordBot.notifyChannel("Gather is starting! Pick your captains at https://gathers.ensl.org");
// Setup timer for elections
this.startElectionCountdown();
},
onleaveelection: function () {
this.cancelElectionCountdown();
},
// Selection State
onenterselection: function () {
// Remove all leaders and teams
this.gatherers.forEach(gatherer => {
gatherer.leader = false;
gatherer.team = "lobby";
});
// Assign leaders based on vote
// 1st place alien comm
// 2nd place marine comm
let voteCount = {};
this.gatherers.forEach(gatherer => { voteCount[gatherer.id] = 0 });
this.leaderVotes().forEach(candidateId => { voteCount[candidateId]++ });
let rank = [];
for (let candidate in voteCount) {
rank.push({ candidate: candidate, count: voteCount[candidate] });
}
rank.sort((a, b) => {
return a.count - b.count;
});
this.assignAlienLeader(parseInt(rank.pop().candidate, 0));
this.assignMarineLeader(parseInt(rank.pop().candidate, 0));
},
onleaveselection: function (event, from, to, voter, candidate) {
if (event === "removeGatherer" || event === "regather") {
this.gatherers.forEach(gatherer => {
gatherer.team = "lobby";
});
}
},
onbeforeconfirmSelection: function (event, from, to, leader) {
return (this.aliens().length === this.teamSize
&& this.marines().length === this.teamSize);
},
// Remove gatherer event
onbeforeremoveGatherer: function (event, from, to, user) {
// Cancel transition if no gatherers have been removed
let userCount = this.gatherers.length;
this.removeUser(user);
let userRemoved = userCount > this.gatherers.length;
if (userRemoved && from !== 'gathering') this.applyCooldown(user);
return userRemoved;
},
// Set gatherer vote & if threshold met, reset gather
onbeforeregather: function (event, from, to, user, vote) {
let self = this;
self.modifyGatherer(user, (gatherer) => gatherer.voteRegather(vote));
if (self.regatherVotes() >= self.REGATHER_THRESHOLD) {
self.resetState();
// discordBot.notifyChannel("@here Gather was reset! Rejoin to play!");
return true;
} else {
return false;
}
},
// On enter done
onenterdone: function () {
// discordBot.notifyChannel("Picking finished! Join the server!");
this.done.time = new Date();
this.onDone.apply(this, [].slice.call(arguments));
}
}
});
Gather.prototype.lobbyFull = function () {
return this.gatherers.length === (this.teamSize * 2);
};
Gather.prototype.leaderVotesFull = function () {
return this.leaderVotes().length === (this.teamSize * 2);
};
Gather.prototype.resetState = function () {
this.gatherers = [];
this.cancelElectionCountdown();
return this;
};
Gather.prototype.alienLeader = function () {
return this.gatherers.reduce((acc, gatherer) => {
if (gatherer.team === "alien" && gatherer.leader) acc.push(gatherer);
return acc;
}, []).pop();
};
Gather.prototype.marineLeader = function () {
return this.gatherers.reduce((acc, gatherer) => {
if (gatherer.team === "marine" && gatherer.leader) acc.push(gatherer);
return acc;
}, []).pop();
};
Gather.prototype.assignMarineLeader = function (id) {
this.modifyGatherer({id: id}, gatherer => {
gatherer.leader = true;
gatherer.team = "marine";
});
};
Gather.prototype.assignAlienLeader = function (id) {
this.modifyGatherer({id: id}, gatherer => {
gatherer.leader = true;
gatherer.team = "alien";
});
};
Gather.prototype.containsUser = function (user) {
return this.gatherers.some(gatherer => {
return gatherer.id === user.id;
});
};
Gather.prototype.addUser = function (user) {
if (this.containsUser(user)) return null;
let gatherer = new Gatherer(user);
this.gatherers.push(gatherer);
return gatherer;
};
Gather.prototype.removeUser = function (user) {
this.gatherers = this.gatherers.filter(gatherer => user.id !== gatherer.id);
};
Gather.prototype.modifyGatherer = function (user, callback){
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.forEach(callback);
};
// 08/04/20 : 1-1-1-1-1
// 08/11/20 : 1-2-1-1-1
// 09/21/20 : 1-1-1-2-1
Gather.prototype.getPickingPattern = function () {
const pickingPattern = [
"marine",
"alien",
"marine",
"alien",
"alien",
"marine",
"alien",
"marine",
"alien",
"marine",
"alien",
"marine",
];
return pickingPattern;
}
// Determines picking order of teams
// Marine pick first
Gather.prototype.pickingTurnIndex = function () {
if (this.current !== 'selection') return null;
const captainCount = 2;
const alienCount = this.aliens().length;
const marineCount = this.marines().length;
const alreadyPickedCount = (marineCount + alienCount) - captainCount;
const pickingPattern = this.getPickingPattern();
const pickingTurn = alreadyPickedCount % pickingPattern.length;
// prevent any team from growing beyond the team size limit
if (marineCount >= this.teamSize) {
return "alien";
} else if (alienCount >= this.teamSize) {
return "marine";
}
return pickingTurn;
};
// Moves player to marine
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
Gather.prototype.moveToMarine = function (user, mover) {
if (this.marines().length >= this.teamSize) return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "marine" ||
!leader.leader ||
this.getPickingPattern()[this.pickingTurnIndex()] !== "marine") return;
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby") return;
}
}
this.modifyGatherer(user, gatherer => gatherer.team = "marine");
};
// Moves player to alien
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
Gather.prototype.moveToAlien = function (user, mover) {
if (this.aliens().length >= this.teamSize) return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "alien" ||
!leader.leader ||
this.getPickingPattern()[this.pickingTurnIndex()] !== "alien") return;
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby") return;
}
}
return this.modifyGatherer(user, gatherer => gatherer.team = "alien");
};
Gather.prototype.moveToLobby = function (user) {
this.modifyGatherer(user, gatherer => gatherer.team = "lobby");
};
Gather.prototype.retrieveGroup = function (team) {
return this.gatherers.filter(gatherer => gatherer.team === team);
};
Gather.prototype.lobby = function () {
return this.retrieveGroup("lobby");
};
Gather.prototype.aliens = function () {
return this.retrieveGroup("alien");
};
Gather.prototype.marines = function () {
return this.retrieveGroup("marine");
};
Gather.prototype.electionStartTime = function () {
return (this.election.startTime === null) ?
null : this.election.startTime.toISOString();
};
Gather.prototype.toJson = function () {
return {
name: this.name,
icon: this.icon,
description: this.description,
type: this.type,
gatherers: this.gatherers,
servers: this.getServers(),
state: this.current,
pickingTurn: this.getPickingPattern()[this.pickingTurnIndex()],
pickingTurnIndex: this.pickingTurnIndex(),
pickingPattern: this.getPickingPattern().splice(0, this.getPickingPattern().length-2), //why is the picking pattern length 12 anyway ? 12 - 2 captains
election: {
startTime: this.electionStartTime(),
interval: this.election.INTERVAL
},
teamSize: this.teamSize,
done: {
time: this.done.time
},
cooldown: this.cooldown
}
};
Gather.prototype.toggleMapVote = function (voter, mapId) {
this.modifyGatherer(voter, gatherer => gatherer.toggleMapVote(mapId));
};
Gather.prototype.toggleServerVote = function (voter, serverId) {
this.modifyGatherer(voter, gatherer => gatherer.toggleServerVote(serverId));
};
// Returns an array of IDs representing votes for leaders
Gather.prototype.leaderVotes = function () {
let self = this;
return self.gatherers
.map(gatherer => gatherer.leaderVote)
.filter(leaderId => typeof leaderId === 'number')
.filter(leaderId => self.containsUser({id: leaderId}));
};
Gather.prototype.voteForLeader = function (voter, candidate) {
this.modifyGatherer(voter, gatherer => gatherer.voteForLeader(candidate));
};
Gather.prototype.getGatherer = function (user) {
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.pop() || null;
};
Gather.prototype.regatherVotes = function () {
let self = this;
return self.gatherers.reduce((acc, gatherer) => {
if (gatherer.regatherVote) acc++;
return acc;
}, 0);
};
// Initiates a timer which will push gather into next state
Gather.prototype.startElectionCountdown = function () {
let self = this;
self.election.startTime = new Date();
this.election.timer = setTimeout(() => {
if (self.can("electionTimeout")) self.electionTimeout();
}, self.election.INTERVAL);
};
Gather.prototype.cancelElectionCountdown = function () {
clearTimeout(this.election.timer);
this.election.timer = null;
this.election.startTime = null;
};
Gather.prototype.applyCooldown = function (user) {
if (user && typeof user.id === 'number') {
let d = new Date();
d.setUTCSeconds(d.getUTCSeconds() + this.COOLDOWN_TIME);
this.cooldown[user.id] = d;
}
};
Gather.prototype.needsToCoolOff = function (user) {
if (user && typeof user.id === 'number') {
let cooldownTime = this.cooldown[user.id];
if (cooldownTime === undefined) return false;
return cooldownTime > new Date();
}
};
Gather.prototype.failsTest = function (user) {
if (!this.membershipTest) return false;
return !this.membershipTest(user);
};
Gather.prototype.getServers = function () {
if (!this.serverMembershipTest) return Server.list;
return Server.list.filter(this.serverMembershipTest);
};
module.exports = Gather;

444
lib/gather/gather.mjs Normal file
View file

@ -0,0 +1,444 @@
/*
* Implements Gather Model
*
* Gather States
* - Gathering
* - Election (Electing leaders)
* - Selection (Selecting teams)
* - Done
*
*/
import Gatherer from "./gatherer.mjs";
import StateMachine from "javascript-state-machine";
import Server from "./server.mjs";
class Gather {
constructor(options) {
if (options === undefined)
options = {};
if (!(this instanceof Gather)) {
return new Gather(options);
}
this.gatherers = [];
let noop = () => { };
this.onDone = (typeof options.onDone === 'function') ?
options.onDone : noop;
this.onEvent = (typeof options.onEvent === 'function') ?
options.onEvent : noop;
this.done = {
time: null
};
this.teamSize = options.teamSize || 6;
// Store cooldown times for gather leaves
this.cooldown = {};
this.COOLDOWN_TIME = 60 * 3; // 3 Minutes
this.REGATHER_THRESHOLD = this.teamSize + 2;
this.type = options.type || "classic";
this.icon = options.icon || "gather icon";
this.name = options.name || "Classic Gather";
this.description = options.description || "No player requirements";
this.election = {
INTERVAL: 60000,
startTime: null,
timer: null
};
if (typeof options.membershipTest === 'function') {
this.membershipTest = options.membershipTest.bind(this);
}
if (typeof options.serverMembershipTest === 'function') {
this.serverMembershipTest = options.serverMembershipTest.bind(this);
}
this.initState();
}
lobbyFull() {
return this.gatherers.length === (this.teamSize * 2);
}
leaderVotesFull() {
return this.leaderVotes().length === (this.teamSize * 2);
}
resetState() {
this.gatherers = [];
this.cancelElectionCountdown();
return this;
}
alienLeader() {
return this.gatherers.reduce((acc, gatherer) => {
if (gatherer.team === "alien" && gatherer.leader)
acc.push(gatherer);
return acc;
}, []).pop();
}
marineLeader() {
return this.gatherers.reduce((acc, gatherer) => {
if (gatherer.team === "marine" && gatherer.leader)
acc.push(gatherer);
return acc;
}, []).pop();
}
assignMarineLeader(id) {
this.modifyGatherer({ id: id }, gatherer => {
gatherer.leader = true;
gatherer.team = "marine";
});
}
assignAlienLeader(id) {
this.modifyGatherer({ id: id }, gatherer => {
gatherer.leader = true;
gatherer.team = "alien";
});
}
containsUser(user) {
return this.gatherers.some(gatherer => {
return gatherer.id === user.id;
});
}
addUser(user) {
if (this.containsUser(user))
return null;
let gatherer = new Gatherer(user);
this.gatherers.push(gatherer);
return gatherer;
}
removeUser(user) {
this.gatherers = this.gatherers.filter(gatherer => user.id !== gatherer.id);
}
modifyGatherer(user, callback) {
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.forEach(callback);
}
// 08/04/20 : 1-1-1-1-1
// 08/11/20 : 1-2-1-1-1
// 09/21/20 : 1-1-1-2-1
getPickingPattern() {
const pickingPattern = [
"marine",
"alien",
"marine",
"alien",
"alien",
"marine",
"alien",
"marine",
"alien",
"marine",
"alien",
"marine",
];
return pickingPattern;
}
// Determines picking order of teams
// Marine pick first
pickingTurnIndex() {
if (this.current !== 'selection')
return null;
const captainCount = 2;
const alienCount = this.aliens().length;
const marineCount = this.marines().length;
const alreadyPickedCount = (marineCount + alienCount) - captainCount;
const pickingPattern = this.getPickingPattern();
const pickingTurn = alreadyPickedCount % pickingPattern.length;
// prevent any team from growing beyond the team size limit
if (marineCount >= this.teamSize) {
return "alien";
} else if (alienCount >= this.teamSize) {
return "marine";
}
return pickingTurn;
}
// Moves player to marine
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
moveToMarine(user, mover) {
if (this.marines().length >= this.teamSize)
return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "marine" ||
!leader.leader ||
this.getPickingPattern()[this.pickingTurnIndex()] !== "marine")
return;
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby")
return;
}
}
this.modifyGatherer(user, gatherer => gatherer.team = "marine");
}
// Moves player to alien
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
moveToAlien(user, mover) {
if (this.aliens().length >= this.teamSize)
return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "alien" ||
!leader.leader ||
this.getPickingPattern()[this.pickingTurnIndex()] !== "alien")
return;
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby")
return;
}
}
return this.modifyGatherer(user, gatherer => gatherer.team = "alien");
}
moveToLobby(user) {
this.modifyGatherer(user, gatherer => gatherer.team = "lobby");
}
retrieveGroup(team) {
return this.gatherers.filter(gatherer => gatherer.team === team);
}
lobby() {
return this.retrieveGroup("lobby");
}
aliens() {
return this.retrieveGroup("alien");
}
marines() {
return this.retrieveGroup("marine");
}
electionStartTime() {
return (this.election.startTime === null) ?
null : this.election.startTime.toISOString();
}
toJson() {
return {
name: this.name,
icon: this.icon,
description: this.description,
type: this.type,
gatherers: this.gatherers,
servers: this.getServers(),
state: this.current,
pickingTurn: this.getPickingPattern()[this.pickingTurnIndex()],
pickingTurnIndex: this.pickingTurnIndex(),
pickingPattern: this.getPickingPattern().splice(0, this.getPickingPattern().length - 2),
election: {
startTime: this.electionStartTime(),
interval: this.election.INTERVAL
},
teamSize: this.teamSize,
done: {
time: this.done.time
},
cooldown: this.cooldown
};
}
toggleMapVote(voter, mapId) {
this.modifyGatherer(voter, gatherer => gatherer.toggleMapVote(mapId));
}
toggleServerVote(voter, serverId) {
this.modifyGatherer(voter, gatherer => gatherer.toggleServerVote(serverId));
}
// Returns an array of IDs representing votes for leaders
leaderVotes() {
let self = this;
return self.gatherers
.map(gatherer => gatherer.leaderVote)
.filter(leaderId => typeof leaderId === 'number')
.filter(leaderId => self.containsUser({ id: leaderId }));
}
voteForLeader(voter, candidate) {
this.modifyGatherer(voter, gatherer => gatherer.voteForLeader(candidate));
}
getGatherer(user) {
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.pop() || null;
}
regatherVotes() {
let self = this;
return self.gatherers.reduce((acc, gatherer) => {
if (gatherer.regatherVote)
acc++;
return acc;
}, 0);
}
// Initiates a timer which will push gather into next state
startElectionCountdown() {
let self = this;
self.election.startTime = new Date();
this.election.timer = setTimeout(() => {
if (self.can("electionTimeout"))
self.electionTimeout();
}, self.election.INTERVAL);
}
cancelElectionCountdown() {
clearTimeout(this.election.timer);
this.election.timer = null;
this.election.startTime = null;
}
applyCooldown(user) {
if (user && typeof user.id === 'number') {
let d = new Date();
d.setUTCSeconds(d.getUTCSeconds() + this.COOLDOWN_TIME);
this.cooldown[user.id] = d;
}
}
needsToCoolOff(user) {
if (user && typeof user.id === 'number') {
let cooldownTime = this.cooldown[user.id];
if (cooldownTime === undefined)
return false;
return cooldownTime > new Date();
}
}
failsTest(user) {
if (!this.membershipTest)
return false;
return !this.membershipTest(user);
}
getServers() {
if (!this.serverMembershipTest)
return Server.list;
return Server.list.filter(this.serverMembershipTest);
}
}
StateMachine.create({
target: Gather.prototype,
events: [
{ name: "initState", from: "none", to: "gathering" },
{ name: "addGatherer", from: "gathering", to: "election" },
{ name: "selectLeader", from: "election", to: "selection" },
{ name: "electionTimeout", from: "election", to: "selection" },
{ name: "confirmSelection", from: "selection", to: "done" },
{
name: "removeGatherer",
from: ["gathering", "election", "selection"],
to: "gathering"
},
{
name: "regather",
from: ["gathering", "election", "selection"],
to: "gathering"
}
],
callbacks: {
// Callbacks for events
onafterevent: function () {
this.onEvent.apply(this, [].slice.call(arguments));
},
// Gathering State
onbeforeaddGatherer: function (event, from, to, user) {
if (this.needsToCoolOff(user)) return false;
if (this.failsTest(user)) return false;
this.addUser(user);
if (!this.lobbyFull()) {
return false;
}
},
// Election State
onbeforeselectLeader: function (event, from, to, voter, candidate) {
this.voteForLeader(voter, candidate);
if (!this.leaderVotesFull()) return false;
},
onenterelection: function () {
// discordBot.notifyChannel("Gather is starting! Pick your captains at https://gathers.ensl.org");
// Setup timer for elections
this.startElectionCountdown();
},
onleaveelection: function () {
this.cancelElectionCountdown();
},
// Selection State
onenterselection: function () {
// Remove all leaders and teams
this.gatherers.forEach(gatherer => {
gatherer.leader = false;
gatherer.team = "lobby";
});
// Assign leaders based on vote
// 1st place alien comm
// 2nd place marine comm
let voteCount = {};
this.gatherers.forEach(gatherer => { voteCount[gatherer.id] = 0 });
this.leaderVotes().forEach(candidateId => { voteCount[candidateId]++ });
let rank = [];
for (let candidate in voteCount) {
rank.push({ candidate: candidate, count: voteCount[candidate] });
}
rank.sort((a, b) => {
return a.count - b.count;
});
this.assignAlienLeader(parseInt(rank.pop().candidate, 0));
this.assignMarineLeader(parseInt(rank.pop().candidate, 0));
},
onleaveselection: function (event, from, to, voter, candidate) {
if (event === "removeGatherer" || event === "regather") {
this.gatherers.forEach(gatherer => {
gatherer.team = "lobby";
});
}
},
onbeforeconfirmSelection: function (event, from, to, leader) {
return (this.aliens().length === this.teamSize
&& this.marines().length === this.teamSize);
},
// Remove gatherer event
onbeforeremoveGatherer: function (event, from, to, user) {
// Cancel transition if no gatherers have been removed
let userCount = this.gatherers.length;
this.removeUser(user);
let userRemoved = userCount > this.gatherers.length;
if (userRemoved && from !== 'gathering') this.applyCooldown(user);
return userRemoved;
},
// Set gatherer vote & if threshold met, reset gather
onbeforeregather: function (event, from, to, user, vote) {
let self = this;
self.modifyGatherer(user, (gatherer) => gatherer.voteRegather(vote));
if (self.regatherVotes() >= self.REGATHER_THRESHOLD) {
self.resetState();
// discordBot.notifyChannel("@here Gather was reset! Rejoin to play!");
return true;
} else {
return false;
}
},
// On enter done
onenterdone: function () {
// discordBot.notifyChannel("Picking finished! Join the server!");
this.done.time = new Date();
this.onDone.apply(this, [].slice.call(arguments));
}
}
});
export default Gather;

View file

@ -1,19 +1,18 @@
"use strict"
/*
* Implements a pool of concurrent gathers
* (no longer a singleton class, should rename)
*
*/
import _ from "lodash";
import winston from "winston"
import mongoose from "mongoose";
import Gather from "./gather.mjs";
import InvitationalGather from "./invitational_gather.mjs";
const _ = require("lodash");
const Gather = require("./gather");
const winston = require("winston");
const mongoose = require("mongoose");
const ArchivedGather = mongoose.model("ArchivedGather");
const InvitationalGather = require("./invitational_gather");
let gatherCallbacks = {};
let archiveUpdatedCallback = () => {};
let archiveUpdatedCallback = () => { };
const GatherPool = new Map();
const GATHER_CONFIGS = [
@ -26,34 +25,28 @@ const GATHER_CONFIGS = [
return server.name.toLowerCase().indexOf("promod") === -1;
}
},
/*{
type: "progmod",
icon: '/progmodGather.png',
name: "Progressive Mod Gather",
{
type: "novice",
icon: "/normalGather.png",
name: "Novice Gather",
description: "No Requirements",
serverMembershipTest: function (server) {
return server.name.toLowerCase().indexOf("promod") !== -1;
}
},*/
return server.name.toLowerCase().indexOf("promod") === -1;
},
},
{
type: "invitational",
icon: "/inviteGather.png",
name: "Invitational Gather",
description: "Join on ensl.org/teams/949",
// Grant invite if on a particular nsl team
membershipTest: function (user) {
return InvitationalGather.list.some(m => m.id === user.id);
},
serverMembershipTest: function (server) {
return server.name.toLowerCase().indexOf("promod") === -1;
},
}
// {
// type: "casual",
// name: "Casual Gather",
// description: "No Requirements",
// teamSize: 7
// }
icon: "/inviteGather.png",
name: "Invitational Gather",
description: "Join on ensl.org/teams/949",
// Grant invite if on a particular nsl team
membershipTest: function (user) {
return InvitationalGather.list.some(m => m.id === user.id);
},
serverMembershipTest: function (server) {
return server.name.toLowerCase().indexOf("promod") === -1;
},
},
];
GATHER_CONFIGS.forEach(config => {
@ -79,11 +72,11 @@ GATHER_CONFIGS.forEach(config => {
reset: function () {
return newGather();
},
current: Gather(),
current: new Gather(),
previous: undefined,
gatherCallbacks: {}
};
gatherManager.gatherCallbacks['onDone'] = [function () {
rotateGather();
}];
@ -103,13 +96,13 @@ GATHER_CONFIGS.forEach(config => {
});
};
return gatherManager.current = Gather(newGatherConfig);
return gatherManager.current = new Gather(newGatherConfig);
};
const archiveGather = gather => {
ArchivedGather.archive(gather, (error, result) => {
if (error) return winston.error(error);
if (archiveUpdatedCallback
if (archiveUpdatedCallback
&& typeof archiveUpdatedCallback === 'function') {
archiveUpdatedCallback();
}
@ -130,4 +123,4 @@ GATHER_CONFIGS.forEach(config => {
// Register initial callback to reset gather when state is `done`
module.exports = GatherPool;
export default GatherPool;

View file

@ -1,69 +0,0 @@
"use strict";
/*
* Implements Gatherer
*
* Stores necessary information including:
* - user data
* - voting preferences
* - leader status
* - Team: "lobby" "alien" "marine"
*/
var User = require("../user/user");
var MAX_MAP_VOTES = 2;
var MAX_SERVER_VOTES = 2;
function Gatherer (user) {
this.leaderVote = null;
this.mapVote = [];
this.serverVote = [];
this.confirm = false;
this.id = user.id;
this.user = user;
this.leader = false;
this.team = "lobby";
this.regatherVote = false;
};
Gatherer.prototype.toggleMapVote = function (mapId) {
if (this.mapVote.some(votedId => votedId === mapId)) {
this.mapVote = this.mapVote.filter(voteId => voteId !== mapId);
return;
}
this.mapVote.push(mapId);
this.mapVote = this.mapVote.slice(this.mapVote.length - MAX_MAP_VOTES,
this.mapVote.length);
};
Gatherer.prototype.toggleServerVote = function (serverId) {
if (this.serverVote.some(votedId => votedId === serverId)) {
this.serverVote = this.serverVote.filter(voteId => voteId !== serverId);
return;
}
this.serverVote.push(serverId);
this.serverVote = this.serverVote.slice(this.serverVote.length -
MAX_SERVER_VOTES, this.serverVote.length);
};
Gatherer.prototype.voteForLeader = function (candidate) {
if (candidate === null) {
return this.leaderVote = null;
}
if (typeof candidate === 'number') {
return this.leaderVote = candidate;
}
this.leaderVote = candidate.id;
};
Gatherer.prototype.voteRegather = function (vote) {
if (vote !== undefined && typeof vote === 'boolean') {
return this.regatherVote = vote;
} else {
this.regatherVote = true;
}
return this.regatherVote;
};
module.exports = Gatherer;

67
lib/gather/gatherer.mjs Normal file
View file

@ -0,0 +1,67 @@
/*
* Implements Gatherer
*
* Stores necessary information including:
* - user data
* - voting preferences
* - leader status
* - Team: "lobby" "alien" "marine"
*/
var MAX_MAP_VOTES = 2;
var MAX_SERVER_VOTES = 2;
class Gatherer {
constructor(user) {
this.leaderVote = null;
this.mapVote = [];
this.serverVote = [];
this.confirm = false;
this.id = user.id;
this.user = user;
this.leader = false;
this.team = "lobby";
this.regatherVote = false;
}
toggleMapVote(mapId) {
if (this.mapVote.some(votedId => votedId === mapId)) {
this.mapVote = this.mapVote.filter(voteId => voteId !== mapId);
return;
}
this.mapVote.push(mapId);
this.mapVote = this.mapVote.slice(this.mapVote.length - MAX_MAP_VOTES,
this.mapVote.length);
}
toggleServerVote(serverId) {
if (this.serverVote.some(votedId => votedId === serverId)) {
this.serverVote = this.serverVote.filter(voteId => voteId !== serverId);
return;
}
this.serverVote.push(serverId);
this.serverVote = this.serverVote.slice(this.serverVote.length -
MAX_SERVER_VOTES, this.serverVote.length);
}
voteForLeader(candidate) {
if (candidate === null) {
return this.leaderVote = null;
}
if (typeof candidate === 'number') {
return this.leaderVote = candidate;
}
this.leaderVote = candidate.id;
}
voteRegather(vote) {
if (vote !== undefined && typeof vote === 'boolean') {
return this.regatherVote = vote;
} else {
this.regatherVote = true;
}
return this.regatherVote;
}
};
export default Gatherer;

View file

@ -1,11 +1,8 @@
"use strict";
import { parallel } from "async";
import { getRandomUser } from "../user/helper.mjs";
var User = require("../user/user");
var client = require("../ensl/client")();
var async = require("async");
var getRandomUser = require("../user/helper").getRandomUser;
var createTestUsers = (options, callback) => {
export const createTestUsers = (options, callback) => {
var gather = options.gather;
var instructions = [];
@ -21,7 +18,7 @@ var createTestUsers = (options, callback) => {
});
};
async.parallel(instructions, (error) => {
parallel(instructions, (error) => {
if (error) {
console.log("Error while adding gatherers", error);
} else {
@ -36,6 +33,6 @@ var createTestUsers = (options, callback) => {
});
};
module.exports = {
createTestUsers: createTestUsers
export default {
createTestUsers
};

View file

@ -1,32 +0,0 @@
"use strict";
const winston = require("winston");
const env = process.env.NODE_ENV || "development";
const client = require("../ensl/client")();
const REFRESH_INTERVAL = 1000 * 60; // Check every minute
const invitationalTeamId = 949;
function InvitationalGather () {
}
InvitationalGather.list = [];
InvitationalGather.updateList = function () {
client.getTeamById({
id: invitationalTeamId
}, (error, result) => {
if (error) {
winston.error("Unable to download team list")
winston.error(error);
return;
};
InvitationalGather.list = result.body.members;
});
};
InvitationalGather.updateList();
setInterval(InvitationalGather.updateList, REFRESH_INTERVAL);
module.exports = InvitationalGather;

View file

@ -0,0 +1,29 @@
import winston from "winston";
import EnslClient from "../ensl/client.mjs";
const env = process.env.NODE_ENV || "development";
const client = new EnslClient();
const REFRESH_INTERVAL = 1000 * 60; // Check every minute
const invitationalTeamId = 949;
class InvitationalGather {
static list = [];
static updateList = function () {
client.getTeamById({
id: invitationalTeamId
}, (error, result) => {
if (error) {
winston.error("Unable to download team list")
winston.error(error);
return;
};
InvitationalGather.list = result.body.members;
});
};
}
InvitationalGather.updateList();
setInterval(InvitationalGather.updateList, REFRESH_INTERVAL);
export default InvitationalGather;

View file

@ -1,31 +0,0 @@
"use strict";
var fs = require("fs");
var path = require("path");
var winston = require("winston");
var client = require(path.join(__dirname, "../ensl/client"))();
const mapsPath = path.join(__dirname, "../../config/data/maps.json");
const REFRESH_INTERVAL = 1000 * 60 * 60; // Check every hour
function Map () {
}
Map.list = JSON.parse(fs.readFileSync(mapsPath)).maps;
Map.updateMapList = () => {
client.getMaps((error, result) => {
if (error) {
winston.error("Unable to download server list")
winston.error(error);
return;
};
Map.list = result.maps;
});
};
Map.updateMapList();
setInterval(Map.updateMapList, REFRESH_INTERVAL);
module.exports = Map;

29
lib/gather/map.mjs Normal file
View file

@ -0,0 +1,29 @@
import fs from "fs";
import {resolve} from "path";
import winston from "winston";
import EnslClient from "../ensl/client.mjs";
const client = new EnslClient();
const mapsPath = resolve("config/data/maps.json");
const REFRESH_INTERVAL = 1000 * 60 * 60; // Check every hour
class Map {
static list = JSON.parse(fs.readFileSync(mapsPath)).maps;
static updateMapList = () => {
client.getMaps((error, result) => {
if (error) {
winston.error("Unable to download server list")
winston.error(error);
return;
};
Map.list = result.maps;
});
};
}
Map.updateMapList();
setInterval(Map.updateMapList, REFRESH_INTERVAL);
export default Map;

View file

@ -1,32 +0,0 @@
"use strict";
var fs = require("fs");
var path = require("path");
var winston = require("winston");
var client = require(path.join(__dirname, "../ensl/client"))();
var serverFile = path.join(__dirname, "../../config/data/servers.json");
const REFRESH_INTERVAL = 1000 * 60 * 60; // Check every hour
function Server () {
}
Server.list = JSON.parse(fs.readFileSync(serverFile)).servers;
Server.updateServerList = () => {
client.getServers((error, result) => {
if (error) {
winston.error("Unable to download server list")
winston.error(error);
return;
};
Server.list = result.servers;
});
};
Server.updateServerList();
setInterval(Server.updateServerList, REFRESH_INTERVAL);
module.exports = Server;

30
lib/gather/server.mjs Normal file
View file

@ -0,0 +1,30 @@
import fs from "fs";
import {resolve} from "path";
import winston from "winston";
import EnslClient from "../ensl/client.mjs";
const client = new EnslClient();
const serverFile = resolve("config/data/servers.json");
const REFRESH_INTERVAL = 1000 * 60 * 60; // Check every hour
class Server {
static list = JSON.parse(fs.readFileSync(serverFile)).servers;
static updateServerList = () => {
client.getServers((error, result) => {
if (error) {
winston.error("Unable to download server list")
winston.error(error);
return;
};
Server.list = result.servers;
});
}
}
Server.updateServerList();
setInterval(Server.updateServerList, REFRESH_INTERVAL);
export default Server;

View file

@ -1,40 +0,0 @@
"use strict";
const { response } = require("express");
const path = require("path");
const { env } = require("process");
const request = require("request");
const logger = require("winston");
const UserStatisticsWrapper = require("./stats_wrapper");
const config = require(path.join(__dirname, "../../config/config"));
const statFile = path.join(__dirname, "../../config/data/hive_stats.json")
function HiveClient(options) {
if (!(this instanceof HiveClient)) {
return new HiveClient(options);
}
}
HiveClient.prototype.getUserStats = function (user, callback) {
if (!user || !user.steam.id) {
return callback(new Error("Invalid user instance supplied"));
}
if (!process.env.NODE_ENV === 'production') {
var stats = JSON.parse(fs.readFileSync(statFile));
return callback(null, new UserStatisticsWrapper(stats["playerstats"]));
} else {
return request({
url: `https://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v2/?key=${config.steam.api_key}&steamid=${user.steam.id}&appid=4920`,
json: true
}, (error, _response, stats) => {
if (response.statusCode !== 200) {
return callback(error, null);
}
return callback(error, new UserStatisticsWrapper(stats["playerstats"]));
});
}
};
module.exports = HiveClient;

41
lib/hive/client.mjs Normal file
View file

@ -0,0 +1,41 @@
"use strict";
import { response } from "express";
import { resolve } from "path";
import fs from "fs";
import request from "request";
import UserStatisticsWrapper from "./stats_wrapper.mjs";
import config from "../../config/config.mjs"
const statFile = resolve("config/data/hive_stats.json")
class HiveClient {
constructor(options) {
if (!(this instanceof HiveClient)) {
return new HiveClient(options);
}
}
getUserStats(user, callback) {
if (!user || !user.steam.id) {
return callback(new Error("Invalid user instance supplied"));
}
if (process.env.NODE_ENV === 'production') {
return request({
url: `https://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v2/?key=${config.steam.api_key}&steamid=${user.steam.id}&appid=4920`,
json: true
}, (error, _response, stats) => {
if (response.statusCode !== 200) {
return callback(error, null);
}
return callback(error, new UserStatisticsWrapper(stats["playerstats"]));
});
} else {
var stats = JSON.parse(fs.readFileSync(statFile));
return callback(null, new UserStatisticsWrapper(stats["playerstats"]));
}
}
}
export default HiveClient;

View file

@ -1,29 +0,0 @@
"use strict";
// UserStatistics constructor parses Steam ISteamUserStats responses into
// unified statistical interface
// StatAttributes also provides default value as fallback
const StatAttributes = {
level: (stats, apiValue) => { stats['level'] = apiValue },
score: (stats, apiValue) => { stats['score'] = apiValue },
skill: (stats, apiValue) => { stats['score'] = apiValue },
td_rounds_won_player: (stats, apiValue) => {stats['player_wins'] = apiValue },
td_rounds_won_commander: (stats, apiValue) => {stats['comm_wins'] = apiValue },
skill: (stats, apiValue) => {stats['skill'] = apiValue },
comm_skill: (stats, apiValue) => {stats['comm_skill'] = apiValue },
td_total_time_player: (stats, apiValue) => {stats['player_time'] = apiValue },
td_total_time_commander: (stats, apiValue) => {stats['commander_time'] = apiValue },
};
const NoopSetter = (_stats, _apiValue) => {};
function UserStatisticsWrapper (apiResponse = {}) {
this["steamId"] = apiResponse.steamID
var stats = apiResponse.stats || {};
for(attribute in stats) {
var setter = StatAttributes[attribute.name] || NoopSetter;
setter(this,attribute.value);
}
}
module.exports = UserStatisticsWrapper;

View file

@ -0,0 +1,31 @@
// UserStatistics constructor parses Steam ISteamUserStats responses into
// unified statistical interface
import { stat } from "fs";
// StatAttributes also provides default value as fallback
const StatAttributes = {
level: (stats, apiValue) => { stats['level'] = apiValue },
score: (stats, apiValue) => { stats['score'] = apiValue },
skill: (stats, apiValue) => { stats['score'] = apiValue },
td_rounds_won_player: (stats, apiValue) => { stats['player_wins'] = apiValue },
td_rounds_won_commander: (stats, apiValue) => { stats['comm_wins'] = apiValue },
skill: (stats, apiValue) => { stats['skill'] = apiValue },
comm_skill: (stats, apiValue) => { stats['comm_skill'] = apiValue },
td_total_time_player: (stats, apiValue) => { stats['player_time'] = apiValue },
td_total_time_commander: (stats, apiValue) => { stats['commander_time'] = apiValue },
};
const NoopSetter = (_stats, _apiValue) => { };
class UserStatisticsWrapper {
constructor(apiResponse = {}) {
this["steamId"] = apiResponse.steamID;
var stats = apiResponse.stats || [];
stats.forEach(element => {
var setter = StatAttributes[element.name] || NoopSetter;
setter(this, element.value);
});
}
}
export default UserStatisticsWrapper;

View file

@ -1,94 +0,0 @@
"use strict";
var _ = require("lodash");
var steam = require("steam");
var winston = require("winston");
var password = process.env.GATHER_STEAM_PASSWORD;
var account_name = process.env.GATHER_STEAM_ACCOUNT;
var GatherPool = require("../gather/gather_pool");
function SteamBot(config) {
let self = this;
self.client = new steam.SteamClient();
self.user = new steam.SteamUser(self.client);
self.friends = new steam.SteamFriends(self.client);
let addToGather = (steamId, message) => {
self.friends.sendMessage(steamId, "Added you to the gather");
};
let removeFromGather = (steamId, message) => {
self.friends.sendMessage(steamId, "Removed your from gather");
};
let returnGatherInfo = (steamId, _) => {
let gather = GatherPool.get("public").current;
let state = gather.current.toUpperCase();
let players = gather.gatherers.length;
let message = "Current Gather: " + state + " (" + players + "/12 Players)";
self.friends.sendMessage(steamId, message);
};
let showHelp = (steamId, message) => {
self.friends.sendMessage(steamId, "Bot Commands:");
self.friends.sendMessage(steamId, "!info - Get information on current gather");
// self.friends.sendMessage(steamId, "!join - Join current gather");
// self.friends.sendMessage(steamId, "!leave - Leave current gather");
};
let confirmFriend = steamId => {
self.friends.addFriend(steamId);
self.friends.sendMessage(steamId, "You're now registered on the ENSL Gather Bot. Type !help to find out how to interact");
};
self.client.on("connected", () => {
self.user.logOn(config);
});
self.client.on("error", error => {
winston.error(error);
winston.info("Reconnecting steam bot in 5 seconds");
setTimeout(() => {
self.client.connect();
}, 5000);
});
self.client.on('logOnResponse', logonResp => {
if (logonResp.eresult == steam.EResult.OK) {
winston.info("Logged onto Steam");
// Go online
self.friends.setPersonaState(steam.EPersonaState.Online);
// Accept backlog of friend request
for (let friend in self.friends.friends) {
if (self.friends.friends[friend] < 3) confirmFriend(friend);
};
}
});
self.friends.on('friend', (steamId, relationship) => {
if (steam.EFriendRelationship.RequestRecipient === relationship) {
confirmFriend(steamId);
}
});
self.friends.on('friendMsg', (steamId, message, EChatEntryType) => {
winston.info(message);
if (message.match(/^!help/i)) return showHelp(steamId, message);
// if (message.match(/^!join/i)) return addToGather(steamId, message);
// if (message.match(/^!leave/i)) return removeFromGather(steamId, message);
if (message.match(/^!info/i)) return returnGatherInfo(steamId, message);
});
self.client.connect();
}
var bot;
module.exports = config => {
if (bot) return bot;
if (!config) throw new Error("No credentials provided for Steam Gather Bot");
bot = new SteamBot(config);
return bot;
};

View file

@ -1,5 +1,3 @@
"use strict";
/*
* User Controller
*
@ -14,24 +12,23 @@
*
*/
import winston from "winston";
import _ from "lodash";
import User from "./user.mjs";
var userCache = {};
var User = require("./user");
var winston = require("winston");
var mongoose = require("mongoose");
var Session = mongoose.model("Session");
var enslClient = require("../ensl/client")();
var _ = require("lodash");
module.exports = namespace => {
var refreshUsers = socket => {
var receivers = (socket !== undefined) ? [socket] : namespace.sockets;
export default namespace => {
var refreshUsers = socket => {
var receivers = (socket !== undefined) ? [socket] : namespace.sockets;
var newCache = {};
for(let socketid in namespace.sockets) {
let socket = namespace.sockets[socketid];
var user = socket._user;
var newCache = {};
for (let socketid in namespace.sockets) {
let socket = namespace.sockets[socketid];
var user = socket._user;
newCache[user.id] = user;
}
}
userCache = newCache;
@ -44,14 +41,14 @@ module.exports = namespace => {
}
for(let socketid in receivers) {
let socket = receivers[socketid];
socket.emit('users:update', {
for (let socketid in receivers) {
let socket = receivers[socketid];
socket.emit('users:update', {
count: users.length,
users: users,
currentUser: socket._user
});
}
}
};
namespace.on('connection', socket => {
@ -72,7 +69,7 @@ module.exports = namespace => {
socket.on('users:disconnect', data => {
const id = data.id;
if (typeof id !== 'number' || !socket._user.admin) return;
if (typeof id !== 'number' || !socket._user.admin) return;
Object.values(namespace.sockets)
.filter(socket => socket._user.id === id)
.forEach(socket => socket.disconnect());
@ -93,6 +90,6 @@ module.exports = namespace => {
});
});
socket.on('disconnect', socket => { refreshUsers(); });
socket.on('disconnect', socket => { refreshUsers(); });
});
};

View file

@ -1,8 +1,6 @@
"use strict";
import User from "./user.mjs";
const User = require("../user/user");
const getRandomUser = callback => {
export const getRandomUser = callback => {
const id = Math.floor(Math.random() * 5000) + 1;
User.find(id, function (error, user) {
if (error) return getRandomUser(callback);
@ -10,14 +8,16 @@ const getRandomUser = callback => {
})
};
const getFixedUser = (id, callback) => {
export const getFixedUser = (id, callback) => {
User.find(id, function (error, user) {
if (error) return getRandomUser(callback);
return callback(error, user);
})
};
module.exports = {
export default {
getRandomUser,
getFixedUser
};

View file

@ -1,115 +0,0 @@
"use strict";
/*
* Implements User Model
*
*/
var _ = require("lodash");
var async = require("async");
var mongoose = require("mongoose");
var Profile = mongoose.model("Profile");
var steam = require('steamidconvert')();
var enslClient = require("../ensl/client")();
var hiveClient = require("../hive/client")();
function User (user) {
this.id = user['id'];
this.online = true;
this.username = user['username'];
this.country = user['country'];
this.time_zone = user['time_zone'];
this.avatar = enslClient.getFullAvatarUri(user['avatar']);
this.admin = user['admin'];
this.moderator = user['moderator'];
this.team = user['team'];
this.bans = user['bans'];
if (user['steam']) {
this.steam = {
id: steam.convertTo64(user['steam']['id']),
url: user['steam']['url'] || null,
nickname: user['steam']['nickname'] || null
};
} else {
this.steam = {
id: null,
url: null,
nickname: null
};
}
this.profile = null;
this.hive = {
id: null
};
}
User.prototype.isChatAdmin = function () {
return this.admin || this.moderator;
};
User.prototype.isGatherAdmin = function () {
return this.admin || this.moderator;
};
User.prototype.isUserAdmin = function () {
return this.admin;
};
var allowedAttributes = ["enslo", "division", "skill", "gatherMusic"];
var allowedAbilities = ["skulk", "lerk", "fade", "gorge", "onos", "commander"];
User.prototype.updateProfile = function (data, callback) {
let self = this;
Profile.findOne({userId: self.id}, (error, profile) => {
if (error) return callback(error);
allowedAttributes.forEach(function (attr) {
if (data[attr] !== undefined) profile[attr] = data[attr];
});
if (data.abilities) {
allowedAbilities.forEach(function (attr) {
let newAbility = data.abilities[attr];
let abilities = profile.abilities;
if (newAbility !== undefined) abilities[attr] = newAbility;
});
}
profile.save(function (error, profile) {
if (error) return callback(error);
self.profile = profile.toJson();
return callback(error, profile);
});
});
};
User.find = function (id, callback) {
enslClient.getUserById({
id: id
}, (error, response, body) => {
if (error) return callback(error);
if (response.statusCode !== 200) return callback(new Error("Unable to auth user against API"));
let user = new User(body);
async.parallel([
// Retrieve or create user profile from local store
callback => {
Profile.findOrCreate(user, (error, profile) => {
if (error) return callback(error);
user.profile = profile.toJson();
return callback(null, profile);
});
},
callback => {
hiveClient.getUserStats(user, (error, stats) => {
if (error || !stats || stats.steamId === null) return callback();
_.assign(user.hive, userStats.stats);
return callback(null, userStats);
});
}
], function (error, result) {
if (error) return callback(error);
return callback(null, user);
})
});
};
module.exports = User;

125
lib/user/user.mjs Normal file
View file

@ -0,0 +1,125 @@
/*
* Implements User Model
*
*/
import _ from "lodash";
import async from "async"
import mongoose from "mongoose";
import SteamConvert from "steamidconvert";
import EnslClient from "../ensl/client.mjs";
import HiveClient from "../hive/client.mjs";
const Profile = mongoose.model("Profile");
const steam = new SteamConvert();
const enslClient = new EnslClient();
const hiveClient = new HiveClient();
class User {
constructor(user) {
this.id = user['id'];
this.online = true;
this.username = user['username'];
this.country = user['country'];
this.time_zone = user['time_zone'];
this.avatar = enslClient.getFullAvatarUri(user['avatar']);
this.admin = user['admin'];
this.moderator = user['moderator'];
this.team = user['team'];
this.bans = user['bans'];
if (user['steam']) {
this.steam = {
id: steam.convertTo64(user['steam']['id']),
url: user['steam']['url'] || null,
nickname: user['steam']['nickname'] || null
};
} else {
this.steam = {
id: null,
url: null,
nickname: null
};
}
this.profile = null;
this.hive = {
id: null
};
}
static find(id, callback) {
enslClient.getUserById({
id: id
}, (error, response, body) => {
if (error)
return callback(error);
if (response.statusCode !== 200)
return callback(new Error("Unable to auth user against API"));
let user = new User(body);
async.parallel([
// Retrieve or create user profile from local store
callback => {
Profile.findOrCreate(user, (error, profile) => {
if (error)
return callback(error);
user.profile = profile.toJson();
return callback(null, profile);
});
},
callback => {
hiveClient.getUserStats(user, (error, stats) => {
if (error || !stats || stats.steamId === null)
return callback();
_.assign(user.hive, stats);
return callback(null, stats);
});
}
], function (error, result) {
if (error)
return callback(error);
return callback(null, user);
});
});
}
isChatAdmin() {
return this.admin || this.moderator;
}
isGatherAdmin() {
return this.admin || this.moderator;
}
isUserAdmin() {
return this.admin;
}
updateProfile(data, callback) {
let self = this;
Profile.findOne({ userId: self.id }, (error, profile) => {
if (error)
return callback(error);
allowedAttributes.forEach(function (attr) {
if (data[attr] !== undefined)
profile[attr] = data[attr];
});
if (data.abilities) {
allowedAbilities.forEach(function (attr) {
let newAbility = data.abilities[attr];
let abilities = profile.abilities;
if (newAbility !== undefined)
abilities[attr] = newAbility;
});
}
profile.save(function (error, profile) {
if (error)
return callback(error);
self.profile = profile.toJson();
return callback(error, profile);
});
});
}
}
var allowedAttributes = ["enslo", "division", "skill", "gatherMusic"];
var allowedAbilities = ["skulk", "lerk", "fade", "gorge", "onos", "commander"];
export default User;

427
package-lock.json generated
View file

@ -30,7 +30,6 @@
"moment": "^2.11.2",
"mongoose": "^6.10.1",
"morgan": "~1.9.1",
"newrelic": "~5.13.1",
"perfect-scrollbar": "~0.6.10",
"react": "^16.13.1",
"react-autolink": "^0.2.1",
@ -41,7 +40,6 @@
"snyk": "^1.316.1",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"steam": "^1.1.0",
"steamidconvert": "~0.2.4",
"toastr": "~2.1.4",
"winston": "~1.0.1"
@ -2645,43 +2643,6 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@newrelic/koa": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@newrelic/koa/-/koa-1.0.8.tgz",
"integrity": "sha512-kY//FlLQkGdUIKEeGJlyY3dJRU63EG77YIa48ACMGZxQbWRd3WZMikyft33f8XScTq6WpCDo9xa0viNo8zeYkg==",
"dependencies": {
"methods": "^1.1.2"
},
"peerDependencies": {
"newrelic": ">=3.3.1"
}
},
"node_modules/@newrelic/native-metrics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@newrelic/native-metrics/-/native-metrics-4.1.0.tgz",
"integrity": "sha512-7CZlKMLuaYQW7mV9qVyo9b9HVe2xBnyn+kkETRJoZGs5P7gdfv9AAE3RPhtOBUopTfbmc8ju7njYadjui9J1XA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"nan": "^2.12.1",
"semver": "^5.5.1"
},
"engines": {
"node": ">=6",
"npm": ">=3"
}
},
"node_modules/@newrelic/superagent": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@newrelic/superagent/-/superagent-1.0.3.tgz",
"integrity": "sha512-lJbsqKa79qPLbHZsbiRaXl1jfzaXAN7zqqnLRqBY+zI/O5zcfyNngTmdi+9y+qIUq7xHYNaLsAxCXerrsoINKg==",
"dependencies": {
"methods": "^1.1.2"
},
"peerDependencies": {
"newrelic": ">=4.3.0"
}
},
"node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@ -2689,60 +2650,6 @@
"dev": true,
"optional": true
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -2903,11 +2810,6 @@
"winston": "*"
}
},
"node_modules/@tyriar/fibonacci-heap": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@tyriar/fibonacci-heap/-/fibonacci-heap-2.0.9.tgz",
"integrity": "sha512-bYuSNomfn4hu2tPiDN+JZtnzCpSpbJ/PNeulmocDy3xN2X5OkJL65zo6rPZp65cPPhLF9vfT/dgE+RtFRCSxOA=="
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -2974,25 +2876,6 @@
"node": ">=0.4.0"
}
},
"node_modules/adm-zip": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz",
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==",
"engines": {
"node": ">=0.3.0"
}
},
"node_modules/agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"dependencies": {
"es6-promisify": "^5.0.0"
},
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/ajv": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
@ -3761,18 +3644,11 @@
"ieee754": "^1.1.4"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
"engines": {
"node": "*"
}
},
"node_modules/buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"node_modules/buffer-xor": {
"version": "1.0.3",
@ -3784,25 +3660,6 @@
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
},
"node_modules/bytebuffer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz",
"integrity": "sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ==",
"dependencies": {
"long": "~3"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/bytebuffer/node_modules/long": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
"integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@ -4121,20 +3978,6 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@ -4379,11 +4222,6 @@
"node": "*"
}
},
"node_modules/deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"node_modules/define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -4899,19 +4737,6 @@
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dependencies": {
"es6-promise": "^4.0.3"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -4934,48 +4759,6 @@
"node": ">=0.8.0"
}
},
"node_modules/escodegen": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=4.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/escodegen/node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/escodegen/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/esprima": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz",
@ -4988,18 +4771,11 @@
"node": ">=0.4.0"
}
},
"node_modules/estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -6011,27 +5787,6 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"node_modules/https-proxy-agent": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
"integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
"dependencies": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/https-proxy-agent/node_modules/debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -6532,18 +6287,6 @@
"node": ">= 6"
}
},
"node_modules/levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dependencies": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -7295,12 +7038,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
@ -7364,55 +7101,6 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/newrelic": {
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/newrelic/-/newrelic-5.13.1.tgz",
"integrity": "sha512-FRChTKLh29benj2r//8/q+nLX3oHYlaOkOAjCVkilbTpp8OwR84FFDZNWRVuocxWP+yPR6ayfPaNc0ueLp9R7g==",
"deprecated": "This version of the New Relic Node Agent has reached the end of life.",
"dependencies": {
"@newrelic/koa": "^1.0.8",
"@newrelic/superagent": "^1.0.2",
"@tyriar/fibonacci-heap": "^2.0.7",
"async": "^2.1.4",
"concat-stream": "^2.0.0",
"escodegen": "^1.11.1",
"esprima": "^4.0.1",
"https-proxy-agent": "^3.0.0",
"json-stringify-safe": "^5.0.0",
"readable-stream": "^3.1.1",
"semver": "^5.3.0"
},
"bin": {
"newrelic-naming-rules": "bin/test-naming-rules.js"
},
"engines": {
"node": ">=6.0.0 <13.0.0",
"npm": ">=3.0.0"
},
"optionalDependencies": {
"@newrelic/native-metrics": "^4.0.0"
}
},
"node_modules/newrelic/node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/newrelic/node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/node-browser-modules": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-browser-modules/-/node-browser-modules-0.2.1.tgz",
@ -7668,27 +7356,6 @@
"wrappy": "1"
}
},
"node_modules/optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
"dependencies": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.6",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"word-wrap": "~1.2.3"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/optionator/node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"node_modules/os-browserify": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
@ -7896,14 +7563,6 @@
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/prism-media": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.4.tgz",
@ -7940,39 +7599,6 @@
"node": ">=0.10.0"
}
},
"node_modules/protobufjs": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz",
"integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/protobufjs/node_modules/@types/node": {
"version": "18.14.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz",
"integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA=="
},
"node_modules/protobufjs/node_modules/long": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
"integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -8448,6 +8074,7 @@
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
@ -9142,28 +8769,6 @@
"node": ">= 0.8"
}
},
"node_modules/steam": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/steam/-/steam-1.1.0.tgz",
"integrity": "sha512-Zgg1WR/5SnO2dPBb0bEdC2SPQNlkywoB7ZUNk0HzrfXUWhk+4FbnIzTCc1yAeBZOOYb2K4ID4zZDlRo8Vqr0gg==",
"deprecated": "this project is not maintained",
"dependencies": {
"adm-zip": "*",
"buffer-crc32": "*",
"bytebuffer": ">=3.5.5",
"protobufjs": ">=4.0.0",
"steam-crypto": "*"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/steam-crypto": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/steam-crypto/-/steam-crypto-0.0.1.tgz",
"integrity": "sha1-BHexgqKx/dlBiT28wi4Ok7FKu38=",
"deprecated": "this project is not maintained"
},
"node_modules/steamidconvert": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/steamidconvert/-/steamidconvert-0.2.4.tgz",
@ -9632,17 +9237,6 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
},
"node_modules/type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dependencies": {
"prelude-ls": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/type-detect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz",
@ -9683,11 +9277,6 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"node_modules/uglify-js": {
"version": "3.10.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.2.tgz",
@ -10008,14 +9597,6 @@
"resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
"integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k="
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",

View file

@ -51,7 +51,6 @@
"moment": "^2.11.2",
"mongoose": "^6.10.1",
"morgan": "~1.9.1",
"newrelic": "~5.13.1",
"perfect-scrollbar": "~0.6.10",
"react": "^16.13.1",
"react-autolink": "^0.2.1",
@ -62,7 +61,6 @@
"snyk": "^1.316.1",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"steam": "^1.1.0",
"steamidconvert": "~0.2.4",
"toastr": "~2.1.4",
"winston": "~1.0.1"