Merge pull request #4 from ENSL/fix/hive-stats-from-steam

Hive Stats from Steam + Novice Gather
This commit is contained in:
Absurdon 2023-03-10 16:05:51 +01:00 committed by GitHub
commit 93b0e6b8f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 7674 additions and 10137 deletions

25
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,25 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:0-18
# Install MongoDB command line tools - though mongo-database-tools not available on arm64
ARG MONGO_TOOLS_VERSION=6.0
RUN . /etc/os-release \
&& curl -sSL "https://www.mongodb.org/static/pgp/server-${MONGO_TOOLS_VERSION}.asc" | gpg --dearmor > /usr/share/keyrings/mongodb-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] http://repo.mongodb.org/apt/debian ${VERSION_CODENAME}/mongodb-org/${MONGO_TOOLS_VERSION} main" | tee /etc/apt/sources.list.d/mongodb-org-${MONGO_TOOLS_VERSION}.list \
&& apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y mongodb-mongosh \
&& if [ "$(dpkg --print-architecture)" = "amd64" ]; then apt-get install -y mongodb-database-tools; fi \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"

View file

@ -0,0 +1,31 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node-mongo
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"mongodb.mongodb-vscode"
]
}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [3000, 27017],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -0,0 +1,36 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: mongo:6
restart: unless-stopped
volumes:
- mongodb-data:/data/db
# Uncomment to change startup options
# environment:
# MONGO_INITDB_ROOT_USERNAME: root
# MONGO_INITDB_ROOT_PASSWORD: example
# MONGO_INITDB_DATABASE: your-database-here
# Add "forwardPorts": ["27017"] to **devcontainer.json** to forward MongoDB locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
volumes:
mongodb-data:

View file

@ -24,4 +24,4 @@ RUN /bin/cp -r ./public /home/web/tmp/public && \
EXPOSE 8000
CMD ["node", "index.js"]
CMD ["node", "index.mjs"]

View file

@ -1 +0,0 @@
web: npm run start_production

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

@ -6,4 +6,4 @@ if [ -f "/home/web/tmp/.updatePublic" ]; then
rm -f /home/web/tmp/.updatePublic
fi
node index.js
node index.mjs

View file

@ -1,45 +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.steamBot.account_name = process.env.GATHER_STEAM_ACCOUNT;
}
if (process.env.GATHER_STEAM_PASSWORD) {
baseConfig.steamBot.password = process.env.GATHER_STEAM_PASSWORD;
}
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;

129
config/data/hive_stats.json Normal file
View file

@ -0,0 +1,129 @@
{
"playerstats": {
"steamID": "12345678901234567",
"gameName": "",
"achievements": [],
"stats": [
{
"name": "skulk_challenge_1",
"value": 4
},
{
"name": "rounds_played",
"value": 44
},
{
"name": "halloween_18_rounds_played",
"value": 42
},
{
"name": "unearthed_release_rounds_played",
"value": 42
},
{
"name": "origin_release_rounds_played",
"value": 42
},
{
"name": "metro_release_rounds_played",
"value": 42
},
{
"name": "tanith_rounds_won",
"value": 42
},
{
"name": "td_total_time_player",
"value": 147699
},
{
"name": "td_total_time_commander",
"value": 13236
},
{
"name": "td_rounds_won_player",
"value": 90
},
{
"name": "td_rounds_won_commander",
"value": 5
},
{
"name": "skill",
"value": 4491
},
{
"name": "skill_offset",
"value": 156
},
{
"name": "comm_skill",
"value": 1338
},
{
"name": "comm_skill_offset",
"value": 65
},
{
"name": "adagrad",
"value": 2.19463610649108887
},
{
"name": "comm_adagrad",
"value": 0.0838916152715682983
},
{
"name": "td_skill",
"value": 4689
},
{
"name": "td_skill_offset",
"value": 94
},
{
"name": "td_comm_skill",
"value": 1016
},
{
"name": "td_comm_skill_offset",
"value": 118
},
{
"name": "td_adagrad",
"value": 2.04355740547180176
},
{
"name": "td_comm_adagrad",
"value": 0.0194523315876722336
},
{
"name": "xp",
"value": 9797637
},
{
"name": "level",
"value": 600
},
{
"name": "score",
"value": 1818653
},
{
"name": "skill_offset_sign",
"value": 1
},
{
"name": "comm_skill_offset_sign",
"value": 1
},
{
"name": "td_skill_offset_sign",
"value": 0
},
{
"name": "td_comm_skill_offset_sign",
"value": 1
}
]
}
}

View file

@ -1,17 +0,0 @@
"use strict";
var config = {
port: 8000,
mongo: {
uri: "mongodb://localhost/swsgather_development"
},
secret_token: "",
session_store_name: "_ENSL_session_key",
hive_url: "http://hive.naturalselection2.com",
hive2_url: "http://hive2.ns2cdt.com",
ensl_url: "http://www.ensl.org/",
ensl_rules_url: "http://www.ensl.org/articles/464",
steam_bot_link: "http://steamcommunity.com/id/nslgathers"
};
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,17 +0,0 @@
"use strict";
var config = {
port: 80,
mongo: {
uri: "" // Set using MONGOLAB_URI
},
secret_token: "",
session_store_name: "_ENSL_session_key",
hive_url: "http://hive.naturalselection2.com",
hive2_url: "http://hive2.ns2cdt.com",
ensl_url: "https://www.ensl.org/",
ensl_rules_url: "https://www.ensl.org/articles/464",
steam_bot_link: "http://steamcommunity.com/id/nslgathers"
};
module.exports = config;

View file

@ -0,0 +1,15 @@
var config = {
port: 80,
mongo: {
uri: "" // Set using MONGOLAB_URI
},
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,17 +0,0 @@
"use strict";
var config = {
port: 80,
mongo: {
uri: "" // Set using MONGOLAB_URI
},
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
hive_url: "http://hive.naturalselection2.com/",
hive2_url: "http://hive2.ns2cdt.com",
ensl_url: "http://staging.ensl.org/",
ensl_rules_url: "http://www.ensl.org/articles/464",
steam_bot_link: "http://steamcommunity.com/id/nslgathers"
};
module.exports = config;

View file

@ -0,0 +1,15 @@
var config = {
port: 80,
mongo: {
uri: "" // Set using MONGOLAB_URI
},
secret_token: "",
session_store_name: "_ENSL_session_key_staging",
ensl_url: "http://staging.ensl.org",
ensl_rules_url: "http://www.ensl.org/articles/464",
steam: {
api_key: '',
}
};
export default config;

View file

@ -1,5 +1,3 @@
"use strict";
var config = {
port: 9000,
mongo: {
@ -7,11 +5,11 @@ var config = {
},
secret_token: "SUPERSECRETFOO",
session_store_name: "_ENSL_session_key_staging",
hive_url: "http://hive.naturalselection2.com",
hive2_url: "http://hive2.ns2cdt.com",
ensl_rules_url: "http://www.ensl.org/articles/464",
ensl_url: "http://staging.ensl.org/",
steam_bot_link: "http://steamcommunity.com/id/nslgathers"
ensl_url: "http://staging.ensl.org",
steam: {
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");
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,8 @@
"use strict";
import winston from "winston"
import mongoose from "mongoose";
import config from "../config/config.mjs";
var path = require("path");
var winston = require("winston");
var mongoose = require("mongoose");
var config = require(path.join(__dirname, "../config/config.js"));
mongoose.set('strictQuery', true);
var connect = function () {
mongoose.connect(config.mongo.uri, {
@ -23,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,39 +0,0 @@
version: "3.8"
services:
production:
container_name: ensl_gather_production
build:
context: ./
target: production
dockerfile: Dockerfile
depends_on:
- mongodb
command: ["/app/bin/entry.sh"]
user: web:web
environment:
- NODE_ENV=production
- PORT=$NODE_PORT
- "MONGOLAB_URI=mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@mongodb/${MONGODB_DATABASE}"
- RAILS_SECRET
- NEW_RELIC_LICENSE_KEY
- GATHER_STEAM_ACCOUNT
- GATHER_STEAM_PASSWORD
- GATHER_DISCORD_HOOK_ID
- GATHER_DISCORD_HOOK_TOKEN
- RANDOM_USER
- FIXED_USER
ports:
- "${NODE_PORT}:${NODE_PORT}"
volumes:
- "./public:/app/public"
init: true
mongodb:
image: "bitnami/mongodb:4.4"
container_name: ensl_gather_mongodb
volumes:
- "./db/data:/bitnami/mongodb"
environment:
- MONGODB_USERNAME
- MONGODB_PASSWORD
- MONGODB_DATABASE
- MONGODB_ROOT_PASSWORD=$MONGODB_PASSWORD

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,42 +0,0 @@
"use strict";
const path = require("path");
const request = require("request");
const logger = require("winston");
const querystring = require('querystring');
const UserStatisticsWrapper = require("./stats_wrapper");
const config = require(path.join(__dirname, "../../config/config"));
function HiveClient (options) {
if (!(this instanceof HiveClient)) {
return new HiveClient(options);
}
this.baseUrl = config.hive2_url;
}
HiveClient.prototype.getUserStats = function (user, callback) {
if (!user || !user.hive.id) {
return callback(new Error("Invalid user instance supplied"));
}
return request({
url: `${config.hive_url}/api/get/playerData/${user.hive.id}`,
json: true
}, (error, response, body) => {
return callback(error, response, new UserStatisticsWrapper(body));
});
};
HiveClient.prototype.getUserStatsV2 = function (user, callback) {
if (!user || !user.hive.id) {
return callback(new Error("Invalid user instance supplied"));
}
return request({
url: `${config.hive2_url}/api/get/playerData/${user.hive.id}`,
json: true
}, (error, response, body) => {
return callback(error, response, new UserStatisticsWrapper(body));
});
};
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,104 +0,0 @@
"use strict";
// UserStatistics constructor parses Hive 1 and Hive 2 responses into
// unified statistical interface
// StatAttributes outlines required parameters and how to extract them
// from V1 or V2 api responses. Also provides default value as fallback
const StatAttributes = {
steamId: {
v1: "steamId",
v2: "steamid",
default: 0
},
assists: {
v1: "assists",
v2: null,
default: 0
},
deaths: {
v1: "deaths",
v2: null,
default: 0
},
kills: {
v1: "kills",
v2: null,
default: 0
},
level: {
v1: "level",
v2: "level",
default: 0
},
loses: {
v1: "loses",
v2: "loses",
default: 0
},
playTime: {
v1: "playTime",
v2: "time_played",
default: 0
},
score: {
v1: "score",
v2: "score",
default: 0
},
wins: {
v1: "wins",
v2: null,
default: 0
},
reinforcedTier: {
v1: "reinforcedTier",
v2: "reinforced_tier",
default: null
},
badges: {
v1: "badges",
v2: "badges",
default: []
},
skill: {
v1: "skill",
v2: "skill",
default: 0
},
pid: {
v1: null,
v2: "pid",
default: 0
},
"marine_playtime": {
v1: null,
v2: "marine_playtime",
default: 0
},
"alien_playtime": {
v1: null,
v2: "alien_playtime",
default: 0
},
"commander_time": {
v1: null,
v2: "commander_time",
default: 0
}
};
function UserStatisticsWrapper (apiResponse = {}) {
for (let attr in StatAttributes) {
let adapter = StatAttributes[attr];
this[attr] = apiResponse[adapter.v1] ||
apiResponse[adapter.v2] ||
adapter.default;
}
// New skill type appears to be Number, revert to string for now
if (typeof this["skill"] === "number") {
this["skill"] = Math.round(this["skill"]).toString();
}
}
module.exports = UserStatisticsWrapper;

View file

@ -0,0 +1,28 @@
// 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['skill'] = apiValue },
skill_offset: (stats, apiValue) => { stats['skill_offset'] = apiValue },
comm_skill: (stats, apiValue) => { stats['comm_skill'] = apiValue },
comm_skill_offset: (stats, apiValue) => { stats['comm_offset'] = 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,124 +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: user['steam']['id'] || this.getSteamId(),
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
};
if (this.steam.id) {
this.hive.id = this.getHiveId();
}
}
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;
};
User.prototype.getSteamId = function () {
if (this.steam.url === null) return null;
var urlId = this.steam.url.match(/\d*$/);
if (!urlId || urlId[0].length === 0) return null;
return steam.convertToText(urlId[0]);
};
User.prototype.getHiveId = function () {
var steamId = this.steam.id;
if (!steamId) return null;
var index = steamId.match(/:0:/) ? 0 : 1;
var tailId = parseInt(steamId.match(/\d*$/), 10);
return index === 1 ? (tailId * 2) + 1 : tailId * 2;
};
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);
});
}
], 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;

View file

@ -1,24 +0,0 @@
/**
* New Relic agent configuration.
*
* See lib/config.defaults.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
exports.config = {
/**
* Array of application names.
*/
app_name: ['enslgathers'],
/**
* Your New Relic license key.
*/
license_key: process.env.NEW_RELIC_LICENSE_KEY,
logging: {
/**
* Level at which to log. 'trace' is most useful to New Relic when diagnosing
* issues with the agent, 'info' and higher will impose the least overhead on
* production applications.
*/
level: 'info'
}
}

14572
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
{
"name": "sws_gathers",
"version": "2.0.3",
"version": "2.1.0",
"description": "ENSL Gather Service",
"main": "index.js",
"main": "index.mjs",
"keywords": [
"NS2",
"Gathers",
@ -10,28 +10,29 @@
],
"scripts": {
"test": "NODE_ENV=test mocha spec/",
"start": "npm run compile && node index.js",
"start_production": "npm run compile_production & node index.js",
"start": "npm run compile && node index.mjs",
"start_production": "npm run compile_production & node index.mjs",
"watch": "npm run compile && node_modules/brunch/bin/brunch watch --server",
"compile": "node node_modules/brunch/bin/brunch build",
"compile_production": "node node_modules/brunch/bin/brunch build --production",
"dev": "nodemon index.js",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
"dev": "nodemon index.mjs",
"prepare": "npm run snyk-protect",
"snyk-protect": "snyk-protect"
},
"repository": {
"type": "git",
"url": "https://github.com/cblanc/sws_gathers"
"url": "https://github.com/ENSL/ensl_gathers"
},
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/cblanc/sws_gathers/issues"
"url": "https://github.com/ENSL/ensl_gathers/issues"
},
"homepage": "https://github.com/cblanc/sws_gathers",
"homepage": "https://github.com/ENSL/ensl_gathers",
"dependencies": {
"@snyk/protect": "^1.1111.0",
"async": "~1.4.0",
"bootstrap": "~4.0.0",
"bootstrap": "^4.0.0",
"bootstrap-solarized": "~1.0.2",
"brunch": "~3.0.0",
"clean-css-brunch": ">=3.0.0",
@ -39,8 +40,8 @@
"cors": "~2.7.1",
"css-brunch": ">= 1.0 < 1.8",
"discord.js": "^11.1.0",
"express": "~4.16.0",
"express-handlebars": "~3.0.0",
"express": "^4.16.0",
"express-handlebars": "^7.0.1",
"extend": "~3.0.0",
"howler": "~1.1.28",
"javascript-brunch": ">= 1.0 < 1.8",
@ -48,21 +49,18 @@
"jquery": "~3.5.0",
"lodash": "~4.17.20",
"marshal": "^0.5.2",
"moment": "~2.11.2",
"mongoose": "~5.7.5",
"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",
"react-dom": "~16.0.1",
"react-emoji": "~0.4.1",
"react-autolink": "^0.2.1",
"react-dom": "^16.0.1",
"react-emoji": "^0.4.1",
"request": "~2.88.0",
"serve-favicon": "~2.4.5",
"snyk": "^1.316.1",
"socket.io": "~2.1.1",
"socket.io-client": "~2.1.1",
"steam": "1.4.0",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"steamidconvert": "~0.2.4",
"toastr": "~2.1.4",
"winston": "~1.0.1"
@ -82,13 +80,13 @@
"@types/winston": "^2.4.4",
"babel-brunch": "^7.0.1",
"chai": "~3.1.0",
"mocha": "~2.2.5",
"nodemon": "~1.4.0",
"supertest": "~1.0.1",
"mocha": "^10.2.0",
"nodemon": "^2.0.21",
"supertest": "^6.3.3",
"terser-brunch": "^4.0.0"
},
"engines": {
"node": "^10.13.0"
"node": ">=10.13.0"
},
"snyk": true
}
}