mirror of
https://github.com/ENSL/ensl_gathers.git
synced 2024-11-22 04:31:03 +00:00
Merge pull request #4 from ENSL/fix/hive-stats-from-steam
Hive Stats from Steam + Novice Gather
This commit is contained in:
commit
93b0e6b8f7
67 changed files with 7674 additions and 10137 deletions
25
.devcontainer/Dockerfile
Normal file
25
.devcontainer/Dockerfile
Normal 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>"
|
||||
|
||||
|
||||
|
31
.devcontainer/devcontainer.json
Normal file
31
.devcontainer/devcontainer.json
Normal 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"
|
||||
}
|
36
.devcontainer/docker-compose.yml
Normal file
36
.devcontainer/docker-compose.yml
Normal 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:
|
|
@ -24,4 +24,4 @@ RUN /bin/cp -r ./public /home/web/tmp/public && \
|
|||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
CMD ["node", "index.mjs"]
|
||||
|
|
1
Procfile
1
Procfile
|
@ -1 +0,0 @@
|
|||
web: npm run start_production
|
|
@ -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>
|
||||
<AssumeUserIdButton socket={socket}
|
||||
</button>
|
||||
<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>
|
||||
<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">
|
||||
|
|
|
@ -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"> </i> Github
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://steamcommunity.com/id/nslgathers" target="_blank">
|
||||
<i className="fa fa-steam-square"> </i> Steam Bot
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.ensl.org/gatherre" target="_blank">
|
||||
<i className="fa fa-legal"> </i> Gather Rules
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/messages" target="_blank">
|
||||
<i className="fa fa-comments"> </i> Message Archive
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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} Muted
|
||||
</a>
|
||||
</a>
|
||||
</li>;
|
||||
} else {
|
||||
mutedIcon = <i className="fa fa-volume-up fa-fw"></i>;
|
||||
mutedButton = <li>
|
||||
<a href="#" onClick={this.mute}>
|
||||
{mutedIcon} 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> Play
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='#' onClick={this.stop}>
|
||||
<i className="fa fa-stop"></i> Stop
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
<hr />
|
||||
<li>
|
||||
|
|
|
@ -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} />
|
||||
{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>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,4 +6,4 @@ if [ -f "/home/web/tmp/.updatePublic" ]; then
|
|||
rm -f /home/web/tmp/.updatePublic
|
||||
fi
|
||||
|
||||
node index.js
|
||||
node index.mjs
|
||||
|
|
|
@ -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
24
config/config.mjs
Normal 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
129
config/data/hive_stats.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
15
config/environments/development.mjs
Normal file
15
config/environments/development.mjs
Normal 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;
|
|
@ -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;
|
15
config/environments/production.mjs
Normal file
15
config/environments/production.mjs
Normal 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;
|
|
@ -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;
|
15
config/environments/staging.mjs
Normal file
15
config/environments/staging.mjs
Normal 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;
|
|
@ -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;
|
|
@ -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
32
config/express.mjs
Normal 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');
|
||||
};
|
|
@ -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) => {
|
|
@ -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) {
|
|
@ -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";
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
15
db/models/session.mjs
Normal 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);
|
|
@ -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
|
43
index.js
43
index.js
|
@ -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
34
index.mjs
Normal 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
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
};
|
|
@ -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
118
lib/ensl/client.mjs
Normal 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;
|
|
@ -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", {
|
|
@ -1,3 +0,0 @@
|
|||
const EventEmitter = require("events").EventEmitter;
|
||||
|
||||
module.exports = new EventEmitter();
|
5
lib/event/pubsub.mjs
Normal file
5
lib/event/pubsub.mjs
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { EventEmitter } from "events";
|
||||
|
||||
const pubsub = new EventEmitter()
|
||||
|
||||
export default pubsub;
|
|
@ -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;
|
|
@ -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
444
lib/gather/gather.mjs
Normal 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;
|
|
@ -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;
|
|
@ -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
67
lib/gather/gatherer.mjs
Normal 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;
|
|
@ -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
|
||||
};
|
|
@ -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;
|
29
lib/gather/invitational_gather.mjs
Normal file
29
lib/gather/invitational_gather.mjs
Normal 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;
|
|
@ -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
29
lib/gather/map.mjs
Normal 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;
|
|
@ -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
30
lib/gather/server.mjs
Normal 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;
|
||||
|
|
@ -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
41
lib/hive/client.mjs
Normal 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;
|
|
@ -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;
|
28
lib/hive/stats_wrapper.mjs
Normal file
28
lib/hive/stats_wrapper.mjs
Normal 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;
|
|
@ -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;
|
||||
};
|
|
@ -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(); });
|
||||
});
|
||||
};
|
|
@ -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
|
||||
};
|
124
lib/user/user.js
124
lib/user/user.js
|
@ -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
125
lib/user/user.mjs
Normal 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;
|
24
newrelic.js
24
newrelic.js
|
@ -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
14572
package-lock.json
generated
File diff suppressed because it is too large
Load diff
54
package.json
54
package.json
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue