From f15aec00b50a04c2fa883e2d654549ab77c4772f Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 00:40:47 +0200 Subject: [PATCH 01/42] Better project structure - Controllers and middlewares separated from route declaration - es6 imports & Babel to transpile everything - eslint to have a beautiful code - ez installer --- .babelrc | 16 + .eslintrc | 3 + .gitignore | 49 +- API.js | 255 -- EventHandler.js | 22 - README.md | 9 + Server.js | 107 - installer.js | 19 + package-lock.json | 989 ----- package.json | 23 +- DatabaseHandler.js => src/DatabaseHandler.js | 138 +- Dispatcher.js => src/Dispatcher.js | 62 +- src/EventHandler.js | 20 + Logger.js => src/Logger.js | 27 +- Utils.js => src/Utils.js | 39 +- VoiceServer.js => src/VoiceServer.js | 47 +- src/api.js | 41 + src/controllers/core.js | 19 + src/controllers/invites.js | 56 + src/controllers/messages.js | 36 + src/controllers/servers.js | 73 + src/controllers/users.js | 23 + src/index.js | 16 + src/middlewares/authorize.js | 18 + src/middlewares/cors.js | 8 + src/middlewares/serverExists.js | 19 + src/socket.js | 84 + yarn.lock | 4088 ++++++++++++++++++ 28 files changed, 4718 insertions(+), 1588 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintrc delete mode 100644 API.js delete mode 100644 EventHandler.js delete mode 100644 Server.js create mode 100644 installer.js delete mode 100644 package-lock.json rename DatabaseHandler.js => src/DatabaseHandler.js (77%) rename Dispatcher.js => src/Dispatcher.js (81%) create mode 100644 src/EventHandler.js rename Logger.js => src/Logger.js (56%) rename Utils.js => src/Utils.js (78%) rename VoiceServer.js => src/VoiceServer.js (54%) create mode 100644 src/api.js create mode 100644 src/controllers/core.js create mode 100644 src/controllers/invites.js create mode 100644 src/controllers/messages.js create mode 100644 src/controllers/servers.js create mode 100644 src/controllers/users.js create mode 100644 src/index.js create mode 100644 src/middlewares/authorize.js create mode 100644 src/middlewares/cors.js create mode 100644 src/middlewares/serverExists.js create mode 100644 src/socket.js create mode 100644 yarn.lock diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..4c6fde6 --- /dev/null +++ b/.babelrc @@ -0,0 +1,16 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ] + ], + "plugins": [ + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-proposal-class-properties" + ] +} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0741462 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "standard" +} diff --git a/.gitignore b/.gitignore index 7e43366..3f1ed48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,21 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output +# Jetbrains +.idea -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories +# The petabyte node_modules -jspm_packages -# Optional npm cache directory -.npm +# Logs +logs +*.log -# Optional REPL history -.node_repl_history +# Built app +build +dist +/index.html +# Config & certs Configuration.json -.vs/ +*.pem + +# My stuff +_work diff --git a/API.js b/API.js deleted file mode 100644 index 70a3143..0000000 --- a/API.js +++ /dev/null @@ -1,255 +0,0 @@ -const config = require('./Configuration.json'); -const { randomBytes } = require('crypto'); -const { deduplicate, filter, getClientsById, remove } = require('./Utils.js'); -const { addMemberToServer, authenticate, createUser, createServer, deleteServer, generateInvite, getMember, getInvite, - getOwnedServers, getServer, isInServer, ownsServer, removeMemberFromServer, serverExists, storeMessage } = require('./DatabaseHandler.js'); -const { dispatchMessage, dispatchMember, dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } = require('./Dispatcher.js'); -const express = require('express'); -const bodyParser = require('body-parser'); -const api = express.Router(); - -api.use(bodyParser.json()); -api.use(bodyParser.urlencoded({ extended: true })); -api.use(allowCORS); -//api.use(notFound); - -api.get('/', (req, res) => { - res.send('Welcome to the Union API!'); - // TODO: Serve docs or something -}); - -api.get('/info', (req, res) => { - res.json({ - api_version: 1, - websocket: config.ws.port, - voice: config.voicews.port, - app_settings: { - max_servers: config.rules.maxServersPerUser, - max_message_characters: config.rules.messageCharacterLimit, - max_username_characters: config.rules.usernameCharacterLimit - } - }); -}); - -api.patch('/self', (req, res) => { - res.send('Not done.'); - // TODO -}); - -api.post('/create', async (req, res) => { - const { username, password } = req.body; - - if (!username || 0 === username.trim().length) { - return res.status(400).json({ 'error': 'Username cannot be empty.' }); - } - - if (username.trim().length > config.rules.usernameCharacterLimit) { - return res.status(400).json({ 'error': `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }); - } - - if (!password || 5 > password.length) { - return res.status(400).json({ 'error': 'Password cannot be empty and must be 5 characters long or more.' }); - } - - const created = await createUser(username.trim(), password); - - if (created) { - return res.status(200).send('Account created! You may now login.'); // todo: json - } else { - return res.status(200).send('Unable to create account (it may already exist!)'); // also json here - } -}); - -api.post('/server', authorize, async (req, res) => { // this feels so inconsistent lul - const { name, iconUrl } = req.body; - - if (!name || 0 === name.trim().length) { - return res.status(400).json({ 'error': 'Server name cannot be empty.' }); - } - - if (await getOwnedServers(req.user.id) >= config.rules.maxServersPerUser) { - return res.status(400).json({ 'error': `You cannot create more than ${config.rules.maxServersPerUser} servers` }); - } - - const server = await createServer(name, iconUrl, req.user.id); - - res.status(200).send(); - - const clients = getClientsById(global.server.clients, req.user.id); - - if (0 < clients.length) { - clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, server.id)); - dispatchServerJoin(clients, server); - } -}); - - -api.delete('/server/:serverId', validateServer, authorize, async (req, res) => { - const { serverId } = req; - - if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ 'error': 'You can only delete servers that you own.' }); - } - - await deleteServer(serverId); - - res.status(200).send(); - - const clients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)); - - if (0 < clients.length) { - clients.forEach(ws => remove(ws.user.servers, serverId)); - dispatchServerLeave(clients, serverId); - } -}); - - -api.post('/server/:serverId/invite', validateServer, authorize, async (req, res) => { - const { serverId } = req; - - if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ 'error': 'Only the server owner can generate invites.' }); - } - - const inviteCode = await generateInvite(serverId, req.user.id); - - res.status(200).send({ code: inviteCode }); -}); - - -api.post('/server/:serverId/messages', validateServer, authorize, async (req, res) => { - const { serverId } = req; - const { content } = req.body; - - if (!req.user.servers.includes(serverId)) { - return res.status(400).json({ 'error': 'You cannot send messages to this server' }); - } - - if (!content || 0 === content.trim().length) { - return res.status(400).json({ 'error': 'Content must be a string and not empty' }); - } - - if (content.length > config.rules.messageCharacterLimit) { - return res.status(400).json({ 'error': `Content cannot exceed ${config.rules.messageCharacterLimit} characters` }); - } - - const id = randomBytes(15).toString('hex'); - storeMessage(id, req.user.id); - - const message = { - id, - server: serverId, - content: content.trim(), - author: req.user.id, - createdAt: Date.now() - }; - - const recipients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)); - dispatchMessage(recipients, message); - res.status(200).send(); -}); - - -api.post('/invites/:inviteId', authorize, async (req, res) => { - const invite = await getInvite(req.params.inviteId); - - if (!invite) { - return res.status(404).json({ 'error': 'Unknown invite' }); - } - - if (!await serverExists(invite.serverId)) { - return res.status(404).json({ 'error': 'Unknown server; invite expired' }); - } - - const { serverId } = invite; - - if (await isInServer(req.user.id, serverId)) { - return res.status(200).send(); - } - - await addMemberToServer(req.user.id, serverId); - - const clients = await getClientsById(global.server.clients, req.user.id); - const server = await getServer(serverId); - - if (0 < clients.length) { - clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, serverId)); - dispatchServerJoin(clients, server); - } - - const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id); - const member = await getMember(req.user.id); - - if (0 < members.length) { - dispatchMember(members, serverId, member); - } - - res.status(200).send(); -}); - - -api.delete('/self/server/:serverId', validateServer, authorize, async (req, res) => { - const { serverId } = req; - - if (!await isInServer(req.user.id, serverId)) { - return res.status(400).json({ 'error': 'You are not in that server' }); - } - - await removeMemberFromServer(req.user.id, serverId); - - const self = await getClientsById(global.server.clients, req.user.id); - const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)); - - if (0 < members.length) { - dispatchMemberLeave(members, req.user.id, serverId); - } - - if (0 < self.length) { - self.forEach(ws => remove(ws.user.servers, serverId)); - } - - res.status(200).send(); -}); - -/** - * Validates the 'authorization' header and populates req.user - */ -async function authorize (req, res, next) { - const user = await authenticate(req.headers.authorization); - - if (!user) { - return res.status(401).json({ 'error': 'Unauthorized: You must be logged in to access this route.' }); - } - - req.user = user; - next(); -} - - -/** - * Ensures a server matching 'serverId' exists - */ -async function validateServer (req, res, next) { - const { serverId } = req.params; - const sid = Number(serverId); - - if (!await serverExists(sid)) { - return res.status(400).json({ 'error': 'Unknown server' }); - } - - req.serverId = sid; - next(); -} - - -/** - * Middleware to allow CORS - */ -function allowCORS (req, res, next) { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); -} - - -module.exports = api; diff --git a/EventHandler.js b/EventHandler.js deleted file mode 100644 index d471477..0000000 --- a/EventHandler.js +++ /dev/null @@ -1,22 +0,0 @@ -const { getSessionsOf } = require('./Utils.js'); -const { dispatchPresenceUpdate } = require('./Dispatcher.js'); -const { getUser, updatePresenceOf } = require('./DatabaseHandler.js'); - - -async function handlePresenceUpdate (userId, clients) { - const { online } = await getUser(userId); - const sessions = getSessionsOf(userId, clients); - - const newState = 0 < sessions; - const shouldUpdate = newState !== online; - - if (shouldUpdate) { - updatePresenceOf(userId, newState); - dispatchPresenceUpdate(clients, userId, newState); - } -} - - -module.exports = { - handlePresenceUpdate -}; diff --git a/README.md b/README.md index 96aa0a8..a09cf4e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # Union A light chat service + +## Installation + +To build and install everything that Union needs to work, just do `yarn run install` +and `yarn run start` to start the whole thing + +`yarn run install` will create the database or migrate it if it exists, build the app, +download web interface from https://github.com/Union-Chat/union-react and generate +a default `Confuguration.json` diff --git a/Server.js b/Server.js deleted file mode 100644 index e7d1624..0000000 --- a/Server.js +++ /dev/null @@ -1,107 +0,0 @@ -const config = require('./Configuration.json'); -const fs = require('fs'); - -/* Server middleware */ -const { authenticate, resetPresenceStates } = require('./DatabaseHandler.js'); -const { dispatchHello } = require('./Dispatcher.js'); -const { handlePresenceUpdate } = require('./EventHandler.js'); -const logger = require('./Logger.js'); - -/* Server */ -const https = require('https'); -const WebSocket = require('ws'); -const express = require('express'); -const bodyParser = require('body-parser'); -const api = require('./API.js'); - -/* Apps */ -const wss = https.createServer({ - cert: fs.readFileSync(config.ws.certPath), - key: fs.readFileSync(config.ws.keyPath), -}); -const app = express.Router(); -const server = new WebSocket.Server({ server: wss }); -global.server = server; - - -server.on('connection', async (client, req) => { - if (!req.headers.authorization) { - setTimeout(() => { - if (!client.user && client.readyState !== WebSocket.CLOSED) { // Not logged in - client.close(4001, 'Unauthorized: Invalid credentials'); - } - }, 30e3); // 30 second grace period to login - } else { - checkLogin(client, req.headers.authorization); - } - - client.on('message', (data) => { - if ('string' === typeof data && data.startsWith('Basic ') && !client.isAuthenticated) { - checkLogin(client, data); - } - }); - client.on('error', (error) => logger.warn('Client encountered an error\n\tisAuthenticated: {0}\n\tUser: {1}\n\tError: {2}', client.isAuthenticated, client.user, error)); - client.on('close', () => client.user && handlePresenceUpdate(client.user.id, server.clients)); - client.on('pong', () => client.isAlive = true); -}); - - -async function checkLogin (client, data) { - const user = await authenticate(data); - - if (!user) { - return client.close(4001, 'Unauthorized: Invalid credentials'); - } - - logger.info('Client connected\n\t{0}\n\t{1} clients connected', user.id, server.clients.size); - client._un = user.id; - client.user = user; - client.isAlive = true; - client.isAuthenticated = true; - - await dispatchHello(client); - handlePresenceUpdate(client.user.id, server.clients); -} - - -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(express.static(`${__dirname}/views`)); -app.use('/api', api); -app.use(allowCORS); - -function allowCORS (req, res, next) { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); -} - - -wss.listen(config.ws.port, () => { - logger.info('[WS] Server started on port {0}', config.ws.port); - setInterval(() => { - server.clients.forEach(ws => { - if (ws.readyState === WebSocket.CLOSING || ws.readyState === WebSocket.CLOSED) { - return; - } - - if (!ws.isAlive && ws.isAuthenticated) { - logger.info('Client disconnected\n\t{0}\n\t{1} clients connected', ws._un, server.clients.size - 1); - handlePresenceUpdate(ws.user.id, server.clients); - return ws.terminate(); - } - - ws.isAlive = false; - ws.ping(); - }); - }, 10e3); -}); - -process.on('SIGINT', async () => { - server.clients.forEach(ws => ws.close(1000)); - await resetPresenceStates(); - - process.exit(); -}); - -exports.router = app; \ No newline at end of file diff --git a/installer.js b/installer.js new file mode 100644 index 0000000..36e4439 --- /dev/null +++ b/installer.js @@ -0,0 +1,19 @@ +const { execSync } = require('child_process') + +console.log('Union installer v1.0.0') + +console.log('Migrating database...') +// @todo + +console.log('Downloading web interface...') +execSync('git clone https://github.com/Union-Chat/union-react') + +console.log('Building web interface...') +execSync('cd union-react && yarn && yarn run prod && cd ..') + +console.log('Cleaning up...') +execSync('mv union-react/index.html index.html') +execSync('mv union-react/dist dist') +execSync('rm -rf union-react') + +console.log('Success! Union is ready to work. Just do `yarn run start`') diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 3f9b7b1..0000000 --- a/package-lock.json +++ /dev/null @@ -1,989 +0,0 @@ -{ - "name": "Union", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "bcrypt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.0.tgz", - "integrity": "sha512-gjicxsD4e5U3nH0EqiEb5y+fKpsZ7F52wcnmNfu45nxnolWVAYh7NgbdfilY+5x1v6cLspxmzz4hf+ju2pFxhA==", - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.2" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "minipass": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", - "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } - } - }, - "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "needle": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", - "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz", - "integrity": "sha512-16lql9QTqs6KsB9fl3neWyZm02KxIKdI9FlJjrB0y7eMTP5Nyz+xalwPbOlw3iw7EejllJPmlJSnY711PLD1ug==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" - }, - "npm-packlist": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "readable-stream": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", - "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", - "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.3", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - } - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - } - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - } - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" - }, - "nanoid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.1.0.tgz", - "integrity": "sha512-iOCqgXieGrk8/wDt1n9rZS2KB1dYVssemY0NTWjfzVr+1t1gAmdTp1u2+YHppKro3Bk5S+Gs+xmYCfpuXauYXQ==" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - } - } - }, - "rethinkdbdash": { - "version": "2.3.31", - "resolved": "https://registry.npmjs.org/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz", - "integrity": "sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ==", - "requires": { - "bluebird": ">= 3.0.1" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, - "shortid": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.12.tgz", - "integrity": "sha512-sw0knB/ioTu/jVYgJz1IP1b5uhPZtZYwQ9ir/EqXZHI4+Jh8rzzGLM3LKptGHBKoDsgTBDfr4yCRNUX7hEIksQ==", - "requires": { - "nanoid": "^1.0.7" - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "ws": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", - "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } -} diff --git a/package.json b/package.json index 8bb0284..8d0cabd 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,14 @@ "name": "Union", "version": "1.0.0", "description": "Lightweight chat service built with C# and NodeJS", - "main": "Server.js", + "main": "build/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node Server.js" + "lint": "eslint **/*.js --ignore-pattern build/*", + "setup": "yarn run build && node installer.js", + "start": "node build/index.js", + "build": "babel src -d build --copy-files", + "dev": "concurrently -k -n \"Babel,Node\" -p \"[{name}]\" -c \"yellow,green\" \"babel src -w -d build --copy-files\" \"nodemon build/index.js\"" }, "keywords": [], "author": "Kromatic", @@ -17,5 +21,20 @@ "rethinkdbdash": "^2.3.31", "shortid": "^2.2.12", "ws": "^6.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "concurrently": "^4.0.1", + "eslint": "^5.6.1", + "eslint-config-standard": "^12.0.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-node": "^7.0.1", + "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-standard": "^4.0.0", + "nodemon": "^1.18.4" } } diff --git a/DatabaseHandler.js b/src/DatabaseHandler.js similarity index 77% rename from DatabaseHandler.js rename to src/DatabaseHandler.js index 40f61d1..d7be843 100644 --- a/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -1,9 +1,8 @@ -const { hash, compare } = require('bcrypt'); -const shortId = require('shortid'); +const { hash, compare } = require('bcrypt') +const shortId = require('shortid') const r = require('rethinkdbdash')({ db: 'union' -}); - +}) /** * Creates a user object with the provided username and password, and stores it in the DB @@ -12,10 +11,10 @@ const r = require('rethinkdbdash')({ * @returns {Boolean} Whether the account was created or not */ async function createUser (username, password) { - const account = await r.table('users').get(username); + const account = await r.table('users').get(username) - if (null !== account) { - return false; + if (account !== null) { + return false } else { await r.table('users').insert({ id: username, @@ -23,13 +22,12 @@ async function createUser (username, password) { createdAt: Date.now(), servers: [], online: false - }); + }) - return true; + return true } } - /** * Creates a server with the provided name and iconUrl * @param {String} name The name of the server @@ -37,41 +35,39 @@ async function createUser (username, password) { * @returns {Object} The created server */ async function createServer (name, iconUrl, owner) { - const largestId = await r.table('servers').max('id'); - const id = largestId.id + 1; + const largestId = await r.table('servers').max('id') + const id = largestId.id + 1 const server = { name, iconUrl, owner, id - }; + } - await r.table('servers').insert(server); - await addMemberToServer(owner, id); - return getServer(id); + await r.table('servers').insert(server) + await addMemberToServer(owner, id) + return getServer(id) } - /** * Adds a member to a server * @param {String} username The member to add to the server * @param {Number} id The server to add the member to */ async function addMemberToServer (username, id) { - const user = await r.table('users').get(username); - const server = await r.table('servers').get(id); + const user = await r.table('users').get(username) + const server = await r.table('servers').get(id) if (!user || !server || user.servers.includes(id)) { - return; + return } await r.table('users').get(username).update({ servers: r.row('servers').append(id) - }); + }) } - /** * Validates username and password from the provided auth * @param {String} auth The authentication type + base64-encoded credentials @@ -79,47 +75,45 @@ async function addMemberToServer (username, id) { */ async function authenticate (auth) { if (!auth) { - return null; + return null } - const [type, creds] = auth.split(' '); + const [type, creds] = auth.split(' ') - if ('Basic' !== type || !creds) { - return null; + if (type !== 'Basic' || !creds) { + return null } - const [username, password] = Buffer.from(creds, 'base64').toString().split(':'); + const [username, password] = Buffer.from(creds, 'base64').toString().split(':') if (!username || !password) { - return null; + return null } - const user = await r.table('users').get(username); + const user = await r.table('users').get(username) if (!user) { - return null; + return null } - const isPasswordValid = await compare(password, user.password); + const isPasswordValid = await compare(password, user.password) if (!isPasswordValid) { - return null; + return null } - return user; + return user } - /** * Retrieves a list of users in the server with the provided serverId * @param {Number} serverId The user to get the servers of * @returns {Array} A list of users in the server */ function getUsersInServer (serverId) { - return r.table('users').filter(u => u('servers').contains(serverId)).without(['servers', 'password']); + return r.table('users').filter(u => u('servers').contains(serverId)).without(['servers', 'password']) } - /** * Checks whether a user is in a server * @param {String} userId The user to check the servers of @@ -127,70 +121,62 @@ function getUsersInServer (serverId) { * @returns {Boolean} Whether the user is in the server */ function isInServer (userId, serverId) { - return r.table('users').get(userId)('servers').contains(serverId).default(false); + return r.table('users').get(userId)('servers').contains(serverId).default(false) } - /** * Gets a list of servers that the given user is in * @param {String} username Username of the user to retrieve the servers of - * @returns {Array} A list of servers that the user is in + * @returns {Promise>} A list of servers that the user is in */ async function getServersOfUser (username) { - const user = await r.table('users').get(username); + const user = await r.table('users').get(username) if (!user) { - return []; // This shouldn't happen but you can never be too careful + return [] // This shouldn't happen but you can never be too careful } - const servers = await r.table('servers') + return r.table('servers') .getAll(...user.servers) .merge(server => ({ members: r.table('users').filter(u => u('servers').contains(server('id'))).without(['servers', 'password']).coerceTo('array') - })); - - return servers; + })) } - /** * Updates the online status of the given user * @param {String} username Username of the user to update the presence of * @param {Boolean} online Whether the user is online or not */ function updatePresenceOf (username, online) { - r.table('users').get(username).update({ online }).run(); + r.table('users').get(username).update({ online }).run() } - /** * Resets the online status of all members. Useful when the server is shutting down */ function resetPresenceStates () { - return r.table('users').update({ online: false }); + return r.table('users').update({ online: false }) } - /** * Updates the online status of the given user * @param {String} username Username of the user to update the presence of * @param {Boolean} online Whether the user is online or not */ function getUser (username) { - return r.table('users').get(username); + return r.table('users').get(username) } - /** * Retrieves a user without private properties * @param {String} username The name of the user to retrieve * @returns {Object|Null} The user, if they exist */ function getMember (username) { - return r.table('users').get(username).without(['password', 'servers']); + return r.table('users').get(username).without(['password', 'servers']) } - /** * Removes a member from a server * @param {String} username The name of the user to kick from the server @@ -201,20 +187,18 @@ function removeMemberFromServer (username, serverId) { .get(username) .update({ servers: r.row('servers').difference([serverId]) - }); + }) } - /** * Returns the number of servers that the given user owns * @param {String} username The username to filter servers against * @returns {Number} The amount of servers the user owns */ function getOwnedServers (username) { - return r.table('servers').filter(s => s('owner').eq(username)).count(); + return r.table('servers').filter(s => s('owner').eq(username)).count() } - /** * Retrieves a server from the database by its ID * @param {Number} serverId The ID of the server to retrieve @@ -225,26 +209,24 @@ function getServer (serverId) { .get(serverId) .merge(server => ({ members: r.table('users').filter(u => u('servers').contains(server('id'))).without(['servers', 'password']).coerceTo('array') - })); + })) } - /** * Deletes a server by its ID * @param {Number} serverId The ID of the server to delete */ async function deleteServer (serverId) { - await r.table('servers').get(serverId).delete(); - await r.table('invites').filter(inv => inv('serverId').eq(serverId)).delete(); + await r.table('servers').get(serverId).delete() + await r.table('invites').filter(inv => inv('serverId').eq(serverId)).delete() await r.table('users') .filter(u => u('servers').contains(serverId)) .update({ servers: r.row('servers').difference([serverId]) - }); + }) } - /** * Checks if the given user is the owner of the given server * @param {String} username The name of the user to check @@ -252,10 +234,9 @@ async function deleteServer (serverId) { * @returns {Boolean} Whether or not the user owns the server */ function ownsServer (username, serverId) { - return r.table('servers').get(serverId)('owner').eq(username).default(false); + return r.table('servers').get(serverId)('owner').eq(username).default(false) } - /** * Checks whether the server exists * @param {Number} serverId The id of the server to check @@ -263,13 +244,12 @@ function ownsServer (username, serverId) { */ function serverExists (serverId) { if (!serverId) { - return false; + return false } - return r.table('servers').filter({ id: serverId }).count().eq(1); + return r.table('servers').filter({ id: serverId }).count().eq(1) } - /** * Generates an invite for the specified server * @param {Number} serverId The server ID to associate the invite with @@ -277,38 +257,34 @@ function serverExists (serverId) { * @returns {String} The invite code */ async function generateInvite (serverId, inviter) { - const invite = shortId(); + const invite = shortId() await r.table('invites').insert({ id: invite, serverId, inviter - }); + }) - return invite; + return invite } - /** * Returns an invite object from the provided code * @param {String} code The code to lookup * @returns {Object|Null} The invite, if it exists */ function getInvite (code) { - return r.table('invites').get(code); + return r.table('invites').get(code) } - function storeMessage (id, author) { - r.table('messages').insert({ id, author }).run(); + r.table('messages').insert({ id, author }).run() } - function retrieveMessage (id) { - return r.table('messages').get(id); + return r.table('messages').get(id) } - module.exports = { addMemberToServer, authenticate, @@ -330,5 +306,5 @@ module.exports = { retrieveMessage, serverExists, storeMessage, - updatePresenceOf, -}; + updatePresenceOf +} diff --git a/Dispatcher.js b/src/Dispatcher.js similarity index 81% rename from Dispatcher.js rename to src/Dispatcher.js index 8e86220..f83024a 100644 --- a/Dispatcher.js +++ b/src/Dispatcher.js @@ -1,24 +1,22 @@ -const OPCODES = require('./OpCodes.json'); -const { getServersOfUser } = require('./DatabaseHandler.js'); -const logger = require('./Logger.js'); -const WebSocket = require('ws'); - +const OPCODES = require('../OpCodes.json') +const { getServersOfUser } = require('./DatabaseHandler.js') +const logger = require('./Logger.js') +const WebSocket = require('ws') /** * Dispatch a HELLO payload to a client * @param {WebSocket} client The client to dispatch to */ async function dispatchHello (client) { - const servers = await getServersOfUser(client.user.id); + const servers = await getServersOfUser(client.user.id) const payload = { op: OPCODES.Hello, d: servers - }; + } - send([client], payload); + send([client], payload) } - /** * Dispatch a presence update to all connected clients * @param {Set} clients The clients to dispatch the payload to @@ -32,12 +30,11 @@ function dispatchPresenceUpdate (clients, userId, status) { id: userId, status } - }; + } - send(clients, payload); + send(clients, payload) } - /** * Dispatches a user message to the provided clients * @param {Set|WebSocket[]} clients The clients to dispatch the message to @@ -47,11 +44,10 @@ function dispatchMessage (clients, message) { const payload = { op: OPCODES.DispatchMessage, d: message - }; - send(clients, payload); + } + send(clients, payload) } - /** * Dispatches a member object to the provided clients * @param {Set|WebSocket[]} clients The clients to dispatch the member to @@ -65,11 +61,10 @@ function dispatchMember (clients, serverId, member) { server: serverId, member } - }; - send(clients, payload); + } + send(clients, payload) } - /** * Dispatches a member leave payload to the provided clients * @param {Set|WebSocket[]} clients The clients to dispatch the payload to @@ -84,11 +79,10 @@ function dispatchMemberLeave (clients, username, serverId) { server: serverId, kicked: false // soon } - }; - send(clients, payload); + } + send(clients, payload) } - /** * Dispatches a list of members to the given client * @param {WebSocket} client The client to dispatch the members to @@ -98,11 +92,10 @@ function dispatchMembers (client, members) { const payload = { op: OPCODES.DispatchMembers, d: members - }; - send([client], payload); + } + send([client], payload) } - /** * Dispatches a guildJoin op to the given clients * @param {Set|WebSocket[]} clients The client to dispatch the event to @@ -112,12 +105,11 @@ function dispatchServerJoin (clients, server) { const payload = { op: OPCODES.DispatchServerJoin, d: server - }; + } - send(clients, payload); + send(clients, payload) } - /** * Dispatches a guildLeave op to the given clients * @param {Set|WebSocket[]} clients The client to dispatch the event to @@ -127,29 +119,27 @@ function dispatchServerLeave (clients, serverId) { const payload = { op: OPCODES.DispatchServerLeave, d: serverId - }; + } - send(clients, payload); + send(clients, payload) } - /** * Dispatch a payload to the provided clients * @param {Set|WebSocket[]} clients The clients to dispatch the payload to * @param {Object} payload The payload to send to the clients */ function send (clients, payload) { - logger.debug('Dispatching OP {0} to {1} clients', payload.op, clients.size || clients.length); - payload = JSON.stringify(payload); + logger.debug('Dispatching OP {0} to {1} clients', payload.op, clients.size || clients.length) + payload = JSON.stringify(payload) clients.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { - ws.send(payload); + ws.send(payload) } - }); + }) } - module.exports = { dispatchHello, dispatchPresenceUpdate, @@ -159,4 +149,4 @@ module.exports = { dispatchMembers, dispatchServerJoin, dispatchServerLeave -}; +} diff --git a/src/EventHandler.js b/src/EventHandler.js new file mode 100644 index 0000000..951c6f4 --- /dev/null +++ b/src/EventHandler.js @@ -0,0 +1,20 @@ +const { getSessionsOf } = require('./Utils.js') +const { dispatchPresenceUpdate } = require('./Dispatcher.js') +const { getUser, updatePresenceOf } = require('./DatabaseHandler.js') + +async function handlePresenceUpdate (userId, clients) { + const { online } = await getUser(userId) + const sessions = getSessionsOf(userId, clients) + + const newState = sessions > 0 + const shouldUpdate = newState !== online + + if (shouldUpdate) { + updatePresenceOf(userId, newState) + dispatchPresenceUpdate(clients, userId, newState) + } +} + +module.exports = { + handlePresenceUpdate +} diff --git a/Logger.js b/src/Logger.js similarity index 56% rename from Logger.js rename to src/Logger.js index c8e9d4b..a8a6670 100644 --- a/Logger.js +++ b/src/Logger.js @@ -1,5 +1,4 @@ -const formatterRegex = /(\{(\d+)\})+/gm; - +const formatterRegex = /({(\d+)})+/gm /** * Enforces two-digits on a number by prepending '0' where needed @@ -7,24 +6,22 @@ const formatterRegex = /(\{(\d+)\})+/gm; * @returns {String} The padded number */ function forceTwoDigits (number) { - return number.toString().padStart(2, '0'); + return number.toString().padStart(2, '0') } - /** * Formats the string with drop-in args * @param {String} content The string to format * @param {...Object} args The args to format the string with */ function formatString (content, ...args) { - let match; - while (null !== (match = formatterRegex.exec(content))) { - content = content.replace(match[0], args[Number(match[2])]); + let match + while ((match = formatterRegex.exec(content)) !== null) { + content = content.replace(match[0], args[Number(match[2])]) } - return content; + return content } - /** * Logs a message to console * @param {String} loglevel The severity of the message @@ -32,14 +29,14 @@ function formatString (content, ...args) { * @param {Array<*>} args Any additional arguments */ function log (loglevel, message, ...args) { - const date = new Date(); - const hour = forceTwoDigits(date.getHours()); - const min = forceTwoDigits(date.getMinutes()); - const sec = forceTwoDigits(date.getSeconds()); + const date = new Date() + const hour = forceTwoDigits(date.getHours()) + const min = forceTwoDigits(date.getMinutes()) + const sec = forceTwoDigits(date.getSeconds()) - console.log(`[${hour}:${min}:${sec}] [${loglevel.padEnd(5)}] ${formatString(message, ...args)}`); // eslint-disable-line + console.log(`[${hour}:${min}:${sec}] [${loglevel.padEnd(5)}] ${formatString(message, ...args)}`) // eslint-disable-line } for (const method of ['DEBUG', 'INFO', 'WARN', 'ERROR']) { - module.exports[method.toLowerCase()] = log.bind(null, method); + module.exports[method.toLowerCase()] = log.bind(null, method) } diff --git a/Utils.js b/src/Utils.js similarity index 78% rename from Utils.js rename to src/Utils.js index 9017726..6db4be3 100644 --- a/Utils.js +++ b/src/Utils.js @@ -5,23 +5,21 @@ * @returns {Array} The filtered elements */ function filter (set, expression) { - const results = []; - set.forEach(item => expression(item) && results.push(item)); - return results; + const results = [] + set.forEach(item => expression(item) && results.push(item)) + return results } - /** * Filters the given set by the given predicate and returns the first match, if any. * @param {Set} set The set to filter the elements of * @param {Function} expression The predicate to match elements against - * @returns {Any|Null} The first match, if any + * @returns {any|Null} The first match, if any */ function findFirst (set, expression) { - return filter(set, expression)[0]; + return filter(set, expression)[0] } - /** * Gets the first websocket client with the given user ID * @param {Set|WebSocket[]} clients The clients to filter @@ -29,23 +27,21 @@ function findFirst (set, expression) { * @returns {Array} The matching clients */ function getClientsById (clients, userId) { - return filter(clients, (ws) => ws.isAuthenticated && ws.user.id === userId); + return filter(clients, (ws) => ws.isAuthenticated && ws.user.id === userId) } - /** * Parses a string into an object and suppresses any errors thrown * @param {String} data The string to convert to an object */ function safeParse (data) { try { - return JSON.parse(data); + return JSON.parse(data) } catch (exception) { - return null; + return null } } - /** * De-duplicates an array by converting it to a set and back * @param {Array<*>} array The array to deduplicate @@ -53,36 +49,33 @@ function safeParse (data) { * @returns {Array<*>} The deduplicated array */ function deduplicate (array, ...append) { - array.push(...append); - const arraySet = new Set(array); - return [...arraySet.values()]; + array.push(...append) + const arraySet = new Set(array) + return [...arraySet.values()] } - /** * Removes an element from an array * @param {Array<*>} array The array to remove the item from * @param {*} item The item to remove */ function remove (array, item) { - const ind = array.indexOf(item); + const ind = array.indexOf(item) - if (-1 < ind) { - array.splice(ind, 1); + if (ind > -1) { + array.splice(ind, 1) } } - /** * Gets the number of sessions of the given user ID * @param {String} userId The user ID to get the sessions of * @param {Set} clients The clients to filter */ function getSessionsOf (userId, clients) { - return filter(clients, ws => ws.user && ws.user.id === userId).length; + return filter(clients, ws => ws.user && ws.user.id === userId).length } - module.exports = { deduplicate, filter, @@ -91,4 +84,4 @@ module.exports = { getClientsById, getSessionsOf, remove -}; +} diff --git a/VoiceServer.js b/src/VoiceServer.js similarity index 54% rename from VoiceServer.js rename to src/VoiceServer.js index 366558c..9994e10 100644 --- a/VoiceServer.js +++ b/src/VoiceServer.js @@ -1,56 +1,55 @@ -const config = require('./Configuration.json'); -const logger = require('./Logger.js'); -const fs = require('fs'); +const config = require('../Configuration.json') +const logger = require('./Logger.js') +const fs = require('fs') -const https = require('https'); -const WebSocket = require('ws'); +const https = require('https') +const WebSocket = require('ws') const wss = https.createServer({ cert: fs.readFileSync(config.ws.certPath), - key: fs.readFileSync(config.ws.keyPath), -}); -const voiceSocket = new WebSocket.Server({ server: wss }); -global.voiceServer = voiceSocket; - + key: fs.readFileSync(config.ws.keyPath) +}) +const voiceSocket = new WebSocket.Server({ server: wss }) +global.voiceServer = voiceSocket voiceSocket.on('connection', async (client, req) => { - client.clientId = generateClientId(); - logger.info('Connection from {0} (Assigned client-id {1})', client._socket.remoteAddress, client.clientId); + client.clientId = generateClientId() + logger.info('Connection from {0} (Assigned client-id {1})', client._socket.remoteAddress, client.clientId) client.on('message', (data) => { voiceSocket.clients.forEach(ws => { if (ws.readyState === WebSocket.OPEN && ws.clientId !== client.clientId) { ws.send(data, { binary: true - }); + }) } - }); - }); -}); + }) + }) +}) function generateClientId () { - let largestId = 1; + let largestId = 1 voiceSocket.clients.forEach(ws => { if (ws.clientId && ws.clientId > largestId) { - largestId = ws.clientId; + largestId = ws.clientId } - }); + }) - return largestId + 1; + return largestId + 1 } function start () { wss.listen(config.voicews.port, () => { - logger.info('[VWS] Voice WebSocket started on port {0}', config.voicews.port); - }); + logger.info('[VWS] Voice WebSocket started on port {0}', config.voicews.port) + }) } function shutdown () { - wss.close(); + wss.close() } module.exports = { start, shutdown -}; +} diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..a985765 --- /dev/null +++ b/src/api.js @@ -0,0 +1,41 @@ +import express from 'express' +import bodyParser from 'body-parser' + +import authorize from './middlewares/authorize' +import allowCORS from './middlewares/cors' +import validateServer from './middlewares/serverExists' + +import { home, info } from './controllers/core' +import { create as userCreate } from './controllers/users' +import { create as serverCreate, remove as serverRemove, leave as serverLeave } from './controllers/servers' +import { create as inviteCreate, accept as inviteAccept } from './controllers/invites' +import { post as messagePost } from './controllers/messages' + +const api = express.Router() + +// Middlewares +api.use(bodyParser.json()) +api.use(bodyParser.urlencoded({ extended: true })) +api.use(allowCORS) + +// Core +api.get('/', home) +api.get('/info', info) + +// User +api.post('/create', userCreate) +api.patch('/self', (req, res) => res.send('Not done.')) + +// Servers +api.post('/server', authorize, serverCreate) +api.delete('/server/:serverId', validateServer, authorize, serverRemove) +api.delete('/self/server/:serverId', validateServer, authorize, serverLeave) + +api.post('/server/:serverId/invite', validateServer, authorize, inviteCreate) + +api.post('/server/:serverId/messages', validateServer, authorize, messagePost) + +// Invites +api.post('/invites/:inviteId', authorize, inviteAccept) + +export default api diff --git a/src/controllers/core.js b/src/controllers/core.js new file mode 100644 index 0000000..cf6bd16 --- /dev/null +++ b/src/controllers/core.js @@ -0,0 +1,19 @@ +import config from '../../Configuration' + +export function home (req, res) { + res.send('Welcome to the Union API!') + // TODO: Serve docs or something +} + +export function info (req, res) { + res.send({ + api_version: 2, + websocket: config.ws.port, + voice: config.voicews.port, + app_settings: { + max_servers: config.rules.messageCharacterLimit, + max_message_characters: config.rules.messageCharacterLimit, + max_username_characters: config.rules.usernameCharacterLimit + } + }) +} diff --git a/src/controllers/invites.js b/src/controllers/invites.js new file mode 100644 index 0000000..1049a23 --- /dev/null +++ b/src/controllers/invites.js @@ -0,0 +1,56 @@ +import { + addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, + ownsServer +} from '../DatabaseHandler' +import { dispatchMember, dispatchServerJoin } from '../Dispatcher' +import { deduplicate, filter, getClientsById } from '../Utils' +import serverExists from '../middlewares/serverExists' + +export async function create (req, res) { + const { serverId } = req; + + if (!await ownsServer(req.user.id, serverId)) { + return res.status(403).json({ 'error': 'Only the server owner can generate invites.' }); + } + + const inviteCode = await generateInvite(serverId, req.user.id); + + res.status(200).send({ code: inviteCode }); +} + +export async function accept (req, res) { + const invite = await getInvite(req.params.inviteId); + + if (!invite) { + return res.status(404).json({ 'error': 'Unknown invite' }); + } + + if (!await serverExists(invite.serverId)) { + return res.status(404).json({ 'error': 'Unknown server; invite expired' }); + } + + const { serverId } = invite; + + if (await isInServer(req.user.id, serverId)) { + return res.status(200).send(); + } + + await addMemberToServer(req.user.id, serverId); + + const clients = await getClientsById(global.server.clients, req.user.id); + const server = await getServer(serverId); + + if (0 < clients.length) { + clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, serverId)); + dispatchServerJoin(clients, server); + } + + const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id); + const member = await getMember(req.user.id); + + if (0 < members.length) { + dispatchMember(members, serverId, member); + } + + res.status(200).send(); +} diff --git a/src/controllers/messages.js b/src/controllers/messages.js new file mode 100644 index 0000000..eb66b14 --- /dev/null +++ b/src/controllers/messages.js @@ -0,0 +1,36 @@ +import { randomBytes } from 'crypto' +import { storeMessage } from '../DatabaseHandler' +import { dispatchMessage } from '../Dispatcher' +import { filter } from '../Utils' + +export async function post (req, res) { + const { serverId } = req + const { content } = req.body + + if (!req.user.servers.includes(serverId)) { + return res.status(400).json({ 'error': 'You cannot send messages to this server' }) + } + + if (!content || 0 === content.trim().length) { + return res.status(400).json({ 'error': 'Content must be a string and not empty' }) + } + + if (content.length > config.rules.messageCharacterLimit) { + return res.status(400).json({ 'error': `Content cannot exceed ${config.rules.messageCharacterLimit} characters` }) + } + + const id = randomBytes(15).toString('hex') + storeMessage(id, req.user.id) + + const message = { + id, + server: serverId, + content: content.trim(), + author: req.user.id, + createdAt: Date.now() + } + + const recipients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + dispatchMessage(recipients, message) + res.status(200).send() +} diff --git a/src/controllers/servers.js b/src/controllers/servers.js new file mode 100644 index 0000000..3505d3f --- /dev/null +++ b/src/controllers/servers.js @@ -0,0 +1,73 @@ +import config from '../../Configuration' +import { + createServer, deleteServer, getOwnedServers, isInServer, ownsServer, + removeMemberFromServer +} from '../DatabaseHandler' +import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' +import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' + +export async function create (req, res) { // this feels so inconsistent lul + const { name, iconUrl } = req.body + + if (!name || 0 === name.trim().length) { + return res.status(400).json({ 'error': 'Server name cannot be empty.' }) + } + + if (await getOwnedServers(req.user.id) >= config.rules.maxServersPerUser) { + return res.status(400).json({ 'error': `You cannot create more than ${config.rules.maxServersPerUser} servers` }) + } + + const server = await createServer(name, iconUrl, req.user.id) + + res.status(200).send() + + const clients = getClientsById(global.server.clients, req.user.id) + + if (0 < clients.length) { + clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, server.id)) + dispatchServerJoin(clients, server) + } +} + +export async function leave (req, res) { + const { serverId } = req + + if (!await isInServer(req.user.id, serverId)) { + return res.status(400).json({ 'error': 'You are not in that server' }) + } + + await removeMemberFromServer(req.user.id, serverId) + dispatchServerLeave(getClientsById(global.server.clients, req.user.id), serverId) + + const self = await getClientsById(global.server.clients, req.user.id) + const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + + if (0 < members.length) { + dispatchMemberLeave(members, req.user.id, serverId) + } + + if (0 < self.length) { + self.forEach(ws => remove(ws.user.servers, serverId)) + } + + res.status(200).send() +} + +export async function remove (req, res) { + const { serverId } = req + + if (!await ownsServer(req.user.id, serverId)) { + return res.status(403).json({ 'error': 'You can only delete servers that you own.' }) + } + + await deleteServer(serverId) + + res.status(200).send() + + const clients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + + if (0 < clients.length) { + clients.forEach(ws => utilRemove(ws.user.servers, serverId)) + dispatchServerLeave(clients, serverId) + } +} diff --git a/src/controllers/users.js b/src/controllers/users.js new file mode 100644 index 0000000..b760be8 --- /dev/null +++ b/src/controllers/users.js @@ -0,0 +1,23 @@ +export async function create (req, res) { + const { username, password } = req.body + + if (!username || 0 === username.trim().length) { + return res.status(400).json({ 'error': 'Username cannot be empty.' }) + } + + if (username.trim().length > config.rules.usernameCharacterLimit) { + return res.status(400).json({ 'error': `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) + } + + if (!password || 5 > password.length) { + return res.status(400).json({ 'error': 'Password cannot be empty and must be 5 characters long or more.' }) + } + + const created = await createUser(username.trim(), password) + + if (created) { + return res.status(200).send('Account created! You may now login.') // todo: json + } else { + return res.status(200).send('Unable to create account (it may already exist!)') // also json here + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..1741cb7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,16 @@ +import config from '../Configuration' + +import path from 'path' +import express from 'express' +import api from './api' +import socket, { socketInit } from './socket' +// import voice from './voice' + +const app = express() +app.use('/api', api) +app.use('/dist', express.static(`${__dirname}/../dist`)) +app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '../index.html'))) + +app.listen(config.web.port) +socket.listen(config.ws.port, socketInit) +// voice.listen(config.voicews.port) diff --git a/src/middlewares/authorize.js b/src/middlewares/authorize.js new file mode 100644 index 0000000..2a920f9 --- /dev/null +++ b/src/middlewares/authorize.js @@ -0,0 +1,18 @@ +import { authenticate } from '../DatabaseHandler' + +/** + * Validates the 'authorization' header and populates req.user + */ +export default async function authorize (req, res, next) { + const user = await authenticate(req.headers.authorization) + + if (!user) { + return res.status(401).json({ + status: 401, + error: 'You must be logged in to access this route' + }) + } + + req.user = user + next() +} diff --git a/src/middlewares/cors.js b/src/middlewares/cors.js new file mode 100644 index 0000000..ad46610 --- /dev/null +++ b/src/middlewares/cors.js @@ -0,0 +1,8 @@ +/** + * Middleware to allow CORS + */ +export default function allowCORS (req, res, next) { + res.header('Access-Control-Allow-Origin', '*') + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') + next() +} diff --git a/src/middlewares/serverExists.js b/src/middlewares/serverExists.js new file mode 100644 index 0000000..5605c40 --- /dev/null +++ b/src/middlewares/serverExists.js @@ -0,0 +1,19 @@ +import { serverExists } from '../DatabaseHandler' + +/** + * Ensures a server matching 'serverId' exists + */ +export default async function (req, res, next) { + const { serverId } = req.params + const sid = Number(serverId) + + if (!await serverExists(sid)) { + return res.status(404).json({ + status: 404, + error: 'This server does not exists' + }) + } + + req.serverId = sid + next() +} diff --git a/src/socket.js b/src/socket.js new file mode 100644 index 0000000..34d1d88 --- /dev/null +++ b/src/socket.js @@ -0,0 +1,84 @@ +import config from '../Configuration' +import fs from 'fs' +// import http from 'http' +import https from 'https' +import WebSocket from 'ws' + +/* Server middleware */ +import { authenticate, resetPresenceStates } from './DatabaseHandler.js' +import { dispatchHello } from './Dispatcher.js' +import { handlePresenceUpdate } from './EventHandler.js' +import logger from './Logger.js' + +/* Apps */ +let server = https.createServer({ + cert: fs.readFileSync(config.ws.certPath), + key: fs.readFileSync(config.ws.keyPath) +}) +const socket = new WebSocket.Server({ server }) + +socket.on('connection', async (client, req) => { + if (!req.headers.authorization) { + setTimeout(() => { + if (!client.user && client.readyState !== WebSocket.CLOSED) { // Not logged in + client.close(4001, 'Unauthorized: Invalid credentials') + } + }, 30e3) // 30 second grace period to login + } else { + checkLogin(client, req.headers.authorization) + } + + client.on('message', (data) => { + if (typeof data === 'string' && data.startsWith('Basic ') && !client.isAuthenticated) { + checkLogin(client, data) + } + }) + client.on('error', (error) => logger.warn('Client encountered an error\n\tisAuthenticated: {0}\n\tUser: {1}\n\tError: {2}', client.isAuthenticated, client.user, error)) + client.on('close', () => client.user && handlePresenceUpdate(client.user.id, socket.clients)) + client.on('pong', () => { client.isAlive = true }) +}) + +async function checkLogin (client, data) { + const user = await authenticate(data) + + if (!user) { + return client.close(4001, 'Unauthorized: Invalid credentials') + } + + logger.info('Client connected\n\t{0}\n\t{1} clients connected', user.id, socket.clients.size) + client._un = user.id + client.user = user + client.isAlive = true + client.isAuthenticated = true + + await dispatchHello(client) + handlePresenceUpdate(client.user.id, socket.clients) +} + +process.on('SIGINT', async () => { + socket.clients.forEach(ws => ws.close(1000)) + await resetPresenceStates() + + process.exit() +}) + +global.server = socket +export default server + +export function socketInit () { + logger.info('[WS] Server started on port {0}', config.ws.port) + setInterval(() => { + socket.clients.forEach(ws => { + if (ws.readyState === WebSocket.CLOSING || ws.readyState === WebSocket.CLOSED) return + + if (!ws.isAlive && ws.isAuthenticated) { + logger.info('Client disconnected\n\t{0}\n\t{1} clients connected', ws._un, socket.clients.size - 1) + handlePresenceUpdate(ws.user.id, socket.clients) + return ws.terminate() + } + + ws.isAlive = false + ws.ping() + }) + }, 10e3) +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..a4c34ec --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4088 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/cli@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.1.2.tgz#fc2853ae96824b3779ca85de4fd025ce3cf62a5e" + integrity sha512-K3WDlpBPGpoW11SLKFEBhMsITomPovsrZ/wnM3y+WStbytukDXC0OBic3yQp+j058QUw0+R/jfx2obwp1fOzcA== + dependencies: + commander "^2.8.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.1.0" + glob "^7.0.0" + lodash "^4.17.10" + mkdirp "^0.5.1" + output-file-sync "^2.0.0" + slash "^2.0.0" + source-map "^0.5.0" + optionalDependencies: + chokidar "^2.0.3" + +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.2.tgz#f8d2a9ceb6832887329a7b60f9d035791400ba4e" + integrity sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.1.2" + "@babel/helpers" "^7.1.2" + "@babel/parser" "^7.1.2" + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.1.2" + convert-source-map "^1.1.0" + debug "^3.1.0" + json5 "^0.5.0" + lodash "^4.17.10" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.0.0", "@babel/generator@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630" + integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig== + dependencies: + "@babel/types" "^7.1.2" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" + integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-call-delegate@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" + integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-define-map@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" + integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-explode-assignable-expression@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" + integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== + dependencies: + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-hoist-variables@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" + integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-member-expression-to-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" + integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-imports@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" + integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-transforms@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787" + integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-optimise-call-expression@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" + integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27" + integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg== + dependencies: + lodash "^4.17.10" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-replace-supers@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362" + integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-simple-access@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" + integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== + dependencies: + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" + integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-wrap-function@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66" + integrity sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helpers@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.2.tgz#ab752e8c35ef7d39987df4e8586c63b8846234b5" + integrity sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA== + dependencies: + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.1.2" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409" + integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ== + +"@babel/plugin-proposal-async-generator-functions@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce" + integrity sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.0.0" + +"@babel/plugin-proposal-class-properties@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz#9af01856b1241db60ec8838d84691aa0bd1e8df4" + integrity sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + +"@babel/plugin-proposal-json-strings@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e" + integrity sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.0.0" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz#9a17b547f64d0676b6c9cecd4edf74a82ab85e7e" + integrity sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz#b610d928fe551ff7117d42c8bb410eec312a6425" + integrity sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz#498b39cd72536cd7c4b26177d030226eba08cd33" + integrity sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.2.0" + +"@babel/plugin-syntax-async-generators@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz#bf0891dcdbf59558359d0c626fdc9490e20bc13c" + integrity sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-class-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz#e051af5d300cbfbcec4a7476e37a803489881634" + integrity sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-json-strings@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd" + integrity sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz#37d8fbcaf216bd658ea1aebbeb8b75e88ebc549b" + integrity sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz#886f72008b3a8b185977f7cb70713b45e51ee475" + integrity sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-arrow-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz#a6c14875848c68a3b4b3163a486535ef25c7e749" + integrity sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811" + integrity sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + +"@babel/plugin-transform-block-scoped-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz#482b3f75103927e37288b3b67b65f848e2aa0d07" + integrity sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-block-scoping@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz#1745075edffd7cdaf69fab2fb6f9694424b7e9bc" + integrity sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.10" + +"@babel/plugin-transform-classes@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz#ab3f8a564361800cbc8ab1ca6f21108038432249" + integrity sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-define-map" "^7.1.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz#2fbb8900cd3e8258f2a2ede909b90e7556185e31" + integrity sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-destructuring@^7.0.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.2.tgz#5fa77d473f5a0a3f5266ad7ce2e8c995a164d60a" + integrity sha512-cvToXvp/OsYxtEn57XJu9BvsGSEYjAh9UeUuXpoi7x6QHB7YdWyQ4lRU/q0Fu1IJNT0o0u4FQ1DMQBzJ8/8vZg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz#73a24da69bc3c370251f43a3d048198546115e58" + integrity sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/plugin-transform-duplicate-keys@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz#a0601e580991e7cace080e4cf919cfd58da74e86" + integrity sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-exponentiation-operator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz#9c34c2ee7fd77e02779cfa37e403a2e1003ccc73" + integrity sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-for-of@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz#f2ba4eadb83bd17dc3c7e9b30f4707365e1c3e39" + integrity sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz#29c5550d5c46208e7f730516d41eeddd4affadbb" + integrity sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-literals@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz#2aec1d29cdd24c407359c930cdd89e914ee8ff86" + integrity sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz#f9e0a7072c12e296079b5a59f408ff5b97bf86a8" + integrity sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-commonjs@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz#0a9d86451cbbfb29bd15186306897c67f6f9a05c" + integrity sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + +"@babel/plugin-transform-modules-systemjs@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0.tgz#8873d876d4fee23209decc4d1feab8f198cf2df4" + integrity sha512-8EDKMAsitLkiF/D4Zhe9CHEE2XLh4bfLbb9/Zf3FgXYQOZyZYyg7EAel/aT2A7bHv62jwHf09q2KU/oEexr83g== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-umd@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz#a29a7d85d6f28c3561c33964442257cc6a21f2a8" + integrity sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-new-target@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" + integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-object-super@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz#b1ae194a054b826d8d4ba7ca91486d4ada0f91bb" + integrity sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + +"@babel/plugin-transform-parameters@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz#44f492f9d618c9124026e62301c296bf606a7aed" + integrity sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw== + dependencies: + "@babel/helper-call-delegate" "^7.1.0" + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-regenerator@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" + integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== + dependencies: + regenerator-transform "^0.13.3" + +"@babel/plugin-transform-shorthand-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15" + integrity sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz#93583ce48dd8c85e53f3a46056c856e4af30b49b" + integrity sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-sticky-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz#30a9d64ac2ab46eec087b8530535becd90e73366" + integrity sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + +"@babel/plugin-transform-template-literals@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz#084f1952efe5b153ddae69eb8945f882c7a97c65" + integrity sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typeof-symbol@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz#4dcf1e52e943e5267b7313bff347fdbe0f81cec9" + integrity sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-unicode-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz#c6780e5b1863a76fe792d90eded9fcd5b51d68fc" + integrity sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/preset-env@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" + integrity sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.1.0" + "@babel/plugin-proposal-json-strings" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.0.0" + "@babel/plugin-syntax-async-generators" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.1.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.1.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-dotall-regex" "^7.0.0" + "@babel/plugin-transform-duplicate-keys" "^7.0.0" + "@babel/plugin-transform-exponentiation-operator" "^7.1.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.1.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-amd" "^7.1.0" + "@babel/plugin-transform-modules-commonjs" "^7.1.0" + "@babel/plugin-transform-modules-systemjs" "^7.0.0" + "@babel/plugin-transform-modules-umd" "^7.1.0" + "@babel/plugin-transform-new-target" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.1.0" + "@babel/plugin-transform-parameters" "^7.1.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + "@babel/plugin-transform-typeof-symbol" "^7.0.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + browserslist "^4.1.0" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + +"@babel/template@^7.1.0", "@babel/template@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" + integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.1.2" + "@babel/types" "^7.1.2" + +"@babel/traverse@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2" + integrity sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.0.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + debug "^3.1.0" + globals "^11.1.0" + lodash "^4.17.10" + +"@babel/types@^7.0.0", "@babel/types@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" + integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg== + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +acorn-jsx@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e" + integrity sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw== + dependencies: + acorn "^5.0.3" + +acorn@^5.0.3, acorn@^5.6.0: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + +ajv-keywords@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= + +ajv@^6.0.1, ajv@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.4.tgz#247d5274110db653706b550fcc2b797ca28cfc59" + integrity sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= + dependencies: + string-width "^2.0.0" + +ansi-escapes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" + integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-3.0.1.tgz#2ea12883330bf5f81926f70475cf21bdd0c2f8ff" + integrity sha512-DSTLQZdvzJ7znQ1WOqkN3X0Hutt6BTVaZNWyX8/B4P+s9SIxkYgtGKfgHokli1syPcWJUE63/kGVyV1ECA4d1A== + dependencies: + nan "2.11.0" + node-pre-gyp "0.11.0" + +binary-extensions@^1.0.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" + integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== + +"bluebird@>= 3.0.1": + version "3.5.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" + integrity sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg== + +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + integrity sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ= + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + +boxen@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" + integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== + dependencies: + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^2.0.1" + cli-boxes "^1.0.0" + string-width "^2.0.0" + term-size "^1.2.0" + widest-line "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browserslist@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.2.0.tgz#3e5e5edf7fa9758ded0885cf88c1e4be753a591c" + integrity sha512-Berls1CHL7qfQz8Lct6QxYA5d2Tvt4doDWHcjvAISybpd+EKZVppNtXgXhaN6SdrPKo7YLTSZuYBs5cYrSWN8w== + dependencies: + caniuse-lite "^1.0.30000889" + electron-to-chromium "^1.3.73" + node-releases "^1.0.0-alpha.12" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= + +camelcase@^4.0.0, camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + +caniuse-lite@^1.0.30000889: + version "1.0.30000889" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000889.tgz#53e266c83e725ad3bd2e4a3ea76d5031a8aa4c3e" + integrity sha512-MFxcQ6x/LEEoaIhO7Zdb7Eg8YyNONN+WBnS5ERJ0li2yRw51+i4xXUNxnLaveTb/4ZoJqsWKEmlomhG2pYzlQA== + +capture-stack-trace@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" + integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^2.0.2, chokidar@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + lodash.debounce "^4.0.8" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.5" + optionalDependencies: + fsevents "^1.2.2" + +chownr@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.8.1: + version "2.18.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" + integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concurrently@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.0.1.tgz#f6310fbadf2f476dd95df952edb5c0ab789f672c" + integrity sha512-D8UI+mlI/bfvrA57SeKOht6sEpb01dKk+8Yee4fbnkk1Ue8r3S+JXoEdFZIpzQlXJGtnxo47Wvvg/kG4ba3U6Q== + dependencies: + chalk "^2.4.1" + date-fns "^1.23.0" + lodash "^4.17.10" + read-pkg "^4.0.1" + rxjs "6.2.2" + spawn-command "^0.0.2-1" + supports-color "^4.5.0" + tree-kill "^1.1.0" + yargs "^12.0.1" + +configstore@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" + integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.1.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +create-error-class@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= + dependencies: + capture-stack-trace "^1.0.0" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +date-fns@^1.23.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" + integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== + +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407" + integrity sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg== + dependencies: + ms "^2.1.1" + +debug@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.0.1.tgz#f9bb36d439b8d1f0dd52d8fb6b46e4ebb8c1cd5b" + integrity sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw== + dependencies: + ms "^2.1.1" + +decamelize@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7" + integrity sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg== + dependencies: + xregexp "4.0.0" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +depd@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= + +depd@~1.1.1, depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== + dependencies: + is-obj "^1.0.0" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +duplexer@^0.1.1, duplexer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +electron-to-chromium@^1.3.73: + version "1.3.74" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.74.tgz#443f60655e0de630491ca4e48e4436bc375debd1" + integrity sha512-MJm7r64Kt1jQCLEiyfgfcu9lbO6/7jjfpH8x2FrutQ4Cb93aS+6dlfW+G65rPhVTjlTmwCfjQGCKZVEaaltOwQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-standard@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9" + integrity sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ== + +eslint-import-resolver-node@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== + dependencies: + debug "^2.6.9" + resolve "^1.5.0" + +eslint-module-utils@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" + integrity sha1-snA2LNiLGkitMIl2zn+lTphBF0Y= + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-es@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.3.1.tgz#5acb2565db4434803d1d46a9b4cbc94b345bd028" + integrity sha512-9XcVyZiQRVeFjqHw8qHNDAZcQLqaHlOGGpeYqzYh8S4JYCWTCO3yzyen8yVmA5PratfzTRWDwCOFphtDEG+w/w== + dependencies: + eslint-utils "^1.3.0" + regexpp "^2.0.0" + +eslint-plugin-import@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" + integrity sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g== + dependencies: + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.2.0" + has "^1.0.1" + lodash "^4.17.4" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + resolve "^1.6.0" + +eslint-plugin-node@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz#a6e054e50199b2edd85518b89b4e7b323c9f36db" + integrity sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw== + dependencies: + eslint-plugin-es "^1.3.1" + eslint-utils "^1.3.1" + ignore "^4.0.2" + minimatch "^3.0.4" + resolve "^1.8.1" + semver "^5.5.0" + +eslint-plugin-promise@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2" + integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg== + +eslint-plugin-standard@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c" + integrity sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA== + +eslint-scope@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" + integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.0, eslint-utils@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" + integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== + +eslint@^5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.6.1.tgz#348134e32ccc09abb2df1bf282b3f6eed8c7b480" + integrity sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.5.3" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^2.1.0" + eslint-scope "^4.0.0" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^4.0.0" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^4.0.6" + imurmurhash "^0.1.4" + inquirer "^6.1.0" + is-resolvable "^1.1.0" + js-yaml "^3.12.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.5" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^2.0.0" + require-uncached "^1.0.3" + semver "^5.5.1" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^4.0.3" + text-table "^0.2.0" + +espree@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0.tgz#253998f20a0f82db5d866385799d912a83a36634" + integrity sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg== + dependencies: + acorn "^5.6.0" + acorn-jsx "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +event-stream@~3.3.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" + integrity sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g== + dependencies: + duplexer "^0.1.1" + flatmap-stream "^0.1.0" + from "^0.1.7" + map-stream "0.0.7" + pause-stream "^0.0.11" + split "^1.0.1" + stream-combiner "^0.2.2" + through "^2.3.8" + +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +express@^4.16.3: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + integrity sha1-avilAjUNsyRuzEvs9rWjTSL37VM= + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +external-editor@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" + integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + integrity sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE= + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flatmap-stream@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.1.tgz#d34f39ef3b9aa5a2fc225016bd3adf28ac5ae6ea" + integrity sha512-lAq4tLbm3sidmdCN8G3ExaxH7cUCtP5mgDvrYowsx84dcYkJJ4I28N7gkxA6+YlSXzaGLJYIDEi9WGfXzMiXdw== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +from@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + +globals@^11.1.0, globals@^11.7.0: + version "11.8.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" + integrity sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA== + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +http-errors@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY= + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +iconv-lite@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ== + +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +ignore@^4.0.2, ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +inquirer@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8" + integrity sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg== + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.0" + figures "^2.0.0" + lodash "^4.17.10" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.1.0" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" + integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.10: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= + +is-path-in-cwd@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" + integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= + +is-resolvable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= + +is-stream@^1.0.0, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +js-levenshtein@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e" + integrity sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + integrity sha1-5CGiqOINawgZ3yiQj3glJrlt0f4= + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= + dependencies: + package-json "^4.0.0" + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lru-cache@^4.0.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + +map-age-cleaner@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" + integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" + integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +mem@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" + integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^1.0.0" + p-is-promise "^1.1.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@~1.36.0: + version "1.36.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" + integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw== + +mime-types@~2.1.18: + version "2.1.20" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" + integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A== + dependencies: + mime-db "~1.36.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" + integrity sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" + integrity sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +nan@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" + integrity sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw== + +nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== + +nanoid@^1.0.7: + version "1.2.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-1.2.6.tgz#5b9427cabf41f5df1fc371b14c969ffcd3b095aa" + integrity sha512-um9vXiM407BaRbBNa0aKPzFBSD2fDbVmmA9TzCWWlxZvEBzTbixM7ss6GDS4G/cNMYeZSNFx5SzAgWoG1uHU9g== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-pre-gyp@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-releases@^1.0.0-alpha.12: + version "1.0.0-alpha.12" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.12.tgz#32e461b879ea76ac674e511d9832cf29da345268" + integrity sha512-VPB4rTPqpVyWKBHbSa4YPFme3+8WHsOSpvbp0Mfj0bWsC8TEjt4HQrLl1hsBDELlp1nB4lflSgSuGTYiuyaP7Q== + dependencies: + semver "^5.3.0" + +nodemon@^1.18.4: + version "1.18.4" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.4.tgz#873f65fdb53220eb166180cf106b1354ac5d714d" + integrity sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg== + dependencies: + chokidar "^2.0.2" + debug "^3.1.0" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.0" + semver "^5.5.0" + supports-color "^5.2.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^2.3.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + +npm-packlist@^1.1.6: + version "1.1.11" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" + integrity sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.0.1.tgz#3b014fbf01d87f60a1e5348d80fe870dc82c4620" + integrity sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw== + dependencies: + execa "^0.10.0" + lcid "^2.0.0" + mem "^4.0.0" + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-2.0.1.tgz#f53118282f5f553c2799541792b723a4c71430c0" + integrity sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ== + dependencies: + graceful-fs "^4.1.11" + is-plain-obj "^1.1.0" + mkdirp "^0.5.1" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= + dependencies: + got "^6.7.1" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +pause-stream@^0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= + dependencies: + through "~2.3" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= + dependencies: + find-up "^1.0.0" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +private@^0.1.6: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8= + +proxy-addr@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" + integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.8.0" + +ps-tree@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" + integrity sha1-tCGyQUDWID8e08dplrRCewjowBQ= + dependencies: + event-stream "~3.3.0" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +pstree.remy@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.0.tgz#f2af27265bd3e5b32bbfcc10e80bac55ba78688b" + integrity sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q== + dependencies: + ps-tree "^1.1.0" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== + +qs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + integrity sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k= + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + +readable-stream@^2.0.2, readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +regenerate-unicode-properties@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" + integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-transform@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" + integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== + dependencies: + private "^0.1.6" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpp@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpu-core@^4.1.3, regexpu-core@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" + integrity sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^7.0.0" + regjsgen "^0.4.0" + regjsparser "^0.3.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.0.2" + +registry-auth-token@^3.0.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= + dependencies: + rc "^1.0.1" + +regjsgen@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561" + integrity sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA== + +regjsparser@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.3.0.tgz#3c326da7fcfd69fa0d332575a41c8c0cdf588c96" + integrity sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.3.2, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rethinkdbdash@^2.3.31: + version "2.3.31" + resolved "https://registry.yarnpkg.com/rethinkdbdash/-/rethinkdbdash-2.3.31.tgz#fe2f73d1fa6e6f5d96d8e881292013cf6dca914d" + integrity sha512-6nXrKFjdg2Ug0YpdmPWSvyD/2EisHnFNt4FWZ74dcXGK48ievSv+cNFTmVv+KjLi6I9CCf6w4CKZ6yCYTfMfdQ== + dependencies: + bluebird ">= 3.0.1" + +rimraf@^2.2.8, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" + integrity sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ== + dependencies: + tslib "^1.9.0" + +rxjs@^6.1.0: + version "6.3.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" + integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= + dependencies: + semver "^5.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" + integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + integrity sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ= + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shortid@^2.2.12: + version "2.2.13" + resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.13.tgz#b2441e71c664ace458a341d343959f677910ef5b" + integrity sha512-dBuNnQGKrJNfjunmXI2X7bl1gnMO4PwbNxrTzO1JvilODmL7WyyCtA+DYxe9XunLXmxmgzFIvKPQ6XRAQrr46Q== + dependencies: + nanoid "^1.0.7" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== + dependencies: + is-fullwidth-code-point "^2.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= + +spdx-correct@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" + integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f" + integrity sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +split@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== + +stream-combiner@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" + integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg= + dependencies: + duplexer "~0.1.1" + through "~2.3.4" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + integrity sha1-vnoN5ITexcXN34s9WRJQRJEvY1s= + dependencies: + has-flag "^2.0.0" + +supports-color@^5.2.0, supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" + integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== + dependencies: + ajv "^6.0.1" + ajv-keywords "^3.0.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +tar@^4: + version "4.4.6" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + integrity sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg== + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= + dependencies: + execa "^0.7.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +tree-kill@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" + integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg== + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.15, type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.18" + +undefsafe@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" + integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= + dependencies: + debug "^2.2.0" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" + integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" + integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== + +update-notifier@^2.3.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" + integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== + dependencies: + boxen "^1.2.1" + chalk "^2.0.1" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-ci "^1.0.10" + is-installed-globally "^0.1.0" + is-npm "^1.0.0" + latest-version "^3.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= + dependencies: + prepend-http "^1.0.1" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +widest-line@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" + integrity sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM= + dependencies: + string-width "^2.1.1" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= + dependencies: + mkdirp "^0.5.1" + +ws@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.0.tgz#119a9dbf92c54e190ec18d10e871d55c95cf9373" + integrity sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg== + dependencies: + async-limiter "~1.0.0" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= + +xregexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" + integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== + +"y18n@^3.2.1 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= + +yargs-parser@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== + dependencies: + camelcase "^4.1.0" + +yargs@^12.0.1: + version "12.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc" + integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ== + dependencies: + cliui "^4.0.0" + decamelize "^2.0.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^10.1.0" From f78272818b41d7b7071cbe119b158986497b15de Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 00:42:11 +0200 Subject: [PATCH 02/42] nearly perfect --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a09cf4e..18d4332 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ A light chat service ## Installation -To build and install everything that Union needs to work, just do `yarn run install` +To build and install everything that Union needs to work, just do `yarn run setup` and `yarn run start` to start the whole thing -`yarn run install` will create the database or migrate it if it exists, build the app, +`yarn run setup` will create the database or migrate it if it exists, build the app, download web interface from https://github.com/Union-Chat/union-react and generate a default `Confuguration.json` From 87a9a3c8fea79b12da252bd35b4e52bfdb7d3f62 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 10:31:36 +0200 Subject: [PATCH 03/42] Setup unit testing --- package.json | 8 +- test/cors_middleware.spec.js | 20 +++ yarn.lock | 265 ++++++++++++++++++++++++++++++++--- 3 files changed, 275 insertions(+), 18 deletions(-) create mode 100644 test/cors_middleware.spec.js diff --git a/package.json b/package.json index 8d0cabd..52ed9ad 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Lightweight chat service built with C# and NodeJS", "main": "build/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "mocha --require @babel/register", "lint": "eslint **/*.js --ignore-pattern build/*", "setup": "yarn run build && node installer.js", "start": "node build/index.js", @@ -28,6 +28,7 @@ "@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/preset-env": "^7.1.0", + "@babel/register": "^7.0.0", "concurrently": "^4.0.1", "eslint": "^5.6.1", "eslint-config-standard": "^12.0.0", @@ -35,6 +36,9 @@ "eslint-plugin-node": "^7.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", - "nodemon": "^1.18.4" + "mocha": "^5.2.0", + "node-mocks-http": "^1.7.2", + "nodemon": "^1.18.4", + "supertest": "^3.3.0" } } diff --git a/test/cors_middleware.spec.js b/test/cors_middleware.spec.js new file mode 100644 index 0000000..b43c970 --- /dev/null +++ b/test/cors_middleware.spec.js @@ -0,0 +1,20 @@ +/* eslint-env node, mocha */ +import assert from 'assert' +import mock from 'node-mocks-http' +import corsMiddleware from '../src/middlewares/cors' + +describe('CORS Middleware', () => { + it('should append CORS headers to the request', (done) => { + let request = mock.createRequest({ + method: 'GET', + url: '/' + }) + let response = mock.createResponse() + + corsMiddleware(request, response, () => { + assert.strictEqual(response.getHeader('Access-Control-Allow-Origin'), '*') + assert.strictEqual(response.getHeader('Access-Control-Allow-Headers'), 'Origin, X-Requested-With, Content-Type, Accept') + done() + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index a4c34ec..8630e20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -586,6 +586,19 @@ js-levenshtein "^1.1.3" semver "^5.3.0" +"@babel/register@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.0.0.tgz#fa634bae1bfa429f60615b754fc1f1d745edd827" + integrity sha512-f/+CRmaCe7rVEvcvPvxeA8j5aJhHC3aJie7YuqcMDhUOuyWLA7J/aNrTaHIzoWPEhpHA54mec4Mm8fv8KBlv3g== + dependencies: + core-js "^2.5.7" + find-cache-dir "^1.0.0" + home-or-tmp "^3.0.0" + lodash "^4.17.10" + mkdirp "^0.5.1" + pirates "^4.0.0" + source-map-support "^0.5.9" + "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" @@ -624,7 +637,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.5: +accepts@^1.3.5, accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= @@ -773,6 +786,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -883,6 +901,11 @@ braces@^2.3.0, braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + browserslist@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.2.0.tgz#3e5e5edf7fa9758ded0885cf88c1e4be753a591c" @@ -892,6 +915,11 @@ browserslist@^4.1.0: electron-to-chromium "^1.3.73" node-releases "^1.0.0-alpha.12" +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1054,12 +1082,29 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +combined-stream@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + integrity sha1-cj599ugBrFYTETp+RFqbactjKBg= + dependencies: + delayed-stream "~1.0.0" + +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + commander@^2.8.1: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== -component-emitter@^1.2.1: +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= @@ -1133,11 +1178,21 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= +cookiejar@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +core-js@^2.5.7: + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== + core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1187,6 +1242,13 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6. dependencies: ms "2.0.0" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.1.0: version "3.2.5" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407" @@ -1258,6 +1320,11 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -1268,7 +1335,7 @@ depd@1.1.1: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= -depd@~1.1.1, depd@~1.1.2: +depd@^1.1.0, depd@~1.1.1, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -1283,6 +1350,11 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -1342,7 +1414,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -1622,6 +1694,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + external-editor@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" @@ -1698,6 +1775,15 @@ finalhandler@1.1.1: statuses "~1.4.0" unpipe "~1.0.0" +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= + dependencies: + commondir "^1.0.1" + make-dir "^1.0.0" + pkg-dir "^2.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -1706,7 +1792,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.0.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= @@ -1740,6 +1826,20 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +form-data@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +formidable@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" + integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -1752,7 +1852,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -1834,6 +1934,18 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -1892,6 +2004,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -1945,6 +2062,16 @@ has@^1.0.1: dependencies: function-bind "^1.1.1" +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= + +home-or-tmp@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-3.0.0.tgz#57a8fe24cf33cdd524860a15821ddc25c86671fb" + integrity sha1-V6j+JM8zzdUkhgoVgh3cJchmcfs= + hosted-git-info@^2.1.4: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -2493,12 +2620,12 @@ mem@^4.0.0: mimic-fn "^1.0.0" p-is-promise "^1.1.0" -merge-descriptors@1.0.1: +merge-descriptors@1.0.1, merge-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -methods@~1.1.2: +methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -2527,7 +2654,7 @@ mime-db@~1.36.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw== -mime-types@~2.1.18: +mime-types@^2.1.12, mime-types@~2.1.18: version "2.1.20" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A== @@ -2539,12 +2666,17 @@ mime@1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== +mime@^1.3.4, mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -2584,13 +2716,30 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2657,11 +2806,37 @@ negotiator@0.6.1: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +net@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/net/-/net-1.0.2.tgz#d1757ec9a7fb2371d83cf4755ce3e27e10829388" + integrity sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-mocks-http@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.7.2.tgz#d4a22e61c29a1e8a0345f55f61e4b0ff94afc40b" + integrity sha512-n7fj7BzRNjhQuWU/xcoRH/aOOPeLg3qMJx3mUDjwheoEmZGkThhhGMTxJZ5nnSUcXmEU1uS3gFq3VdBMhS5ZxQ== + dependencies: + accepts "^1.3.5" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + net "^1.0.2" + parseurl "^1.3.1" + range-parser "^1.2.0" + type-is "^1.6.16" + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + node-pre-gyp@0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" @@ -2959,7 +3134,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parseurl@~1.3.2: +parseurl@^1.3.1, parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= @@ -3047,6 +3222,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pirates@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.0.tgz#850b18781b4ac6ec58a43c9ed9ec5fe6796addbd" + integrity sha512-8t5BsXy1LUIjn3WWOlOuFDuKswhQb/tkak641lvBgmPOBUQHXveORtlMCp6OdPV1dtuTaEahKA8VNz6uLfKBtA== + dependencies: + node-modules-regexp "^1.0.0" + pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" @@ -3054,6 +3236,13 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" @@ -3126,12 +3315,12 @@ qs@6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== -qs@6.5.2: +qs@6.5.2, qs@^6.5.1: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -range-parser@~1.2.0: +range-parser@^1.2.0, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= @@ -3192,7 +3381,7 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -readable-stream@^2.0.2, readable-stream@^2.0.6: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -3563,6 +3752,14 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-support@^0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -3573,6 +3770,11 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + spawn-command@^0.0.2-1: version "0.0.2-1" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" @@ -3702,6 +3904,37 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +superagent@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + +supertest@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.3.0.tgz#79b27bd7d34392974ab33a31fa51a3e23385987e" + integrity sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ== + dependencies: + methods "^1.1.2" + superagent "^3.8.3" + +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + supports-color@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" @@ -3829,7 +4062,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.15, type-is@~1.6.16: +type-is@^1.6.16, type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== From 650ae626690b482a20381db58fe53739394b0716 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 12:51:44 +0200 Subject: [PATCH 04/42] Database migrations --- .gitignore | 1 + README.md | 3 +++ installer.js | 27 ++++++++++++--------- migrations/1.init.js | 6 +++++ migrations/_.migration_template.js | 9 +++++++ migrations/_migrator.js | 39 ++++++++++++++++++++++++++++++ package.json | 7 +++--- yarn.lock | 10 +++++++- 8 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 migrations/1.init.js create mode 100644 migrations/_.migration_template.js create mode 100644 migrations/_migrator.js diff --git a/.gitignore b/.gitignore index 3f1ed48..0591406 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ logs build dist /index.html +migrations/_migration # Config & certs Configuration.json diff --git a/README.md b/README.md index 18d4332..618d891 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,6 @@ and `yarn run start` to start the whole thing `yarn run setup` will create the database or migrate it if it exists, build the app, download web interface from https://github.com/Union-Chat/union-react and generate a default `Confuguration.json` + +`yarn run setup` can also be used to update your installation of Union (But it'll +not download the latest version of Union-Server) diff --git a/installer.js b/installer.js index 36e4439..9dfe451 100644 --- a/installer.js +++ b/installer.js @@ -1,19 +1,24 @@ const { execSync } = require('child_process') +const { migrator } = require('./migrations/_migrator') console.log('Union installer v1.0.0') console.log('Migrating database...') -// @todo +migrator().then(() => { + console.log('Downloading web interface...') + execSync('rm -rf union-react') + execSync('git clone https://github.com/Union-Chat/union-react') -console.log('Downloading web interface...') -execSync('git clone https://github.com/Union-Chat/union-react') + console.log('Building web interface...') + execSync('cd union-react && yarn && yarn run prod && cd ..') -console.log('Building web interface...') -execSync('cd union-react && yarn && yarn run prod && cd ..') + console.log('Cleaning up...') + execSync('rm -rf dist') + execSync('mv union-react/index.html index.html') + execSync('mv union-react/dist dist') + execSync('rm -rf union-react') -console.log('Cleaning up...') -execSync('mv union-react/index.html index.html') -execSync('mv union-react/dist dist') -execSync('rm -rf union-react') - -console.log('Success! Union is ready to work. Just do `yarn run start`') + console.log('Success! Union is ready to work. Just do `yarn run start`') + // Manually exit because rethink is annoying + process.exit(0) +}) diff --git a/migrations/1.init.js b/migrations/1.init.js new file mode 100644 index 0000000..82bf098 --- /dev/null +++ b/migrations/1.init.js @@ -0,0 +1,6 @@ +module.exports = async (r, dbName) => { + await r.db(dbName).tableCreate('users').run() + await r.db(dbName).tableCreate('servers').run() + await r.db(dbName).tableCreate('invites').run() + await r.db(dbName).tableCreate('messages').run() +} diff --git a/migrations/_.migration_template.js b/migrations/_.migration_template.js new file mode 100644 index 0000000..6dbd5a6 --- /dev/null +++ b/migrations/_.migration_template.js @@ -0,0 +1,9 @@ +// - A migration should NEVER be updated. If you want to update something, create a new migration +// - ALL data must be kept safe during a migration. It can be updated, but no data should be deleted, expect if it's +// no longer required by the application + +// Your migration have to export an async function that accepts the connection in its params +module.exports = async (r, dbName) => { + r.db(dbName).tableCreate('test') + // ... +} diff --git a/migrations/_migrator.js b/migrations/_migrator.js new file mode 100644 index 0000000..4590556 --- /dev/null +++ b/migrations/_migrator.js @@ -0,0 +1,39 @@ +const dbName = 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : '') + +const r = require('rethinkdbdash')({ db: dbName }) +const path = require('path') +const fs = require('fs') + +const migrator = async function () { + // Get some env info + let lastMigration = 0 + if (fs.existsSync(path.resolve(__dirname, '_migration'))) lastMigration = parseInt(fs.readFileSync(path.resolve(__dirname, '_migration'))) + + // List available migrations & filter already executed ones + const files = fs.readdirSync(__dirname).filter(file => { + if (file.startsWith('_')) return false + return parseInt(file.split('.')[0]) > lastMigration + }) + + // Create database if not exists + let databases = await r.dbList().run() + if (databases.indexOf(dbName) === -1) await r.dbCreate(dbName).run() + + // Run migrations! + files.forEach(async file => { + const meta = file.split('.') + lastMigration = parseInt(meta[0]) + console.log('Running migration ' + meta[1]) + await require('./' + file)(r, dbName) + }) + + fs.writeFileSync(path.resolve(__dirname, '_migration'), lastMigration) +} + +module.exports = { + migrator, + drop: async function () { + await r.dbDrop(dbName).run() + fs.unlinkSync(path.resolve(__dirname, path.resolve(__dirname, '_migration'))) + } +} diff --git a/package.json b/package.json index 52ed9ad..2e16613 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,12 @@ "description": "Lightweight chat service built with C# and NodeJS", "main": "build/index.js", "scripts": { - "test": "mocha --require @babel/register", + "test": "cross-env NODE_ENV=test mocha --require @babel/register", "lint": "eslint **/*.js --ignore-pattern build/*", "setup": "yarn run build && node installer.js", - "start": "node build/index.js", + "start": "cross-env NODE_ENV=production node build/index.js", "build": "babel src -d build --copy-files", - "dev": "concurrently -k -n \"Babel,Node\" -p \"[{name}]\" -c \"yellow,green\" \"babel src -w -d build --copy-files\" \"nodemon build/index.js\"" + "dev": "cross-env NODE_ENV=development concurrently -k -n \"Babel,Node\" -p \"[{name}]\" -c \"yellow,green\" \"babel src -w -d build --copy-files\" \"nodemon build/index.js\"" }, "keywords": [], "author": "Kromatic", @@ -30,6 +30,7 @@ "@babel/preset-env": "^7.1.0", "@babel/register": "^7.0.0", "concurrently": "^4.0.1", + "cross-env": "^5.2.0", "eslint": "^5.6.1", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.14.0", diff --git a/yarn.lock b/yarn.lock index 8630e20..15aee04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1205,6 +1205,14 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" +cross-env@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2" + integrity sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg== + dependencies: + cross-spawn "^6.0.5" + is-windows "^1.0.0" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2398,7 +2406,7 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-windows@^1.0.2: +is-windows@^1.0.0, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== From 6296f3f49629aa56d102f8d57d07dea268f28a77 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 12:57:16 +0200 Subject: [PATCH 05/42] Don't spam console --- installer.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/installer.js b/installer.js index 9dfe451..4dcd03a 100644 --- a/installer.js +++ b/installer.js @@ -2,21 +2,20 @@ const { execSync } = require('child_process') const { migrator } = require('./migrations/_migrator') console.log('Union installer v1.0.0') - console.log('Migrating database...') migrator().then(() => { console.log('Downloading web interface...') - execSync('rm -rf union-react') - execSync('git clone https://github.com/Union-Chat/union-react') + execSync('rm -rf union-react', { stdio: [null, null, null] }) + execSync('git clone https://github.com/Union-Chat/union-react', { stdio: [null, null, null] }) console.log('Building web interface...') - execSync('cd union-react && yarn && yarn run prod && cd ..') + execSync('cd union-react && yarn && yarn run prod && cd ..', { stdio: [null, null, null] }) console.log('Cleaning up...') - execSync('rm -rf dist') - execSync('mv union-react/index.html index.html') - execSync('mv union-react/dist dist') - execSync('rm -rf union-react') + execSync('rm -rf dist', { stdio: [null, null, null] }) + execSync('mv union-react/index.html index.html', { stdio: [null, null, null] }) + execSync('mv union-react/dist dist', { stdio: [null, null, null] }) + execSync('rm -rf union-react', { stdio: [null, null, null] }) console.log('Success! Union is ready to work. Just do `yarn run start`') // Manually exit because rethink is annoying From e877eb727652cb813f5752dc35b30e33d49df3bc Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 16:34:53 +0200 Subject: [PATCH 06/42] More tests, beginning of API v2 --- installer.js | 5 +- migrations/_migrator.js | 19 ++-- package.json | 1 + src/DatabaseHandler.js | 69 +++++-------- src/api.js | 39 +------- src/controllers/{ => v1}/core.js | 6 +- src/controllers/v1/index.js | 39 ++++++++ src/controllers/{ => v1}/invites.js | 8 +- src/controllers/{ => v1}/messages.js | 6 +- src/controllers/{ => v1}/servers.js | 8 +- src/controllers/{ => v1}/users.js | 7 +- src/controllers/v2/core.js | 14 +++ src/controllers/v2/index.js | 20 ++++ test/_hooks.js | 8 ++ test/m_authorize.spec.js | 50 ++++++++++ ...cors_middleware.spec.js => m_cors.spec.js} | 5 +- test/m_serverExists.spec.js | 38 +++++++ test/r_core.spec.js | 31 ++++++ test/r_users.spec.js | 34 +++++++ yarn.lock | 98 ++++++++++++++++++- 20 files changed, 396 insertions(+), 109 deletions(-) rename src/controllers/{ => v1}/core.js (74%) create mode 100644 src/controllers/v1/index.js rename src/controllers/{ => v1}/invites.js (84%) rename src/controllers/{ => v1}/messages.js (84%) rename src/controllers/{ => v1}/servers.js (91%) rename src/controllers/{ => v1}/users.js (77%) create mode 100644 src/controllers/v2/core.js create mode 100644 src/controllers/v2/index.js create mode 100644 test/_hooks.js create mode 100644 test/m_authorize.spec.js rename test/{cors_middleware.spec.js => m_cors.spec.js} (84%) create mode 100644 test/m_serverExists.spec.js create mode 100644 test/r_core.spec.js create mode 100644 test/r_users.spec.js diff --git a/installer.js b/installer.js index 4dcd03a..74bb62f 100644 --- a/installer.js +++ b/installer.js @@ -1,5 +1,5 @@ const { execSync } = require('child_process') -const { migrator } = require('./migrations/_migrator') +const { migrator, drain } = require('./migrations/_migrator') console.log('Union installer v1.0.0') console.log('Migrating database...') @@ -18,6 +18,5 @@ migrator().then(() => { execSync('rm -rf union-react', { stdio: [null, null, null] }) console.log('Success! Union is ready to work. Just do `yarn run start`') - // Manually exit because rethink is annoying - process.exit(0) + drain() }) diff --git a/migrations/_migrator.js b/migrations/_migrator.js index 4590556..3d92c22 100644 --- a/migrations/_migrator.js +++ b/migrations/_migrator.js @@ -20,12 +20,18 @@ const migrator = async function () { if (databases.indexOf(dbName) === -1) await r.dbCreate(dbName).run() // Run migrations! - files.forEach(async file => { - const meta = file.split('.') - lastMigration = parseInt(meta[0]) - console.log('Running migration ' + meta[1]) - await require('./' + file)(r, dbName) + const promises = [] + files.forEach(file => { + promises.push(new Promise(async resolve => { + const meta = file.split('.') + lastMigration = parseInt(meta[0]) + console.log('Running migration ' + meta[1]) + await require('./' + file)(r, dbName) + console.log('Successfully ran migration ' + meta[1]) + resolve() + })) }) + await Promise.all(promises) fs.writeFileSync(path.resolve(__dirname, '_migration'), lastMigration) } @@ -35,5 +41,6 @@ module.exports = { drop: async function () { await r.dbDrop(dbName).run() fs.unlinkSync(path.resolve(__dirname, path.resolve(__dirname, '_migration'))) - } + }, + drain: () => r.getPoolMaster().drain() } diff --git a/package.json b/package.json index 2e16613..c98f667 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "mocha": "^5.2.0", "node-mocks-http": "^1.7.2", "nodemon": "^1.18.4", + "sinon": "^6.3.5", "supertest": "^3.3.0" } } diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index d7be843..ab7c2de 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -1,7 +1,7 @@ const { hash, compare } = require('bcrypt') const shortId = require('shortid') const r = require('rethinkdbdash')({ - db: 'union' + db: 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : '') }) /** @@ -10,7 +10,7 @@ const r = require('rethinkdbdash')({ * @param {String} password The password of the account to create * @returns {Boolean} Whether the account was created or not */ -async function createUser (username, password) { +export async function createUser (username, password) { const account = await r.table('users').get(username) if (account !== null) { @@ -34,9 +34,12 @@ async function createUser (username, password) { * @param {String} iconUrl A URL leading to an image to be used as the server's icon * @returns {Object} The created server */ -async function createServer (name, iconUrl, owner) { - const largestId = await r.table('servers').max('id') - const id = largestId.id + 1 +export async function createServer (name, iconUrl, owner) { + let largestId = 0 + try { + largestId = (await r.table('servers').max('id')).id + } catch (e) {} + const id = largestId + 1 const server = { name, @@ -55,7 +58,7 @@ async function createServer (name, iconUrl, owner) { * @param {String} username The member to add to the server * @param {Number} id The server to add the member to */ -async function addMemberToServer (username, id) { +export async function addMemberToServer (username, id) { const user = await r.table('users').get(username) const server = await r.table('servers').get(id) @@ -73,7 +76,7 @@ async function addMemberToServer (username, id) { * @param {String} auth The authentication type + base64-encoded credentials * @returns {(Null|Object)} The user object if authentication was successful, otherwise null */ -async function authenticate (auth) { +export async function authenticate (auth) { if (!auth) { return null } @@ -110,7 +113,7 @@ async function authenticate (auth) { * @param {Number} serverId The user to get the servers of * @returns {Array} A list of users in the server */ -function getUsersInServer (serverId) { +export function getUsersInServer (serverId) { return r.table('users').filter(u => u('servers').contains(serverId)).without(['servers', 'password']) } @@ -120,7 +123,7 @@ function getUsersInServer (serverId) { * @param {Number} serverId The server to check the user's presence of * @returns {Boolean} Whether the user is in the server */ -function isInServer (userId, serverId) { +export function isInServer (userId, serverId) { return r.table('users').get(userId)('servers').contains(serverId).default(false) } @@ -129,7 +132,7 @@ function isInServer (userId, serverId) { * @param {String} username Username of the user to retrieve the servers of * @returns {Promise>} A list of servers that the user is in */ -async function getServersOfUser (username) { +export async function getServersOfUser (username) { const user = await r.table('users').get(username) if (!user) { @@ -148,14 +151,14 @@ async function getServersOfUser (username) { * @param {String} username Username of the user to update the presence of * @param {Boolean} online Whether the user is online or not */ -function updatePresenceOf (username, online) { +export function updatePresenceOf (username, online) { r.table('users').get(username).update({ online }).run() } /** * Resets the online status of all members. Useful when the server is shutting down */ -function resetPresenceStates () { +export function resetPresenceStates () { return r.table('users').update({ online: false }) } @@ -164,7 +167,7 @@ function resetPresenceStates () { * @param {String} username Username of the user to update the presence of * @param {Boolean} online Whether the user is online or not */ -function getUser (username) { +export function getUser (username) { return r.table('users').get(username) } @@ -173,7 +176,7 @@ function getUser (username) { * @param {String} username The name of the user to retrieve * @returns {Object|Null} The user, if they exist */ -function getMember (username) { +export function getMember (username) { return r.table('users').get(username).without(['password', 'servers']) } @@ -182,7 +185,7 @@ function getMember (username) { * @param {String} username The name of the user to kick from the server * @param {Number} serverId The server to remove the member from */ -function removeMemberFromServer (username, serverId) { +export function removeMemberFromServer (username, serverId) { return r.table('users') .get(username) .update({ @@ -195,7 +198,7 @@ function removeMemberFromServer (username, serverId) { * @param {String} username The username to filter servers against * @returns {Number} The amount of servers the user owns */ -function getOwnedServers (username) { +export function getOwnedServers (username) { return r.table('servers').filter(s => s('owner').eq(username)).count() } @@ -204,7 +207,7 @@ function getOwnedServers (username) { * @param {Number} serverId The ID of the server to retrieve * @returns {Object|Null} The server, if it exists */ -function getServer (serverId) { +export function getServer (serverId) { return r.table('servers') .get(serverId) .merge(server => ({ @@ -216,7 +219,7 @@ function getServer (serverId) { * Deletes a server by its ID * @param {Number} serverId The ID of the server to delete */ -async function deleteServer (serverId) { +export async function deleteServer (serverId) { await r.table('servers').get(serverId).delete() await r.table('invites').filter(inv => inv('serverId').eq(serverId)).delete() @@ -233,7 +236,7 @@ async function deleteServer (serverId) { * @param {Number} serverId The id of the server to check * @returns {Boolean} Whether or not the user owns the server */ -function ownsServer (username, serverId) { +export function ownsServer (username, serverId) { return r.table('servers').get(serverId)('owner').eq(username).default(false) } @@ -242,7 +245,7 @@ function ownsServer (username, serverId) { * @param {Number} serverId The id of the server to check * @returns {Boolean} Whether the server exists or not */ -function serverExists (serverId) { +export function serverExists (serverId) { if (!serverId) { return false } @@ -256,7 +259,7 @@ function serverExists (serverId) { * @param {String} inviter The user who generated the invite * @returns {String} The invite code */ -async function generateInvite (serverId, inviter) { +export async function generateInvite (serverId, inviter) { const invite = shortId() await r.table('invites').insert({ @@ -285,26 +288,6 @@ function retrieveMessage (id) { return r.table('messages').get(id) } -module.exports = { - addMemberToServer, - authenticate, - createUser, - createServer, - deleteServer, - generateInvite, - getInvite, - getMember, - getOwnedServers, - getUser, - getUsersInServer, - getServer, - getServersOfUser, - isInServer, - ownsServer, - removeMemberFromServer, - resetPresenceStates, - retrieveMessage, - serverExists, - storeMessage, - updatePresenceOf +export function drain () { + r.getPoolMaster().drain() } diff --git a/src/api.js b/src/api.js index a985765..9b589b1 100644 --- a/src/api.js +++ b/src/api.js @@ -1,41 +1,10 @@ import express from 'express' -import bodyParser from 'body-parser' - -import authorize from './middlewares/authorize' -import allowCORS from './middlewares/cors' -import validateServer from './middlewares/serverExists' - -import { home, info } from './controllers/core' -import { create as userCreate } from './controllers/users' -import { create as serverCreate, remove as serverRemove, leave as serverLeave } from './controllers/servers' -import { create as inviteCreate, accept as inviteAccept } from './controllers/invites' -import { post as messagePost } from './controllers/messages' +import v1 from './controllers/v1' +import v2 from './controllers/v2' const api = express.Router() -// Middlewares -api.use(bodyParser.json()) -api.use(bodyParser.urlencoded({ extended: true })) -api.use(allowCORS) - -// Core -api.get('/', home) -api.get('/info', info) - -// User -api.post('/create', userCreate) -api.patch('/self', (req, res) => res.send('Not done.')) - -// Servers -api.post('/server', authorize, serverCreate) -api.delete('/server/:serverId', validateServer, authorize, serverRemove) -api.delete('/self/server/:serverId', validateServer, authorize, serverLeave) - -api.post('/server/:serverId/invite', validateServer, authorize, inviteCreate) - -api.post('/server/:serverId/messages', validateServer, authorize, messagePost) - -// Invites -api.post('/invites/:inviteId', authorize, inviteAccept) +api.use('/', v1) +api.use('/v2', v2) export default api diff --git a/src/controllers/core.js b/src/controllers/v1/core.js similarity index 74% rename from src/controllers/core.js rename to src/controllers/v1/core.js index cf6bd16..e4cc712 100644 --- a/src/controllers/core.js +++ b/src/controllers/v1/core.js @@ -1,4 +1,4 @@ -import config from '../../Configuration' +import config from '../../../Configuration' export function home (req, res) { res.send('Welcome to the Union API!') @@ -7,11 +7,11 @@ export function home (req, res) { export function info (req, res) { res.send({ - api_version: 2, + api_version: 1, websocket: config.ws.port, voice: config.voicews.port, app_settings: { - max_servers: config.rules.messageCharacterLimit, + max_servers: config.rules.maxServersPerUser, max_message_characters: config.rules.messageCharacterLimit, max_username_characters: config.rules.usernameCharacterLimit } diff --git a/src/controllers/v1/index.js b/src/controllers/v1/index.js new file mode 100644 index 0000000..ae0b65b --- /dev/null +++ b/src/controllers/v1/index.js @@ -0,0 +1,39 @@ +import express from 'express' +import bodyParser from 'body-parser' + +import authorize from '../../middlewares/authorize' +import allowCORS from '../../middlewares/cors' +import validateServer from '../../middlewares/serverExists' + +import { home, info } from './core' +import { create as userCreate } from './users' +import { create as serverCreate, remove as serverRemove, leave as serverLeave } from './servers' +import { create as inviteCreate, accept as inviteAccept } from './invites' +import { post as messagePost } from './messages' + +const api = express.Router() + +// Middlewares +api.use(bodyParser.json()) +api.use(bodyParser.urlencoded({ extended: true })) +api.use(allowCORS) + +// Core +api.get('/', home) +api.get('/info', info) + +// User +api.post('/create', userCreate) +api.patch('/self', (req, res) => res.send('Not done.')) + +// Servers +api.post('/server', authorize, serverCreate) +api.delete('/server/:serverId', validateServer, authorize, serverRemove) +api.delete('/self/server/:serverId', validateServer, authorize, serverLeave) + +api.post('/server/:serverId/invite', validateServer, authorize, inviteCreate) + +api.post('/server/:serverId/messages', validateServer, authorize, messagePost) + +// Invites +api.post('/invites/:inviteId', authorize, inviteAccept) diff --git a/src/controllers/invites.js b/src/controllers/v1/invites.js similarity index 84% rename from src/controllers/invites.js rename to src/controllers/v1/invites.js index 1049a23..22442b6 100644 --- a/src/controllers/invites.js +++ b/src/controllers/v1/invites.js @@ -1,10 +1,10 @@ import { addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, ownsServer -} from '../DatabaseHandler' -import { dispatchMember, dispatchServerJoin } from '../Dispatcher' -import { deduplicate, filter, getClientsById } from '../Utils' -import serverExists from '../middlewares/serverExists' +} from '../../DatabaseHandler' +import { dispatchMember, dispatchServerJoin } from '../../Dispatcher' +import { deduplicate, filter, getClientsById } from '../../Utils' +import serverExists from '../../middlewares/serverExists' export async function create (req, res) { const { serverId } = req; diff --git a/src/controllers/messages.js b/src/controllers/v1/messages.js similarity index 84% rename from src/controllers/messages.js rename to src/controllers/v1/messages.js index eb66b14..19e75d0 100644 --- a/src/controllers/messages.js +++ b/src/controllers/v1/messages.js @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto' -import { storeMessage } from '../DatabaseHandler' -import { dispatchMessage } from '../Dispatcher' -import { filter } from '../Utils' +import { storeMessage } from '../../DatabaseHandler' +import { dispatchMessage } from '../../Dispatcher' +import { filter } from '../../Utils' export async function post (req, res) { const { serverId } = req diff --git a/src/controllers/servers.js b/src/controllers/v1/servers.js similarity index 91% rename from src/controllers/servers.js rename to src/controllers/v1/servers.js index 3505d3f..0e62c01 100644 --- a/src/controllers/servers.js +++ b/src/controllers/v1/servers.js @@ -1,10 +1,10 @@ -import config from '../../Configuration' +import config from '../../../Configuration' import { createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer -} from '../DatabaseHandler' -import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' -import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' +} from '../../DatabaseHandler' +import { deduplicate, filter, getClientsById, remove as utilRemove } from '../../Utils' +import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../../Dispatcher' export async function create (req, res) { // this feels so inconsistent lul const { name, iconUrl } = req.body diff --git a/src/controllers/users.js b/src/controllers/v1/users.js similarity index 77% rename from src/controllers/users.js rename to src/controllers/v1/users.js index b760be8..c6cd75b 100644 --- a/src/controllers/users.js +++ b/src/controllers/v1/users.js @@ -1,7 +1,10 @@ +import config from '../../../Configuration' +import { createUser } from '../../DatabaseHandler' + export async function create (req, res) { const { username, password } = req.body - if (!username || 0 === username.trim().length) { + if (!username || username.trim().length === 0) { return res.status(400).json({ 'error': 'Username cannot be empty.' }) } @@ -9,7 +12,7 @@ export async function create (req, res) { return res.status(400).json({ 'error': `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) } - if (!password || 5 > password.length) { + if (!password || password.length < 5) { return res.status(400).json({ 'error': 'Password cannot be empty and must be 5 characters long or more.' }) } diff --git a/src/controllers/v2/core.js b/src/controllers/v2/core.js new file mode 100644 index 0000000..f03e1a1 --- /dev/null +++ b/src/controllers/v2/core.js @@ -0,0 +1,14 @@ +import config from '../../../Configuration' + +export function home (req, res) { + res.send('Welcome to the Union API!') +} + +export function info (req, res) { + res.send({ + apiVersion: 2, + websocket: config.ws.port, + voice: config.voicews.port, + appSettings: config.rules + }) +} diff --git a/src/controllers/v2/index.js b/src/controllers/v2/index.js new file mode 100644 index 0000000..c1e955e --- /dev/null +++ b/src/controllers/v2/index.js @@ -0,0 +1,20 @@ +import express from 'express' +import bodyParser from 'body-parser' + +import allowCORS from '../../middlewares/cors' + +import { home, info } from './core' + +const api = express.Router() + +// Middlewares +api.use(bodyParser.json()) +api.use(bodyParser.urlencoded({ extended: true })) +api.use(allowCORS) + +// Core +api.get('/', home) +api.get('/info', info) + +// User +// api.post('/users/create', userCreate) diff --git a/test/_hooks.js b/test/_hooks.js new file mode 100644 index 0000000..085e966 --- /dev/null +++ b/test/_hooks.js @@ -0,0 +1,8 @@ +/* eslint-env node, mocha */ +import { drain as migratorDrain } from '../migrations/_migrator' +import { drain } from '../src/DatabaseHandler' + +after(() => { + migratorDrain() + drain() +}) diff --git a/test/m_authorize.spec.js b/test/m_authorize.spec.js new file mode 100644 index 0000000..560c5bf --- /dev/null +++ b/test/m_authorize.spec.js @@ -0,0 +1,50 @@ +/* eslint-env node, mocha */ +import sinon from 'sinon' +import assert from 'assert' +import mock from 'node-mocks-http' +import { migrator, drop } from '../migrations/_migrator' +import { createUser } from '../src/DatabaseHandler' +import authorizeMiddleware from '../src/middlewares/authorize' + +describe('Authorize Middleware', () => { + beforeEach(async () => { + await migrator() + }) + + afterEach(async () => { + await drop() + }) + + it('should deny the request if not authenticated', async () => { + let request = mock.createRequest({ method: 'GET', url: '/' }) + let response = mock.createResponse() + let callback = sinon.fake() + await authorizeMiddleware(request, response, callback) + + assert.strictEqual(response.statusCode, 401) + assert(callback.notCalled) + }) + + it('should deny the request if invalid token', async () => { + let request = mock.createRequest({ method: 'GET', url: '/', headers: { Authorization: 'Basic lol' } }) + let response = mock.createResponse() + let callback = sinon.fake() + await authorizeMiddleware(request, response, callback) + + assert.strictEqual(response.statusCode, 401) + assert(callback.notCalled) + }) + + it('should allow the request if valid token', async () => { + await createUser('root', 'a_secure_password') + const token = Buffer.from('root:a_secure_password').toString('base64') + + let request = mock.createRequest({ method: 'GET', url: '/', headers: { Authorization: 'Basic ' + token } }) + let response = mock.createResponse() + let callback = sinon.fake() + await authorizeMiddleware(request, response, callback) + + assert.strictEqual(response.statusCode, 200) + assert(callback.calledOnce) + }) +}) diff --git a/test/cors_middleware.spec.js b/test/m_cors.spec.js similarity index 84% rename from test/cors_middleware.spec.js rename to test/m_cors.spec.js index b43c970..55c6145 100644 --- a/test/cors_middleware.spec.js +++ b/test/m_cors.spec.js @@ -5,10 +5,7 @@ import corsMiddleware from '../src/middlewares/cors' describe('CORS Middleware', () => { it('should append CORS headers to the request', (done) => { - let request = mock.createRequest({ - method: 'GET', - url: '/' - }) + let request = mock.createRequest({ method: 'GET', url: '/' }) let response = mock.createResponse() corsMiddleware(request, response, () => { diff --git a/test/m_serverExists.spec.js b/test/m_serverExists.spec.js new file mode 100644 index 0000000..7965a43 --- /dev/null +++ b/test/m_serverExists.spec.js @@ -0,0 +1,38 @@ +/* eslint-env node, mocha */ +import sinon from 'sinon' +import assert from 'assert' +import mock from 'node-mocks-http' +import { migrator, drop } from '../migrations/_migrator' +import { createServer } from '../src/DatabaseHandler' +import serverExistsMiddleware from '../src/middlewares/serverExists' + +describe('Server Exists Middleware', () => { + beforeEach(async () => { + await migrator() + }) + + afterEach(async () => { + await drop() + }) + + it('should return 404 if the server does not exists', async () => { + let request = mock.createRequest({ method: 'GET', url: '/', params: { serverId: 666 } }) + let response = mock.createResponse() + let callback = sinon.fake() + await serverExistsMiddleware(request, response, callback) + + assert.strictEqual(response.statusCode, 404) + assert(callback.notCalled) + }) + + it('should return 200 if the server exists', async () => { + await createServer('Union', 'lol.png', 'someone') + let request = mock.createRequest({ method: 'GET', url: '/', params: { serverId: 1 } }) + let response = mock.createResponse() + let callback = sinon.fake() + await serverExistsMiddleware(request, response, callback) + + assert.strictEqual(response.statusCode, 200) + assert(callback.calledOnce) + }) +}) diff --git a/test/r_core.spec.js b/test/r_core.spec.js new file mode 100644 index 0000000..01a3786 --- /dev/null +++ b/test/r_core.spec.js @@ -0,0 +1,31 @@ +/* eslint-env node, mocha */ +import assert from 'assert' +import express from 'express' +import request from 'supertest' +import v2 from '../src/controllers/v2' + +let server +describe('Core Controller', () => { + beforeEach(async () => { + const app = express() + app.use('/', v2) + server = app.listen(6666) + }) + + afterEach((done) => { + server.close(done) + }) + + it('should send the welcome message', async () => { + const req = await request(server).get('/') + assert.strictEqual(req.res.text, 'Welcome to the Union API!') + assert.strictEqual(req.res.statusCode, 200) + }) + + it('should send API configuration', async () => { + const req = await request(server).get('/info') + const json = JSON.parse(req.res.text) + assert.strictEqual(req.res.statusCode, 200) + assert.strictEqual(json.api_version, 2) + }) +}) diff --git a/test/r_users.spec.js b/test/r_users.spec.js new file mode 100644 index 0000000..dfe73e5 --- /dev/null +++ b/test/r_users.spec.js @@ -0,0 +1,34 @@ +/* eslint-env node, mocha */ +import assert from 'assert' +import express from 'express' +import request from 'supertest' +import v2 from '../src/controllers/v2' + +import { migrator, drop } from '../migrations/_migrator' + +let server +describe('User Controller', () => { + beforeEach(async () => { + await migrator() + const app = express() + app.use('/', v2) + server = app.listen(6666) + }) + + afterEach((done) => { + drop().then(() => server.close(done)) + }) + + describe('Create', () => { + it('should create an user', async () => { + const req = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) + assert.strictEqual(req.res.statusCode, 204) + }) + + it('should reject empty username') + it('should reject too long usernames') + it('should reject empty password') + it('should reject short passwords') + it('should reject already used accounts') + }) +}) diff --git a/yarn.lock b/yarn.lock index 15aee04..83de18b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -632,6 +632,32 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@sinonjs/commons@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.0.2.tgz#3e0ac737781627b8844257fadc3d803997d0526e" + integrity sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/formatio@3.0.0", "@sinonjs/formatio@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.0.0.tgz#9d282d81030a03a03fa0c5ce31fd8786a4da311a" + integrity sha512-vdjoYLDptCgvtJs57ULshak3iJe4NW3sJ3g36xVDGff5AE8P30S6A093EIEPjdi2noGhfuNOEkbxt3J3awFW1w== + dependencies: + "@sinonjs/samsam" "2.1.0" + +"@sinonjs/samsam@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.0.tgz#b8b8f5b819605bd63601a6ede459156880f38ea3" + integrity sha512-5x2kFgJYupaF1ns/RmharQ90lQkd2ELS8A9X0ymkAAdemYHGtI2KiUHG8nX2WU0T1qgnOU5YMqnBM2V7NUanNw== + dependencies: + array-from "^2.1.1" + +"@sinonjs/samsam@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.2.tgz#16947fce5f57258d01f1688fdc32723093c55d3f" + integrity sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -749,6 +775,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-from@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" + integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU= + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1358,7 +1389,7 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -diff@3.5.0: +diff@3.5.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -2411,6 +2442,11 @@ is-windows@^1.0.0, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2481,6 +2517,11 @@ json5@^0.5.0: resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= +just-extend@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-3.0.0.tgz#cee004031eaabf6406da03a7b84e4fe9d78ef288" + integrity sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ== + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -2558,11 +2599,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== +lolex@^2.3.2, lolex@^2.7.5: + version "2.7.5" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733" + integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q== + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -2824,6 +2875,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.5.tgz#979a97a19c48d627bb53703726ae8d53ce8d4b3e" + integrity sha512-OHRVvdxKgwZELf2DTgsJEIA4MOq8XWvpSUzoOXyxJ2mY0mMENWC66+70AShLR2z05B1dzrzWlUQJmJERlOUpZw== + dependencies: + "@sinonjs/formatio" "3.0.0" + just-extend "^3.0.0" + lolex "^2.3.2" + path-to-regexp "^1.7.0" + text-encoding "^0.6.4" + node-mocks-http@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.7.2.tgz#d4a22e61c29a1e8a0345f55f61e4b0ff94afc40b" @@ -3194,6 +3256,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + dependencies: + isarray "0.0.1" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -3707,6 +3776,21 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +sinon@^6.3.5: + version "6.3.5" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0" + integrity sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ== + dependencies: + "@sinonjs/commons" "^1.0.2" + "@sinonjs/formatio" "^3.0.0" + "@sinonjs/samsam" "^2.1.2" + diff "^3.5.0" + lodash.get "^4.4.2" + lolex "^2.7.5" + nise "^1.4.5" + supports-color "^5.5.0" + type-detect "^4.0.8" + slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -3950,7 +4034,7 @@ supports-color@^4.5.0: dependencies: has-flag "^2.0.0" -supports-color@^5.2.0, supports-color@^5.3.0: +supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -3989,6 +4073,11 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" +text-encoding@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" + integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk= + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -4070,6 +4159,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@4.0.8, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-is@^1.6.16, type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" From 253b5e3241cd80e246616567ea162001f0d473ba Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 19:21:32 +0200 Subject: [PATCH 07/42] User create endpoint --- migrations/_migrator.js | 3 +- src/api.js | 4 +- src/{controllers/v1 => api_v1}/core.js | 2 +- src/{controllers/v1 => api_v1}/index.js | 8 ++- src/{controllers/v1 => api_v1}/invites.js | 8 +-- src/{controllers/v1 => api_v1}/messages.js | 6 +- src/{controllers/v1 => api_v1}/servers.js | 8 +-- src/{controllers/v1 => api_v1}/users.js | 4 +- src/{controllers/v2 => api_v2}/core.js | 2 +- src/{controllers/v2 => api_v2}/index.js | 7 ++- src/api_v2/users.js | 27 +++++++++ test/m_serverExists.spec.js | 10 ++-- test/r_core.spec.js | 12 ++-- test/r_users.spec.js | 64 +++++++++++++++++----- 14 files changed, 117 insertions(+), 48 deletions(-) rename src/{controllers/v1 => api_v1}/core.js (87%) rename src/{controllers/v1 => api_v1}/index.js (83%) rename src/{controllers/v1 => api_v1}/invites.js (84%) rename src/{controllers/v1 => api_v1}/messages.js (84%) rename src/{controllers/v1 => api_v1}/servers.js (91%) rename src/{controllers/v1 => api_v1}/users.js (87%) rename src/{controllers/v2 => api_v2}/core.js (80%) rename src/{controllers/v2 => api_v2}/index.js (64%) create mode 100644 src/api_v2/users.js diff --git a/migrations/_migrator.js b/migrations/_migrator.js index 3d92c22..54a5adc 100644 --- a/migrations/_migrator.js +++ b/migrations/_migrator.js @@ -32,7 +32,6 @@ const migrator = async function () { })) }) await Promise.all(promises) - fs.writeFileSync(path.resolve(__dirname, '_migration'), lastMigration) } @@ -40,7 +39,7 @@ module.exports = { migrator, drop: async function () { await r.dbDrop(dbName).run() - fs.unlinkSync(path.resolve(__dirname, path.resolve(__dirname, '_migration'))) + fs.unlinkSync(path.resolve(__dirname, '_migration')) }, drain: () => r.getPoolMaster().drain() } diff --git a/src/api.js b/src/api.js index 9b589b1..4393271 100644 --- a/src/api.js +++ b/src/api.js @@ -1,6 +1,6 @@ import express from 'express' -import v1 from './controllers/v1' -import v2 from './controllers/v2' +import v1 from './api_v1' +import v2 from './api_v2' const api = express.Router() diff --git a/src/controllers/v1/core.js b/src/api_v1/core.js similarity index 87% rename from src/controllers/v1/core.js rename to src/api_v1/core.js index e4cc712..e997852 100644 --- a/src/controllers/v1/core.js +++ b/src/api_v1/core.js @@ -1,4 +1,4 @@ -import config from '../../../Configuration' +import config from '../../Configuration' export function home (req, res) { res.send('Welcome to the Union API!') diff --git a/src/controllers/v1/index.js b/src/api_v1/index.js similarity index 83% rename from src/controllers/v1/index.js rename to src/api_v1/index.js index ae0b65b..a2f04db 100644 --- a/src/controllers/v1/index.js +++ b/src/api_v1/index.js @@ -1,9 +1,9 @@ import express from 'express' import bodyParser from 'body-parser' -import authorize from '../../middlewares/authorize' -import allowCORS from '../../middlewares/cors' -import validateServer from '../../middlewares/serverExists' +import authorize from '../middlewares/authorize' +import allowCORS from '../middlewares/cors' +import validateServer from '../middlewares/serverExists' import { home, info } from './core' import { create as userCreate } from './users' @@ -37,3 +37,5 @@ api.post('/server/:serverId/messages', validateServer, authorize, messagePost) // Invites api.post('/invites/:inviteId', authorize, inviteAccept) + +export default api diff --git a/src/controllers/v1/invites.js b/src/api_v1/invites.js similarity index 84% rename from src/controllers/v1/invites.js rename to src/api_v1/invites.js index 22442b6..1049a23 100644 --- a/src/controllers/v1/invites.js +++ b/src/api_v1/invites.js @@ -1,10 +1,10 @@ import { addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, ownsServer -} from '../../DatabaseHandler' -import { dispatchMember, dispatchServerJoin } from '../../Dispatcher' -import { deduplicate, filter, getClientsById } from '../../Utils' -import serverExists from '../../middlewares/serverExists' +} from '../DatabaseHandler' +import { dispatchMember, dispatchServerJoin } from '../Dispatcher' +import { deduplicate, filter, getClientsById } from '../Utils' +import serverExists from '../middlewares/serverExists' export async function create (req, res) { const { serverId } = req; diff --git a/src/controllers/v1/messages.js b/src/api_v1/messages.js similarity index 84% rename from src/controllers/v1/messages.js rename to src/api_v1/messages.js index 19e75d0..eb66b14 100644 --- a/src/controllers/v1/messages.js +++ b/src/api_v1/messages.js @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto' -import { storeMessage } from '../../DatabaseHandler' -import { dispatchMessage } from '../../Dispatcher' -import { filter } from '../../Utils' +import { storeMessage } from '../DatabaseHandler' +import { dispatchMessage } from '../Dispatcher' +import { filter } from '../Utils' export async function post (req, res) { const { serverId } = req diff --git a/src/controllers/v1/servers.js b/src/api_v1/servers.js similarity index 91% rename from src/controllers/v1/servers.js rename to src/api_v1/servers.js index 0e62c01..3505d3f 100644 --- a/src/controllers/v1/servers.js +++ b/src/api_v1/servers.js @@ -1,10 +1,10 @@ -import config from '../../../Configuration' +import config from '../../Configuration' import { createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer -} from '../../DatabaseHandler' -import { deduplicate, filter, getClientsById, remove as utilRemove } from '../../Utils' -import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../../Dispatcher' +} from '../DatabaseHandler' +import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' +import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' export async function create (req, res) { // this feels so inconsistent lul const { name, iconUrl } = req.body diff --git a/src/controllers/v1/users.js b/src/api_v1/users.js similarity index 87% rename from src/controllers/v1/users.js rename to src/api_v1/users.js index c6cd75b..59a6f6a 100644 --- a/src/controllers/v1/users.js +++ b/src/api_v1/users.js @@ -1,5 +1,5 @@ -import config from '../../../Configuration' -import { createUser } from '../../DatabaseHandler' +import config from '../../Configuration' +import { createUser } from '../DatabaseHandler' export async function create (req, res) { const { username, password } = req.body diff --git a/src/controllers/v2/core.js b/src/api_v2/core.js similarity index 80% rename from src/controllers/v2/core.js rename to src/api_v2/core.js index f03e1a1..49b0532 100644 --- a/src/controllers/v2/core.js +++ b/src/api_v2/core.js @@ -1,4 +1,4 @@ -import config from '../../../Configuration' +import config from '../../Configuration' export function home (req, res) { res.send('Welcome to the Union API!') diff --git a/src/controllers/v2/index.js b/src/api_v2/index.js similarity index 64% rename from src/controllers/v2/index.js rename to src/api_v2/index.js index c1e955e..6ba6701 100644 --- a/src/controllers/v2/index.js +++ b/src/api_v2/index.js @@ -1,9 +1,10 @@ import express from 'express' import bodyParser from 'body-parser' -import allowCORS from '../../middlewares/cors' +import allowCORS from '../middlewares/cors' import { home, info } from './core' +import { create as userCreate } from './users' const api = express.Router() @@ -17,4 +18,6 @@ api.get('/', home) api.get('/info', info) // User -// api.post('/users/create', userCreate) +api.post('/users/create', userCreate) + +export default api diff --git a/src/api_v2/users.js b/src/api_v2/users.js new file mode 100644 index 0000000..63da4d4 --- /dev/null +++ b/src/api_v2/users.js @@ -0,0 +1,27 @@ +import config from '../../Configuration' +import { createUser } from '../DatabaseHandler' + +export async function create (req, res) { + const { username, password } = req.body + + // Validate data + if (!username || username.trim().length === 0) { + return res.status(400).json({ error: 'Username cannot be empty.' }) + } + + if (username.trim().length > config.rules.usernameCharacterLimit) { + return res.status(400).json({ error: `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) + } + + if (!password || password.length < 5) { + return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more.' }) + } + + // Save data + const created = await createUser(username.trim(), password) + if (created) { + return res.sendStatus(204) + } else { + return res.status(400).send({ error: 'Account cannot be created. Username must be already taken' }) + } +} diff --git a/test/m_serverExists.spec.js b/test/m_serverExists.spec.js index 7965a43..038041e 100644 --- a/test/m_serverExists.spec.js +++ b/test/m_serverExists.spec.js @@ -6,16 +6,16 @@ import { migrator, drop } from '../migrations/_migrator' import { createServer } from '../src/DatabaseHandler' import serverExistsMiddleware from '../src/middlewares/serverExists' -describe('Server Exists Middleware', () => { - beforeEach(async () => { +describe('Server Exists Middleware', function () { + beforeEach(async function () { await migrator() }) - afterEach(async () => { + afterEach(async function () { await drop() }) - it('should return 404 if the server does not exists', async () => { + it('should return 404 if the server does not exists', async function () { let request = mock.createRequest({ method: 'GET', url: '/', params: { serverId: 666 } }) let response = mock.createResponse() let callback = sinon.fake() @@ -25,7 +25,7 @@ describe('Server Exists Middleware', () => { assert(callback.notCalled) }) - it('should return 200 if the server exists', async () => { + it('should return 200 if the server exists', async function () { await createServer('Union', 'lol.png', 'someone') let request = mock.createRequest({ method: 'GET', url: '/', params: { serverId: 1 } }) let response = mock.createResponse() diff --git a/test/r_core.spec.js b/test/r_core.spec.js index 01a3786..8f4a7b8 100644 --- a/test/r_core.spec.js +++ b/test/r_core.spec.js @@ -2,11 +2,11 @@ import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/controllers/v2' +import v2 from '../src/api_v2' let server -describe('Core Controller', () => { - beforeEach(async () => { +describe('Core Controller', function () { + beforeEach(async function () { const app = express() app.use('/', v2) server = app.listen(6666) @@ -16,16 +16,16 @@ describe('Core Controller', () => { server.close(done) }) - it('should send the welcome message', async () => { + it('should send the welcome message', async function () { const req = await request(server).get('/') assert.strictEqual(req.res.text, 'Welcome to the Union API!') assert.strictEqual(req.res.statusCode, 200) }) - it('should send API configuration', async () => { + it('should send API configuration', async function () { const req = await request(server).get('/info') const json = JSON.parse(req.res.text) assert.strictEqual(req.res.statusCode, 200) - assert.strictEqual(json.api_version, 2) + assert.strictEqual(json.apiVersion, 2) }) }) diff --git a/test/r_users.spec.js b/test/r_users.spec.js index dfe73e5..9316522 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -2,33 +2,71 @@ import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/controllers/v2' +import v2 from '../src/api_v2' import { migrator, drop } from '../migrations/_migrator' let server -describe('User Controller', () => { - beforeEach(async () => { +describe('User Controller', function () { + let r + before(() => { r = require('rethinkdbdash')({ db: 'union_test' }) }) + after(() => r.getPoolMaster().drain()) + beforeEach(async function () { await migrator() const app = express() app.use('/', v2) - server = app.listen(6666) + await new Promise(resolve => { server = app.listen(6666, resolve) }) }) - afterEach((done) => { - drop().then(() => server.close(done)) + afterEach(async function () { + await drop() + await new Promise(resolve => { server = server.close(resolve) }) }) - describe('Create', () => { - it('should create an user', async () => { + describe('Create', function () { + it('should create an user', async function () { const req = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await r.table('users').count().run(), 1) }) - it('should reject empty username') - it('should reject too long usernames') - it('should reject empty password') - it('should reject short passwords') - it('should reject already used accounts') + it('should reject empty username', async function () { + const req = await request(server).post('/users/create').send({ username: '', password: 'a_secure_password' }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 0) + }) + + it('should reject too long usernames', async function () { + const req = await request(server).post('/users/create').send({ + username: 'rooooooooooooooooooooooooooooooooooooooooooooooooooooot', + password: 'a_secure_password' + }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 0) + }) + + it('should reject empty password', async function () { + const req = await request(server).post('/users/create').send({ username: 'root', password: '' }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 0) + }) + + it('should reject short passwords', async function () { + const r = require('rethinkdbdash')({ db: 'union_test' }) + const req = await request(server).post('/users/create').send({ username: 'root', password: 'lol' }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 0) + r.getPoolMaster().drain() + }) + + it('should reject already used accounts', async function () { + const r = require('rethinkdbdash')({ db: 'union_test' }) + const req1 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) + const req2 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) + assert.strictEqual(req1.res.statusCode, 204) + assert.strictEqual(req2.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 1) + r.getPoolMaster().drain() + }) }) }) From d3337cdab1413b7036e6a2495d2bd2b99b0f8623 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sun, 7 Oct 2018 19:38:03 +0200 Subject: [PATCH 08/42] Reflect sources on the main repo Will resolve conflicts later, too lazy rn --- package.json | 1 + src/DatabaseHandler.js | 59 +++++++++++++++++++++++++++------------- src/api_v1/users.js | 14 +++++----- src/api_v2/users.js | 13 +++++---- src/socket.js | 16 +++++------ test/m_authorize.spec.js | 4 +-- test/r_users.spec.js | 12 ++++++-- yarn.lock | 4 +++ 8 files changed, 79 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index c98f667..89500ca 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "bcrypt": "^3.0.0", "body-parser": "^1.18.3", "express": "^4.16.3", + "flakeid": "git+https://github.com/Devoxin/FlakeId.git", "rethinkdbdash": "^2.3.31", "shortid": "^2.2.12", "ws": "^6.0.0" diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index ab7c2de..60ad61b 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -1,9 +1,15 @@ -const { hash, compare } = require('bcrypt') -const shortId = require('shortid') +import { hash, compare } from 'bcrypt' +import shortId from 'shortid' +import FlakeId from 'flakeid' + const r = require('rethinkdbdash')({ db: 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : '') }) +const idGenerator = new FlakeId({ + timeOffset: (2018 - 1970) * 31536000 * 1000 +}) + /** * Creates a user object with the provided username and password, and stores it in the DB * @param {String} username The username of the account to create @@ -11,20 +17,34 @@ const r = require('rethinkdbdash')({ * @returns {Boolean} Whether the account was created or not */ export async function createUser (username, password) { - const account = await r.table('users').get(username) + const id = idGenerator.gen() + const discriminator = await rollDiscriminator(username, id) - if (account !== null) { - return false - } else { - await r.table('users').insert({ - id: username, - password: await hash(password, 10), - createdAt: Date.now(), - servers: [], - online: false - }) + if (!discriminator) { + throw new Error('Cannot generate unique discrim. Try a different username.') + } - return true + await r.table('users').insert({ + id, + username, + discriminator, + password: await hash(password, 10), + createdAt: Date.now(), + servers: [], + online: false + }) + + return `${username}#${discriminator}` +} + +async function rollDiscriminator (username, id) { + const discriminator = id.substring(id.length - 4) // hahayes lazy discrim generating method + const isDiscrimTaken = await r.table('users').filter({ username, discriminator }).count().gt(0) + + if (!isDiscrimTaken) { + return discriminator + } else { + return null } } @@ -88,12 +108,13 @@ export async function authenticate (auth) { } const [username, password] = Buffer.from(creds, 'base64').toString().split(':') + const [name, discriminator] = username ? username.split('#') : [] - if (!username || !password) { + if (!username || !password || !name || !discriminator) { return null } - const user = await r.table('users').get(username) + const user = await r.table('users').filter({ username: name, discriminator }).nth(0).default(null) if (!user) { return null @@ -276,15 +297,15 @@ export async function generateInvite (serverId, inviter) { * @param {String} code The code to lookup * @returns {Object|Null} The invite, if it exists */ -function getInvite (code) { +export function getInvite (code) { return r.table('invites').get(code) } -function storeMessage (id, author) { +export function storeMessage (id, author) { r.table('messages').insert({ id, author }).run() } -function retrieveMessage (id) { +export function retrieveMessage (id) { return r.table('messages').get(id) } diff --git a/src/api_v1/users.js b/src/api_v1/users.js index 59a6f6a..24d9f9b 100644 --- a/src/api_v1/users.js +++ b/src/api_v1/users.js @@ -12,15 +12,15 @@ export async function create (req, res) { return res.status(400).json({ 'error': `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) } + if ([':', '#'].some(char => username.includes(char))) { + return res.status(400).json({ 'error': 'Your username contains prohibited characters.' }) + } + if (!password || password.length < 5) { return res.status(400).json({ 'error': 'Password cannot be empty and must be 5 characters long or more.' }) } - const created = await createUser(username.trim(), password) - - if (created) { - return res.status(200).send('Account created! You may now login.') // todo: json - } else { - return res.status(200).send('Unable to create account (it may already exist!)') // also json here - } + createUser(username.trim(), password) + .then(id => res.status(200).send(id)) + .catch(err => res.status(400).json({ 'error': err.message })) } diff --git a/src/api_v2/users.js b/src/api_v2/users.js index 63da4d4..eb74fe1 100644 --- a/src/api_v2/users.js +++ b/src/api_v2/users.js @@ -13,15 +13,16 @@ export async function create (req, res) { return res.status(400).json({ error: `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) } + if ([':', '#'].some(char => username.includes(char))) { + return res.status(400).json({ error: 'Your username contains prohibited characters.' }) + } + if (!password || password.length < 5) { return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more.' }) } // Save data - const created = await createUser(username.trim(), password) - if (created) { - return res.sendStatus(204) - } else { - return res.status(400).send({ error: 'Account cannot be created. Username must be already taken' }) - } + createUser(username.trim(), password) + .then(id => res.status(200).json({ id })) + .catch(err => res.status(400).json({ error: err.message })) } diff --git a/src/socket.js b/src/socket.js index 34d1d88..97235fe 100644 --- a/src/socket.js +++ b/src/socket.js @@ -1,6 +1,6 @@ import config from '../Configuration' import fs from 'fs' -// import http from 'http' +import http from 'http' import https from 'https' import WebSocket from 'ws' @@ -11,10 +11,12 @@ import { handlePresenceUpdate } from './EventHandler.js' import logger from './Logger.js' /* Apps */ -let server = https.createServer({ - cert: fs.readFileSync(config.ws.certPath), - key: fs.readFileSync(config.ws.keyPath) -}) +let server = process.argv.includes('--use-insecure-ws') + ? http.createServer() + : https.createServer({ + cert: fs.readFileSync(config.ws.certPath), + key: fs.readFileSync(config.ws.keyPath) + }) const socket = new WebSocket.Server({ server }) socket.on('connection', async (client, req) => { @@ -57,9 +59,7 @@ async function checkLogin (client, data) { process.on('SIGINT', async () => { socket.clients.forEach(ws => ws.close(1000)) - await resetPresenceStates() - - process.exit() + resetPresenceStates().finally(process.exit) }) global.server = socket diff --git a/test/m_authorize.spec.js b/test/m_authorize.spec.js index 560c5bf..2518113 100644 --- a/test/m_authorize.spec.js +++ b/test/m_authorize.spec.js @@ -36,8 +36,8 @@ describe('Authorize Middleware', () => { }) it('should allow the request if valid token', async () => { - await createUser('root', 'a_secure_password') - const token = Buffer.from('root:a_secure_password').toString('base64') + const id = await createUser('root', 'a_secure_password') + const token = Buffer.from(id + ':a_secure_password').toString('base64') let request = mock.createRequest({ method: 'GET', url: '/', headers: { Authorization: 'Basic ' + token } }) let response = mock.createResponse() diff --git a/test/r_users.spec.js b/test/r_users.spec.js index 9316522..913d24f 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -26,7 +26,7 @@ describe('User Controller', function () { describe('Create', function () { it('should create an user', async function () { const req = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) - assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(req.res.statusCode, 200) assert.strictEqual(await r.table('users').count().run(), 1) }) @@ -36,6 +36,14 @@ describe('User Controller', function () { assert.strictEqual(await r.table('users').count().run(), 0) }) + it('should reject invalid username', async function () { + const req1 = await request(server).post('/users/create').send({ username: 'ro:ot', password: 'a_secure_password' }) + const req2 = await request(server).post('/users/create').send({ username: 'ro#ot', password: 'a_secure_password' }) + assert.strictEqual(req1.res.statusCode, 400) + assert.strictEqual(req2.res.statusCode, 400) + assert.strictEqual(await r.table('users').count().run(), 0) + }) + it('should reject too long usernames', async function () { const req = await request(server).post('/users/create').send({ username: 'rooooooooooooooooooooooooooooooooooooooooooooooooooooot', @@ -63,7 +71,7 @@ describe('User Controller', function () { const r = require('rethinkdbdash')({ db: 'union_test' }) const req1 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) const req2 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) - assert.strictEqual(req1.res.statusCode, 204) + assert.strictEqual(req1.res.statusCode, 200) assert.strictEqual(req2.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 1) r.getPoolMaster().drain() diff --git a/yarn.lock b/yarn.lock index 83de18b..58967b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1845,6 +1845,10 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +"flakeid@git+https://github.com/Devoxin/FlakeId.git": + version "1.0.0" + resolved "git+https://github.com/Devoxin/FlakeId.git#c1cf95fc9ad958b8700872853a72353f3c1b112b" + flat-cache@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" From 0166c289e8b81614aac7959b71745b5c8bb400ea Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Mon, 8 Oct 2018 21:41:27 +0200 Subject: [PATCH 09/42] Port users and servers to APIv2 --- migrations/_migrator.js | 4 +- src/DatabaseHandler.js | 6 +- src/api_v1/core.js | 8 +- src/api_v2/index.js | 8 ++ src/api_v2/servers.js | 83 +++++++++++++++++ test/m_authorize.spec.js | 2 + test/m_cors.spec.js | 2 + test/m_serverExists.spec.js | 2 + test/r_core.spec.js | 2 + test/r_servers.spec.js | 172 ++++++++++++++++++++++++++++++++++++ test/r_users.spec.js | 16 ++-- 11 files changed, 286 insertions(+), 19 deletions(-) create mode 100644 src/api_v2/servers.js create mode 100644 test/r_servers.spec.js diff --git a/migrations/_migrator.js b/migrations/_migrator.js index 54a5adc..2227950 100644 --- a/migrations/_migrator.js +++ b/migrations/_migrator.js @@ -1,6 +1,6 @@ const dbName = 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : '') -const r = require('rethinkdbdash')({ db: dbName }) +const r = require('rethinkdbdash')({ silent: true, db: dbName }) const path = require('path') const fs = require('fs') @@ -25,9 +25,7 @@ const migrator = async function () { promises.push(new Promise(async resolve => { const meta = file.split('.') lastMigration = parseInt(meta[0]) - console.log('Running migration ' + meta[1]) await require('./' + file)(r, dbName) - console.log('Successfully ran migration ' + meta[1]) resolve() })) }) diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index 60ad61b..d5eec8e 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -3,7 +3,11 @@ import shortId from 'shortid' import FlakeId from 'flakeid' const r = require('rethinkdbdash')({ - db: 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : '') + db: 'union' + (process.env.NODE_ENV !== 'production' ? '_' + process.env.NODE_ENV : ''), + silent: process.env.NODE_ENV === 'test', + log: function (message) { + if (process.env.NODE_ENV !== 'test') console.log(message) + } }) const idGenerator = new FlakeId({ diff --git a/src/api_v1/core.js b/src/api_v1/core.js index e997852..bb654ba 100644 --- a/src/api_v1/core.js +++ b/src/api_v1/core.js @@ -7,13 +7,9 @@ export function home (req, res) { export function info (req, res) { res.send({ - api_version: 1, + apiVersion: 1, websocket: config.ws.port, voice: config.voicews.port, - app_settings: { - max_servers: config.rules.maxServersPerUser, - max_message_characters: config.rules.messageCharacterLimit, - max_username_characters: config.rules.usernameCharacterLimit - } + appSettings: config.rules }) } diff --git a/src/api_v2/index.js b/src/api_v2/index.js index 6ba6701..0be29ea 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -2,9 +2,12 @@ import express from 'express' import bodyParser from 'body-parser' import allowCORS from '../middlewares/cors' +import authorize from '../middlewares/authorize' +import serverExists from '../middlewares/serverExists' import { home, info } from './core' import { create as userCreate } from './users' +import { create as serverCreate, leave as serverLeave, remove as serverDelete } from './servers' const api = express.Router() @@ -20,4 +23,9 @@ api.get('/info', info) // User api.post('/users/create', userCreate) +// Server +api.post('/servers/create', authorize, serverCreate) +api.delete('/servers/:serverId([0-9]+)/leave', authorize, serverExists, serverLeave) +api.delete('/servers/:serverId([0-9]+)', authorize, serverExists, serverDelete) + export default api diff --git a/src/api_v2/servers.js b/src/api_v2/servers.js new file mode 100644 index 0000000..e6deeb5 --- /dev/null +++ b/src/api_v2/servers.js @@ -0,0 +1,83 @@ +import config from '../../Configuration' +import { + createServer, deleteServer, getOwnedServers, getServer, isInServer, ownsServer, + removeMemberFromServer +} from '../DatabaseHandler' +import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' +import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' + +export async function create (req, res) { + const { name, iconUrl } = req.body + if (!name || name.trim().length === 0) { + return res.status(400).json({ error: 'Server name cannot be empty.' }) + } + + if (name.trim().length > config.rules.serverCharacterLimit) { + return res.status(400).json({ error: `Username cannot exceed ${config.rules.serverCharacterLimit} characters.` }) + } + + if (await getOwnedServers(req.user.id) >= config.rules.maxServersPerUser) { + return res.status(400).json({ 'error': `You cannot create more than ${config.rules.maxServersPerUser} servers` }) + } + + const server = await createServer(name, iconUrl, req.user.id) + res.status(200).send() + + if (global.server) { // May not be declared in unit tests + const clients = getClientsById(global.server.clients, req.user.id) + if (clients.length > 0) { + clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, server.id) }) + dispatchServerJoin(clients, server) + } + } +} + +export async function leave (req, res) { + const { serverId } = req + + if (!await isInServer(req.user.id, serverId)) { + return res.status(400).json({ error: 'You are not in that server' }) + } + + if (await ownsServer(req.user.id, serverId)) { + return res.status(400).json({ error: 'You cannot leave a server that you own' }) + } + + await removeMemberFromServer(req.user.id, serverId) + res.sendStatus(204) + + if (global.server) { + dispatchServerLeave(getClientsById(global.server.clients, req.user.id), serverId) + + const self = await getClientsById(global.server.clients, req.user.id) + const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + + if (members.length > 0) { + dispatchMemberLeave(members, req.user.id, serverId) + } + + if (self.length > 0) { + self.forEach(ws => utilRemove(ws.user.servers, serverId)) + } + } +} + +export async function remove (req, res) { + const { serverId } = req + + if (!await ownsServer(req.user.id, serverId)) { + return res.status(403).json({ error: 'You can only delete servers that you own.' }) + } + + await deleteServer(serverId) + res.sendStatus(204) + + if (global.server) { + const clients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + + if (clients.length > 0) { + clients.forEach(ws => utilRemove(ws.user.servers, serverId)) + dispatchServerLeave(clients, serverId) + } + } +} diff --git a/test/m_authorize.spec.js b/test/m_authorize.spec.js index 2518113..ffde81c 100644 --- a/test/m_authorize.spec.js +++ b/test/m_authorize.spec.js @@ -1,4 +1,6 @@ /* eslint-env node, mocha */ +import './_hooks' + import sinon from 'sinon' import assert from 'assert' import mock from 'node-mocks-http' diff --git a/test/m_cors.spec.js b/test/m_cors.spec.js index 55c6145..c8ad377 100644 --- a/test/m_cors.spec.js +++ b/test/m_cors.spec.js @@ -1,4 +1,6 @@ /* eslint-env node, mocha */ +import './_hooks' + import assert from 'assert' import mock from 'node-mocks-http' import corsMiddleware from '../src/middlewares/cors' diff --git a/test/m_serverExists.spec.js b/test/m_serverExists.spec.js index 038041e..542a389 100644 --- a/test/m_serverExists.spec.js +++ b/test/m_serverExists.spec.js @@ -1,4 +1,6 @@ /* eslint-env node, mocha */ +import './_hooks' + import sinon from 'sinon' import assert from 'assert' import mock from 'node-mocks-http' diff --git a/test/r_core.spec.js b/test/r_core.spec.js index 8f4a7b8..651bdcb 100644 --- a/test/r_core.spec.js +++ b/test/r_core.spec.js @@ -1,4 +1,6 @@ /* eslint-env node, mocha */ +import './_hooks' + import assert from 'assert' import express from 'express' import request from 'supertest' diff --git a/test/r_servers.spec.js b/test/r_servers.spec.js new file mode 100644 index 0000000..1777ed7 --- /dev/null +++ b/test/r_servers.spec.js @@ -0,0 +1,172 @@ +/* eslint-env node, mocha */ +import './_hooks' + +import assert from 'assert' +import express from 'express' +import request from 'supertest' +import v2 from '../src/api_v2' + +import { migrator, drop } from '../migrations/_migrator' +import { + addMemberToServer, createServer, createUser, getServer, isInServer, + removeMemberFromServer +} from '../src/DatabaseHandler' + +let server, r, userId, token +describe('Servers Controller', function () { + before(() => { r = require('rethinkdbdash')({ silent: true, db: 'union_test' }) }) + after(() => r.getPoolMaster().drain()) + beforeEach(async function () { + await migrator() + const app = express() + app.use('/', v2) + + const username = await createUser('root', 'root') + const usermeta = username.split('#') + userId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + token = Buffer.from(username + ':root').toString('base64') + + await new Promise(resolve => { server = app.listen(6666, resolve) }) + }) + + afterEach(async function () { + await drop() + await new Promise(resolve => { server = server.close(resolve) }) + }) + + describe('Create', function () { + it('should require authentication', async function () { + const req = await request(server).post('/servers/create') + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should create a server with correct owner id', async function () { + const req = await request(server).post('/servers/create').send({ + name: 'A server', + iconUrl: 'lol.png' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 200) + assert.strictEqual(await r.table('servers').count().run(), 1) + const serv = await getServer(1) + assert.strictEqual(serv.owner, userId) + }) + + it('should reject empty name', async function () { + const req = await request(server).post('/servers/create').send({ + name: '', + iconUrl: 'lol.png' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('servers').count().run(), 0) + }) + + it('should reject too long names', async function () { + const req = await request(server).post('/servers/create').send({ + name: 'A serverrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', + iconUrl: 'lol.png' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('servers').count().run(), 0) + }) + + it('should prevent user from creating ton of servers', async function () { + const serverData = { name: 'A server', iconUrl: 'lol.png' } + await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + const req = await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('servers').count().run(), 5) + }) + }) + + describe('Leave', function () { + let serverId, lambdaId, lambdaToken + beforeEach(async function () { + const username = await createUser('sudo', 'sudo') + const usermeta = username.split('#') + lambdaId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + lambdaToken = Buffer.from(username + ':sudo').toString('base64') + + serverId = (await createServer('A server', 'lol.png', userId)).id + await addMemberToServer(lambdaId, serverId) + }) + + afterEach(async function () { + await r.db('union_test').tableDrop('servers').run() + await r.db('union_test').tableCreate('servers').run() + }) + + it('should require authentication', async function () { + const req = await request(server).delete('/servers/' + serverId + '/leave') + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should leave the server', async function () { + const req = await request(server).delete('/servers/' + serverId + '/leave').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await isInServer(lambdaId, serverId), false) + }) + + it('should reject if the server does not exists', async function () { + const req = await request(server).delete('/servers/666/leave').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 404) + }) + + it('should reject if the user is the server owner', async function () { + const req = await request(server).delete('/servers/' + serverId + '/leave').set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await isInServer(lambdaId, serverId), true) + }) + + it('should reject if the user is not in this server', async function () { + await removeMemberFromServer(lambdaId, serverId) + const req = await request(server).delete('/servers/' + serverId + '/leave').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 400) + }) + }) + + describe('Delete', function () { + let serverId, lambdaId, lambdaToken + beforeEach(async function () { + const username = await createUser('sudo', 'sudo') + const usermeta = username.split('#') + lambdaId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + lambdaToken = Buffer.from(username + ':sudo').toString('base64') + + serverId = (await createServer('A server', 'lol.png', userId)).id + await addMemberToServer(lambdaId, serverId) + }) + + afterEach(async function () { + await r.db('union_test').tableDrop('servers').run() + await r.db('union_test').tableCreate('servers').run() + }) + + it('should require authentication', async function () { + const req = await request(server).delete('/servers/' + serverId) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should delete the server', async function () { + const req = await request(server).delete('/servers/' + serverId).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await r.table('servers').count().run(), 0) + }) + + it('should reject if the server does not exists', async function () { + const req = await request(server).delete('/servers/666').set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 404) + assert.strictEqual(await r.table('servers').count().run(), 1) + }) + + it('should reject if the user is not the server owner', async function () { + const req = await request(server).delete('/servers/' + serverId).set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 403) + assert.strictEqual(await r.table('servers').count().run(), 1) + }) + }) +}) diff --git a/test/r_users.spec.js b/test/r_users.spec.js index 913d24f..167feba 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -1,4 +1,6 @@ /* eslint-env node, mocha */ +import './_hooks' + import assert from 'assert' import express from 'express' import request from 'supertest' @@ -7,9 +9,9 @@ import v2 from '../src/api_v2' import { migrator, drop } from '../migrations/_migrator' let server -describe('User Controller', function () { +describe('Users Controller', function () { let r - before(() => { r = require('rethinkdbdash')({ db: 'union_test' }) }) + before(() => { r = require('rethinkdbdash')({ silent: true, db: 'union_test' }) }) after(() => r.getPoolMaster().drain()) beforeEach(async function () { await migrator() @@ -60,21 +62,17 @@ describe('User Controller', function () { }) it('should reject short passwords', async function () { - const r = require('rethinkdbdash')({ db: 'union_test' }) const req = await request(server).post('/users/create').send({ username: 'root', password: 'lol' }) assert.strictEqual(req.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 0) - r.getPoolMaster().drain() }) - it('should reject already used accounts', async function () { - const r = require('rethinkdbdash')({ db: 'union_test' }) + it('should allow duplicate usernames', async function () { const req1 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) const req2 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) assert.strictEqual(req1.res.statusCode, 200) - assert.strictEqual(req2.res.statusCode, 400) - assert.strictEqual(await r.table('users').count().run(), 1) - r.getPoolMaster().drain() + assert.strictEqual(req2.res.statusCode, 200) + assert.strictEqual(await r.table('users').count().run(), 2) }) }) }) From 3a070cfe005360665858d166e3cebb52041bba7b Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Tue, 9 Oct 2018 22:41:39 +0200 Subject: [PATCH 10/42] Implement invites --- src/api_v1/invites.js | 40 +++++++++--------- src/api_v2/index.js | 5 +++ src/api_v2/invites.js | 57 +++++++++++++++++++++++++ src/api_v2/servers.js | 6 +-- src/api_v2/users.js | 8 ++-- test/r_invites.spec.js | 95 ++++++++++++++++++++++++++++++++++++++++++ test/r_servers.spec.js | 10 ----- 7 files changed, 184 insertions(+), 37 deletions(-) create mode 100644 src/api_v2/invites.js create mode 100644 test/r_invites.spec.js diff --git a/src/api_v1/invites.js b/src/api_v1/invites.js index 1049a23..086b9fd 100644 --- a/src/api_v1/invites.js +++ b/src/api_v1/invites.js @@ -7,50 +7,50 @@ import { deduplicate, filter, getClientsById } from '../Utils' import serverExists from '../middlewares/serverExists' export async function create (req, res) { - const { serverId } = req; + const { serverId } = req if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ 'error': 'Only the server owner can generate invites.' }); + return res.status(403).json({ 'error': 'Only the server owner can generate invites.' }) } - const inviteCode = await generateInvite(serverId, req.user.id); + const inviteCode = await generateInvite(serverId, req.user.id) - res.status(200).send({ code: inviteCode }); + res.status(200).send({ code: inviteCode }) } export async function accept (req, res) { - const invite = await getInvite(req.params.inviteId); + const invite = await getInvite(req.params.inviteId) if (!invite) { - return res.status(404).json({ 'error': 'Unknown invite' }); + return res.status(404).json({ 'error': 'Unknown invite' }) } if (!await serverExists(invite.serverId)) { - return res.status(404).json({ 'error': 'Unknown server; invite expired' }); + return res.status(404).json({ 'error': 'Unknown server; invite expired' }) } - const { serverId } = invite; + const { serverId } = invite if (await isInServer(req.user.id, serverId)) { - return res.status(200).send(); + return res.status(200).send() } - await addMemberToServer(req.user.id, serverId); + await addMemberToServer(req.user.id, serverId) - const clients = await getClientsById(global.server.clients, req.user.id); - const server = await getServer(serverId); + const clients = await getClientsById(global.server.clients, req.user.id) + const server = await getServer(serverId) - if (0 < clients.length) { - clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, serverId)); - dispatchServerJoin(clients, server); + if (clients.length > 0) { + clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, serverId) }) + dispatchServerJoin(clients, server) } - const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id); - const member = await getMember(req.user.id); + const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id) + const member = await getMember(req.user.id) - if (0 < members.length) { - dispatchMember(members, serverId, member); + if (members.length > 0) { + dispatchMember(members, serverId, member) } - res.status(200).send(); + res.status(200).send() } diff --git a/src/api_v2/index.js b/src/api_v2/index.js index 0be29ea..21a7172 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -8,6 +8,7 @@ import serverExists from '../middlewares/serverExists' import { home, info } from './core' import { create as userCreate } from './users' import { create as serverCreate, leave as serverLeave, remove as serverDelete } from './servers' +import { create as inviteCreate, accept as inviteAccept } from './invites' const api = express.Router() @@ -28,4 +29,8 @@ api.post('/servers/create', authorize, serverCreate) api.delete('/servers/:serverId([0-9]+)/leave', authorize, serverExists, serverLeave) api.delete('/servers/:serverId([0-9]+)', authorize, serverExists, serverDelete) +// Invites +api.post('/servers/:serverId([0-9]+)/invites/create', authorize, serverExists, inviteCreate) +api.post('/invites/:inviteId([a-zA-Z0-9-_]+)', authorize, inviteAccept) + export default api diff --git a/src/api_v2/invites.js b/src/api_v2/invites.js new file mode 100644 index 0000000..4c5061e --- /dev/null +++ b/src/api_v2/invites.js @@ -0,0 +1,57 @@ +import { + addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, + ownsServer, serverExists +} from '../DatabaseHandler' +import { dispatchMember, dispatchServerJoin } from '../Dispatcher' +import { deduplicate, filter, getClientsById } from '../Utils' + +export async function create (req, res) { + const { serverId } = req + + if (!await ownsServer(req.user.id, serverId)) { + return res.status(403).json({ error: 'Only the server owner can generate invites' }) + } + + const inviteCode = await generateInvite(serverId, req.user.id) + + res.status(200).send({ code: inviteCode }) +} + +export async function accept (req, res) { + const invite = await getInvite(req.params.inviteId) + + if (!invite) { + return res.status(404).json({ error: 'Unknown invite' }) + } + + if (!await serverExists(invite.serverId)) { + return res.status(404).json({ error: 'Unknown server; invite expired' }) + } + + const { serverId } = invite + + if (await isInServer(req.user.id, serverId)) { + return res.status(400).json({ error: 'You\'re already in this server' }) + } + + await addMemberToServer(req.user.id, serverId) + res.sendStatus(204) + + if (global.server) { + const clients = await getClientsById(global.server.clients, req.user.id) + const server = await getServer(serverId) + + if (clients.length > 0) { + clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, serverId) }) + dispatchServerJoin(clients, server) + } + + const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id) + const member = await getMember(req.user.id) + + if (members.length > 0) { + dispatchMember(members, serverId, member) + } + } + +} diff --git a/src/api_v2/servers.js b/src/api_v2/servers.js index e6deeb5..788b1a2 100644 --- a/src/api_v2/servers.js +++ b/src/api_v2/servers.js @@ -9,11 +9,11 @@ import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '.. export async function create (req, res) { const { name, iconUrl } = req.body if (!name || name.trim().length === 0) { - return res.status(400).json({ error: 'Server name cannot be empty.' }) + return res.status(400).json({ error: 'Server name cannot be empty' }) } if (name.trim().length > config.rules.serverCharacterLimit) { - return res.status(400).json({ error: `Username cannot exceed ${config.rules.serverCharacterLimit} characters.` }) + return res.status(400).json({ error: `Username cannot exceed ${config.rules.serverCharacterLimit} characters` }) } if (await getOwnedServers(req.user.id) >= config.rules.maxServersPerUser) { @@ -66,7 +66,7 @@ export async function remove (req, res) { const { serverId } = req if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ error: 'You can only delete servers that you own.' }) + return res.status(403).json({ error: 'You can only delete servers that you own' }) } await deleteServer(serverId) diff --git a/src/api_v2/users.js b/src/api_v2/users.js index eb74fe1..6770285 100644 --- a/src/api_v2/users.js +++ b/src/api_v2/users.js @@ -6,19 +6,19 @@ export async function create (req, res) { // Validate data if (!username || username.trim().length === 0) { - return res.status(400).json({ error: 'Username cannot be empty.' }) + return res.status(400).json({ error: 'Username cannot be empty' }) } if (username.trim().length > config.rules.usernameCharacterLimit) { - return res.status(400).json({ error: `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) + return res.status(400).json({ error: `Username cannot exceed ${config.rules.usernameCharacterLimit} characters` }) } if ([':', '#'].some(char => username.includes(char))) { - return res.status(400).json({ error: 'Your username contains prohibited characters.' }) + return res.status(400).json({ error: 'Your username contains prohibited characters' }) } if (!password || password.length < 5) { - return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more.' }) + return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more' }) } // Save data diff --git a/test/r_invites.spec.js b/test/r_invites.spec.js new file mode 100644 index 0000000..f5c244f --- /dev/null +++ b/test/r_invites.spec.js @@ -0,0 +1,95 @@ +/* eslint-env node, mocha */ +import './_hooks' + +import assert from 'assert' +import express from 'express' +import request from 'supertest' +import v2 from '../src/api_v2' + +import { migrator, drop } from '../migrations/_migrator' +import { + addMemberToServer, createServer, createUser, generateInvite, isInServer +} from '../src/DatabaseHandler' + +let server, r, userId, userToken, lambdaId, lambdaToken, serverId +describe('Invites Controller', function () { + before(() => { r = require('rethinkdbdash')({ silent: true, db: 'union_test' }) }) + after(() => r.getPoolMaster().drain()) + beforeEach(async function () { + await migrator() + const app = express() + app.use('/', v2) + + let username = await createUser('root', 'root') + let usermeta = username.split('#') + userId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + userToken = Buffer.from(username + ':root').toString('base64') + + username = await createUser('sudo', 'sudo') + usermeta = username.split('#') + lambdaId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + lambdaToken = Buffer.from(username + ':sudo').toString('base64') + + serverId = (await createServer('A server', 'lol.png', userId)).id + await addMemberToServer(userId, serverId) + + await new Promise(resolve => { server = app.listen(6666, resolve) }) + }) + + afterEach(async function () { + await drop() + await new Promise(resolve => { server = server.close(resolve) }) + }) + + describe('Create', function () { + it('should require authentication', async function () { + const req = await request(server).post('/servers/' + serverId + '/invites/create') + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should create an invite', async function () { + const req = await request(server).post('/servers/' + serverId + '/invites/create').set('Authorization', 'Basic ' + userToken) + assert.strictEqual(req.res.statusCode, 200) + assert.strictEqual(await r.table('invites').count().run(), 1) + }) + + it('should reject if the server does not exists', async function () { + const req = await request(server).post('/servers/666/invites/create').set('Authorization', 'Basic ' + userToken) + assert.strictEqual(req.res.statusCode, 404) + }) + + it('should reject if the user is not the server owner', async function () { + const req = await request(server).post('/servers/' + serverId + '/invites/create').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 403) + assert.strictEqual(await r.table('invites').count().run(), 0) + }) + }) + + describe('Accept', function () { + let invite + beforeEach(async function () { + invite = await generateInvite(serverId, userId) + }) + + it('should require authentication', async function () { + const req = await request(server).post('/invites/' + invite) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should join the server', async function () { + const req = await request(server).post('/invites/' + invite).set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await isInServer(lambdaId, serverId), true) + }) + + it('should reject if the invite does not exists', async function () { + const req = await request(server).post('/invites/join_this_server_pls').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 404) + }) + + it('should reject if the user is already in the server', async function () { + const req = await request(server).post('/invites/' + invite).set('Authorization', 'Basic ' + userToken) + assert.strictEqual(req.res.statusCode, 400) + }) + }) +}) diff --git a/test/r_servers.spec.js b/test/r_servers.spec.js index 1777ed7..b63d377 100644 --- a/test/r_servers.spec.js +++ b/test/r_servers.spec.js @@ -95,11 +95,6 @@ describe('Servers Controller', function () { await addMemberToServer(lambdaId, serverId) }) - afterEach(async function () { - await r.db('union_test').tableDrop('servers').run() - await r.db('union_test').tableCreate('servers').run() - }) - it('should require authentication', async function () { const req = await request(server).delete('/servers/' + serverId + '/leave') assert.strictEqual(req.res.statusCode, 401) @@ -141,11 +136,6 @@ describe('Servers Controller', function () { await addMemberToServer(lambdaId, serverId) }) - afterEach(async function () { - await r.db('union_test').tableDrop('servers').run() - await r.db('union_test').tableCreate('servers').run() - }) - it('should require authentication', async function () { const req = await request(server).delete('/servers/' + serverId) assert.strictEqual(req.res.statusCode, 401) From 486b0f0e99754c6ce75df42a2ac03c98699d1d76 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 13 Oct 2018 09:49:50 +0200 Subject: [PATCH 11/42] Finish APIv1 port to v2 --- package.json | 2 +- src/DatabaseHandler.js | 2 +- src/api_v1/messages.js | 4 ++- src/api_v1/servers.js | 14 ++++---- src/api_v2/index.js | 2 ++ src/api_v2/invites.js | 1 - src/api_v2/messages.js | 39 +++++++++++++++++++++ src/api_v2/servers.js | 3 +- test/r_messages.spec.js | 75 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 src/api_v2/messages.js create mode 100644 test/r_messages.spec.js diff --git a/package.json b/package.json index 89500ca..fe95cf9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "build/index.js", "scripts": { "test": "cross-env NODE_ENV=test mocha --require @babel/register", - "lint": "eslint **/*.js --ignore-pattern build/*", + "lint": "eslint **/*.js --ignore-pattern build/* --ignore-pattern dist/*", "setup": "yarn run build && node installer.js", "start": "cross-env NODE_ENV=production node build/index.js", "build": "babel src -d build --copy-files", diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index d5eec8e..24a7388 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -18,7 +18,7 @@ const idGenerator = new FlakeId({ * Creates a user object with the provided username and password, and stores it in the DB * @param {String} username The username of the account to create * @param {String} password The password of the account to create - * @returns {Boolean} Whether the account was created or not + * @returns {String} Whether the account was created or not */ export async function createUser (username, password) { const id = idGenerator.gen() diff --git a/src/api_v1/messages.js b/src/api_v1/messages.js index eb66b14..dc2f77d 100644 --- a/src/api_v1/messages.js +++ b/src/api_v1/messages.js @@ -1,3 +1,5 @@ +import config from '../../Configuration' + import { randomBytes } from 'crypto' import { storeMessage } from '../DatabaseHandler' import { dispatchMessage } from '../Dispatcher' @@ -11,7 +13,7 @@ export async function post (req, res) { return res.status(400).json({ 'error': 'You cannot send messages to this server' }) } - if (!content || 0 === content.trim().length) { + if (!content || content.trim().length === 0) { return res.status(400).json({ 'error': 'Content must be a string and not empty' }) } diff --git a/src/api_v1/servers.js b/src/api_v1/servers.js index 3505d3f..4fe9e1f 100644 --- a/src/api_v1/servers.js +++ b/src/api_v1/servers.js @@ -6,10 +6,10 @@ import { import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' -export async function create (req, res) { // this feels so inconsistent lul +export async function create (req, res) { // this feels so inconsistent lul const { name, iconUrl } = req.body - if (!name || 0 === name.trim().length) { + if (!name || name.trim().length === 0) { return res.status(400).json({ 'error': 'Server name cannot be empty.' }) } @@ -23,8 +23,8 @@ export async function create (req, res) { // this feels so inconsistent lul const clients = getClientsById(global.server.clients, req.user.id) - if (0 < clients.length) { - clients.forEach(ws => ws.user.servers = deduplicate(ws.user.servers, server.id)) + if (clients.length > 0) { + clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, server.id) }) dispatchServerJoin(clients, server) } } @@ -42,11 +42,11 @@ export async function leave (req, res) { const self = await getClientsById(global.server.clients, req.user.id) const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) - if (0 < members.length) { + if (members.length > 0) { dispatchMemberLeave(members, req.user.id, serverId) } - if (0 < self.length) { + if (self.length > 0) { self.forEach(ws => remove(ws.user.servers, serverId)) } @@ -66,7 +66,7 @@ export async function remove (req, res) { const clients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) - if (0 < clients.length) { + if (clients.length > 0) { clients.forEach(ws => utilRemove(ws.user.servers, serverId)) dispatchServerLeave(clients, serverId) } diff --git a/src/api_v2/index.js b/src/api_v2/index.js index 21a7172..4bbf89f 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -7,6 +7,7 @@ import serverExists from '../middlewares/serverExists' import { home, info } from './core' import { create as userCreate } from './users' +import { post as messagePost } from './messages' import { create as serverCreate, leave as serverLeave, remove as serverDelete } from './servers' import { create as inviteCreate, accept as inviteAccept } from './invites' @@ -26,6 +27,7 @@ api.post('/users/create', userCreate) // Server api.post('/servers/create', authorize, serverCreate) +api.post('/servers/:serverId/messages', serverExists, authorize, messagePost) api.delete('/servers/:serverId([0-9]+)/leave', authorize, serverExists, serverLeave) api.delete('/servers/:serverId([0-9]+)', authorize, serverExists, serverDelete) diff --git a/src/api_v2/invites.js b/src/api_v2/invites.js index 4c5061e..54b1ead 100644 --- a/src/api_v2/invites.js +++ b/src/api_v2/invites.js @@ -53,5 +53,4 @@ export async function accept (req, res) { dispatchMember(members, serverId, member) } } - } diff --git a/src/api_v2/messages.js b/src/api_v2/messages.js new file mode 100644 index 0000000..bc175e1 --- /dev/null +++ b/src/api_v2/messages.js @@ -0,0 +1,39 @@ +import config from '../../Configuration' + +import { randomBytes } from 'crypto' +import { storeMessage } from '../DatabaseHandler' +import { dispatchMessage } from '../Dispatcher' +import { filter } from '../Utils' + +export async function post (req, res) { + const { serverId } = req + const { content } = req.body + + if (!req.user.servers.includes(serverId)) { + return res.status(403).json({ error: 'You cannot send messages to this server' }) + } + + if (!content || content.trim().length === 0) { + return res.status(400).json({ error: 'Content must be a string and not empty' }) + } + + if (content.length > config.rules.messageCharacterLimit) { + return res.status(400).json({ error: `Content cannot exceed ${config.rules.messageCharacterLimit} characters` }) + } + + const id = randomBytes(15).toString('hex') + storeMessage(id, req.user.id) + + res.sendStatus(204) + + if (global.server) { + const recipients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) + dispatchMessage(recipients, { + id, + server: serverId, + content: content.trim(), + author: req.user.id, + createdAt: Date.now() + }) + } +} diff --git a/src/api_v2/servers.js b/src/api_v2/servers.js index 788b1a2..fd61028 100644 --- a/src/api_v2/servers.js +++ b/src/api_v2/servers.js @@ -1,7 +1,6 @@ import config from '../../Configuration' import { - createServer, deleteServer, getOwnedServers, getServer, isInServer, ownsServer, - removeMemberFromServer + createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer } from '../DatabaseHandler' import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' diff --git a/test/r_messages.spec.js b/test/r_messages.spec.js new file mode 100644 index 0000000..e4c026e --- /dev/null +++ b/test/r_messages.spec.js @@ -0,0 +1,75 @@ +/* eslint-env node, mocha */ +import './_hooks' + +import assert from 'assert' +import express from 'express' +import request from 'supertest' +import v2 from '../src/api_v2' + +import { migrator, drop } from '../migrations/_migrator' +import { + addMemberToServer, createServer, createUser +} from '../src/DatabaseHandler' + +let server, r, userId, userToken, serverId +describe('Messages Controller', function () { + before(() => { r = require('rethinkdbdash')({ silent: true, db: 'union_test' }) }) + after(() => r.getPoolMaster().drain()) + beforeEach(async function () { + await migrator() + const app = express() + app.use('/', v2) + + const username = await createUser('root', 'root') + const usermeta = username.split('#') + userId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + userToken = Buffer.from(username + ':root').toString('base64') + + serverId = (await createServer('A server', 'lol.png', userId)).id + await addMemberToServer(userId, serverId) + + await new Promise(resolve => { server = app.listen(6666, resolve) }) + }) + + afterEach(async function () { + await drop() + await new Promise(resolve => { server = server.close(resolve) }) + }) + + describe('Post', function () { + it('should require authentication', async function () { + const req = await request(server).post('/servers/' + serverId + '/messages') + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should post a message', async function () { + const req = await request(server).post('/servers/' + serverId + '/messages').set('Authorization', 'Basic ' + userToken).send({ content: 'Hi' }) + assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await r.table('messages').count().run(), 1) + }) + + it('should reject if the message is empty', async function () { + const req = await request(server).post('/servers/' + serverId + '/messages').set('Authorization', 'Basic ' + userToken).send({ content: '' }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('messages').count().run(), 0) + }) + + it('should reject if the message is too long', async function () { + const req = await request(server).post('/servers/' + serverId + '/messages').set('Authorization', 'Basic ' + userToken).send({ content: 'Hi'.padEnd(1500, 'i') }) + assert.strictEqual(req.res.statusCode, 400) + assert.strictEqual(await r.table('messages').count().run(), 0) + }) + + it('should reject if the server does not exists', async function () { + const req = await request(server).post('/servers/666/messages').set('Authorization', 'Basic ' + userToken).send({ content: 'Hi' }) + assert.strictEqual(req.res.statusCode, 404) + }) + + it('should reject if the user is not in the server', async function () { + const lambdaToken = Buffer.from((await createUser('sudo', 'sudo')) + ':sudo').toString('base64') + const req = await request(server).post('/servers/' + serverId + '/messages').set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 403) + assert.strictEqual(await r.table('invites').count().run(), 0) + }) + }) +}) From 929ba5f3f7b5751f146c8027f9ab420553f14097 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 13 Oct 2018 11:21:10 +0200 Subject: [PATCH 12/42] Add reCAPTCHA support (closes #8) --- Configuration.json.example | 5 +++++ package.json | 1 + src/api_v2/core.js | 6 +++++- src/api_v2/users.js | 26 ++++++++++++++++++++++++++ yarn.lock | 5 +++++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Configuration.json.example b/Configuration.json.example index f69b620..951e827 100644 --- a/Configuration.json.example +++ b/Configuration.json.example @@ -13,6 +13,11 @@ "rules": { "messageCharacterLimit": 1000, "usernameCharacterLimit": 15, + "serverCharacterLimit": 15, "maxServersPerUser": 5 + }, + "recaptcha (Remove this entirely if you don't want to setup it)": { + "key": "...", + "secret": "..." } } diff --git a/package.json b/package.json index fe95cf9..efd57ef 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "body-parser": "^1.18.3", "express": "^4.16.3", "flakeid": "git+https://github.com/Devoxin/FlakeId.git", + "node-fetch": "^2.2.0", "rethinkdbdash": "^2.3.31", "shortid": "^2.2.12", "ws": "^6.0.0" diff --git a/src/api_v2/core.js b/src/api_v2/core.js index 49b0532..bd27461 100644 --- a/src/api_v2/core.js +++ b/src/api_v2/core.js @@ -9,6 +9,10 @@ export function info (req, res) { apiVersion: 2, websocket: config.ws.port, voice: config.voicews.port, - appSettings: config.rules + appSettings: config.rules, + recaptcha: { + enabled: !!config.recaptcha, + key: config.recaptcha ? config.recaptcha.key : null + } }) } diff --git a/src/api_v2/users.js b/src/api_v2/users.js index 6770285..babeeba 100644 --- a/src/api_v2/users.js +++ b/src/api_v2/users.js @@ -1,9 +1,35 @@ import config from '../../Configuration' + +import fetch from 'node-fetch' import { createUser } from '../DatabaseHandler' export async function create (req, res) { const { username, password } = req.body + if (config.recaptcha && process.env.NODE_ENV !== 'test') { + console.log(config.recaptcha) + const gRecaptchaResponse = req.body['g-recaptcha-response'] + if (!gRecaptchaResponse) { + return res.status(400).json({ error: 'reCAPTCHA is missing' }) + } + + const response = await (await fetch('https://www.google.com/recaptcha/api/siteverify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + secret: config.recaptcha.secret, + response: gRecaptchaResponse, + remoteip: req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress + }) + })).json() + + if (!response.success) { + return res.status(400).json({ error: 'reCAPTCHA check failed' }) + } + } + // Validate data if (!username || username.trim().length === 0) { return res.status(400).json({ error: 'Username cannot be empty' }) diff --git a/yarn.lock b/yarn.lock index 58967b5..5d4b60e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2890,6 +2890,11 @@ nise@^1.4.5: path-to-regexp "^1.7.0" text-encoding "^0.6.4" +node-fetch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" + integrity sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA== + node-mocks-http@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.7.2.tgz#d4a22e61c29a1e8a0345f55f61e4b0ff94afc40b" From 397e30cbada59bcfff641e975de288035af74cf1 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 13 Oct 2018 11:46:31 +0200 Subject: [PATCH 13/42] Add developer routes (Not implemented yet) --- Configuration.json.example | 3 ++- src/api_v2/index.js | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Configuration.json.example b/Configuration.json.example index 951e827..b2c7dc9 100644 --- a/Configuration.json.example +++ b/Configuration.json.example @@ -14,7 +14,8 @@ "messageCharacterLimit": 1000, "usernameCharacterLimit": 15, "serverCharacterLimit": 15, - "maxServersPerUser": 5 + "maxServersPerUser": 5, + "maxAppsPerUser": 5 }, "recaptcha (Remove this entirely if you don't want to setup it)": { "key": "...", diff --git a/src/api_v2/index.js b/src/api_v2/index.js index 4bbf89f..58e7014 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -12,6 +12,7 @@ import { create as serverCreate, leave as serverLeave, remove as serverDelete } import { create as inviteCreate, accept as inviteAccept } from './invites' const api = express.Router() +const notImplemented = (req, res) => res.status(501).json({ error: 'This feature is not implemented yet. Contribute to help us bring this feature faster! https://github.com/Union-Chat/Union-Server' }) // Middlewares api.use(bodyParser.json()) @@ -23,16 +24,28 @@ api.get('/', home) api.get('/info', info) // User -api.post('/users/create', userCreate) +api.post('/users', userCreate) // Server -api.post('/servers/create', authorize, serverCreate) +api.post('/servers', authorize, serverCreate) api.post('/servers/:serverId/messages', serverExists, authorize, messagePost) api.delete('/servers/:serverId([0-9]+)/leave', authorize, serverExists, serverLeave) api.delete('/servers/:serverId([0-9]+)', authorize, serverExists, serverDelete) // Invites -api.post('/servers/:serverId([0-9]+)/invites/create', authorize, serverExists, inviteCreate) +api.post('/servers/:serverId([0-9]+)/invites', authorize, serverExists, inviteCreate) api.post('/invites/:inviteId([a-zA-Z0-9-_]+)', authorize, inviteAccept) +// Developers +api.post('/developers/applications', notImplemented) + +api.get('/developers/applications/:applicationId', notImplemented) +api.put('/developers/applications/:applicationId', notImplemented) +api.patch('/developers/applications/:applicationId', notImplemented) +api.delete('/developers/applications/:applicationId', notImplemented) + +api.get('/oauth2/authorize', notImplemented) +api.post('/oauth2/token', notImplemented) +api.post('/oauth2/revoke', notImplemented) + export default api From 7cf377d2c106ab0d38668686e509d519161d8693 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 13 Oct 2018 18:28:07 +0200 Subject: [PATCH 14/42] Implement user settings (hi #9) --- README.md | 16 +++-- src/DatabaseHandler.js | 28 +++++--- src/api_v2/index.js | 7 +- src/api_v2/users.js | 45 +++++++++++- test/r_invites.spec.js | 8 +-- test/r_servers.spec.js | 20 +++--- test/r_users.spec.js | 156 ++++++++++++++++++++++++++++++++++++++--- 7 files changed, 239 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 618d891..38229f2 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,14 @@ A light chat service ## Installation -To build and install everything that Union needs to work, just do `yarn run setup` -and `yarn run start` to start the whole thing + - `git clone https://github.com/Union-Chat/Union-Server` - Download Union-Server + - `yarn run setup` - Build the server & the UI, setup the database + - Create a Configuration.json with your custom settings + - `yarn run start` - Start the app! -`yarn run setup` will create the database or migrate it if it exists, build the app, -download web interface from https://github.com/Union-Chat/union-react and generate -a default `Confuguration.json` +## Updating -`yarn run setup` can also be used to update your installation of Union (But it'll -not download the latest version of Union-Server) + - `git pull` - Download the latest Union-Server + - `yarn run setup` - Re-build everything (**Will keep your database safe** and update it) + - Check if there is no new fields in Configuration.json + - `yarn run start` - Start the app! diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index 24a7388..697fd11 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -41,15 +41,21 @@ export async function createUser (username, password) { return `${username}#${discriminator}` } -async function rollDiscriminator (username, id) { - const discriminator = id.substring(id.length - 4) // hahayes lazy discrim generating method - const isDiscrimTaken = await r.table('users').filter({ username, discriminator }).count().gt(0) +export async function updateUser (id, username, password) { + const discriminator = await rollDiscriminator(username) + const update = { username, discriminator } + if (password) Object.assign(update, { password: await hash(password, 10) }) + await r.table('users').get(id).update(update) + return `${username}#${discriminator}` +} - if (!isDiscrimTaken) { - return discriminator - } else { - return null +async function rollDiscriminator (username) { + let discriminator + while (true) { + discriminator = Math.floor(Math.random() * 9999 + 1).toString().padStart(4, '0') + if (!await r.table('users').filter({ username, discriminator }).count().gt(0)) break } + return discriminator } /** @@ -255,6 +261,10 @@ export async function deleteServer (serverId) { }) } +export async function deleteUser (userId) { + await r.table('users').get(userId).delete() +} + /** * Checks if the given user is the owner of the given server * @param {String} username The name of the user to check @@ -305,8 +315,8 @@ export function getInvite (code) { return r.table('invites').get(code) } -export function storeMessage (id, author) { - r.table('messages').insert({ id, author }).run() +export async function storeMessage (id, author) { + await r.table('messages').insert({ id, author }).run() } export function retrieveMessage (id) { diff --git a/src/api_v2/index.js b/src/api_v2/index.js index 58e7014..bbb23b9 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -6,7 +6,7 @@ import authorize from '../middlewares/authorize' import serverExists from '../middlewares/serverExists' import { home, info } from './core' -import { create as userCreate } from './users' +import { create as userCreate, getSelf as userGetSelf, patch as userPatch, remove as userDelete } from './users' import { post as messagePost } from './messages' import { create as serverCreate, leave as serverLeave, remove as serverDelete } from './servers' import { create as inviteCreate, accept as inviteAccept } from './invites' @@ -25,6 +25,10 @@ api.get('/info', info) // User api.post('/users', userCreate) +api.get('/users/self', authorize, userGetSelf) +api.put('/users/self', authorize, userPatch) +api.patch('/users/self', authorize, userPatch) +api.delete('/users/self', authorize, userDelete) // Server api.post('/servers', authorize, serverCreate) @@ -37,6 +41,7 @@ api.post('/servers/:serverId([0-9]+)/invites', authorize, serverExists, inviteCr api.post('/invites/:inviteId([a-zA-Z0-9-_]+)', authorize, inviteAccept) // Developers +api.get('/developers/applications', notImplemented) api.post('/developers/applications', notImplemented) api.get('/developers/applications/:applicationId', notImplemented) diff --git a/src/api_v2/users.js b/src/api_v2/users.js index babeeba..fff2466 100644 --- a/src/api_v2/users.js +++ b/src/api_v2/users.js @@ -1,7 +1,8 @@ import config from '../../Configuration' import fetch from 'node-fetch' -import { createUser } from '../DatabaseHandler' +import { createUser, deleteUser, updateUser } from '../DatabaseHandler' +import { compare } from 'bcrypt' export async function create (req, res) { const { username, password } = req.body @@ -52,3 +53,45 @@ export async function create (req, res) { .then(id => res.status(200).json({ id })) .catch(err => res.status(400).json({ error: err.message })) } + +export async function getSelf (req, res) { + res.json(Object.assign({}, req.user, { password: undefined })) +} + +export async function patch (req, res) { + const { password, username, newPassword } = req.body + if (!password || !await compare(password, req.user.password)) { + return res.sendStatus(401) + } + + if (username !== undefined) { + if (username.trim().length === 0) { + return res.status(400).json({ error: 'Username cannot be empty' }) + } + + if (username.trim().length > config.rules.usernameCharacterLimit) { + return res.status(400).json({ error: `Username cannot exceed ${config.rules.usernameCharacterLimit} characters` }) + } + + if ([':', '#'].some(char => username.includes(char))) { + return res.status(400).json({ error: 'Your username contains prohibited characters' }) + } + } + + if (newPassword !== undefined && newPassword.length < 5) { + return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more' }) + } + + const newUser = await updateUser(req.user.id, username || req.user.username, newPassword) + res.status(200).send(newUser) +} + +export async function remove (req, res) { + const { password } = req.body + if (!password || !await compare(password, req.user.password)) { + return res.sendStatus(401) + } + + await deleteUser(req.user.id) + res.sendStatus(204) +} diff --git a/test/r_invites.spec.js b/test/r_invites.spec.js index f5c244f..b527d88 100644 --- a/test/r_invites.spec.js +++ b/test/r_invites.spec.js @@ -43,23 +43,23 @@ describe('Invites Controller', function () { describe('Create', function () { it('should require authentication', async function () { - const req = await request(server).post('/servers/' + serverId + '/invites/create') + const req = await request(server).post('/servers/' + serverId + '/invites') assert.strictEqual(req.res.statusCode, 401) }) it('should create an invite', async function () { - const req = await request(server).post('/servers/' + serverId + '/invites/create').set('Authorization', 'Basic ' + userToken) + const req = await request(server).post('/servers/' + serverId + '/invites').set('Authorization', 'Basic ' + userToken) assert.strictEqual(req.res.statusCode, 200) assert.strictEqual(await r.table('invites').count().run(), 1) }) it('should reject if the server does not exists', async function () { - const req = await request(server).post('/servers/666/invites/create').set('Authorization', 'Basic ' + userToken) + const req = await request(server).post('/servers/666/invites').set('Authorization', 'Basic ' + userToken) assert.strictEqual(req.res.statusCode, 404) }) it('should reject if the user is not the server owner', async function () { - const req = await request(server).post('/servers/' + serverId + '/invites/create').set('Authorization', 'Basic ' + lambdaToken) + const req = await request(server).post('/servers/' + serverId + '/invites').set('Authorization', 'Basic ' + lambdaToken) assert.strictEqual(req.res.statusCode, 403) assert.strictEqual(await r.table('invites').count().run(), 0) }) diff --git a/test/r_servers.spec.js b/test/r_servers.spec.js index b63d377..9f59071 100644 --- a/test/r_servers.spec.js +++ b/test/r_servers.spec.js @@ -36,12 +36,12 @@ describe('Servers Controller', function () { describe('Create', function () { it('should require authentication', async function () { - const req = await request(server).post('/servers/create') + const req = await request(server).post('/servers') assert.strictEqual(req.res.statusCode, 401) }) it('should create a server with correct owner id', async function () { - const req = await request(server).post('/servers/create').send({ + const req = await request(server).post('/servers').send({ name: 'A server', iconUrl: 'lol.png' }).set('Authorization', 'Basic ' + token) @@ -52,7 +52,7 @@ describe('Servers Controller', function () { }) it('should reject empty name', async function () { - const req = await request(server).post('/servers/create').send({ + const req = await request(server).post('/servers').send({ name: '', iconUrl: 'lol.png' }).set('Authorization', 'Basic ' + token) @@ -61,7 +61,7 @@ describe('Servers Controller', function () { }) it('should reject too long names', async function () { - const req = await request(server).post('/servers/create').send({ + const req = await request(server).post('/servers').send({ name: 'A serverrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', iconUrl: 'lol.png' }).set('Authorization', 'Basic ' + token) @@ -71,12 +71,12 @@ describe('Servers Controller', function () { it('should prevent user from creating ton of servers', async function () { const serverData = { name: 'A server', iconUrl: 'lol.png' } - await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) - await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) - await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) - await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) - await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) - const req = await request(server).post('/servers/create').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) + await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) + const req = await request(server).post('/servers').send(serverData).set('Authorization', 'Basic ' + token) assert.strictEqual(req.res.statusCode, 400) assert.strictEqual(await r.table('servers').count().run(), 5) diff --git a/test/r_users.spec.js b/test/r_users.spec.js index 167feba..ddf3193 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -7,6 +7,8 @@ import request from 'supertest' import v2 from '../src/api_v2' import { migrator, drop } from '../migrations/_migrator' +import { createUser } from '../src/DatabaseHandler' +import { hash } from 'bcrypt' let server describe('Users Controller', function () { @@ -27,27 +29,33 @@ describe('Users Controller', function () { describe('Create', function () { it('should create an user', async function () { - const req = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) + const req = await request(server).post('/users').send({ username: 'root', password: 'a_secure_password' }) assert.strictEqual(req.res.statusCode, 200) assert.strictEqual(await r.table('users').count().run(), 1) }) it('should reject empty username', async function () { - const req = await request(server).post('/users/create').send({ username: '', password: 'a_secure_password' }) + const req = await request(server).post('/users').send({ username: '', password: 'a_secure_password' }) assert.strictEqual(req.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 0) }) it('should reject invalid username', async function () { - const req1 = await request(server).post('/users/create').send({ username: 'ro:ot', password: 'a_secure_password' }) - const req2 = await request(server).post('/users/create').send({ username: 'ro#ot', password: 'a_secure_password' }) + const req1 = await request(server).post('/users').send({ + username: 'ro:ot', + password: 'a_secure_password' + }) + const req2 = await request(server).post('/users').send({ + username: 'ro#ot', + password: 'a_secure_password' + }) assert.strictEqual(req1.res.statusCode, 400) assert.strictEqual(req2.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 0) }) it('should reject too long usernames', async function () { - const req = await request(server).post('/users/create').send({ + const req = await request(server).post('/users').send({ username: 'rooooooooooooooooooooooooooooooooooooooooooooooooooooot', password: 'a_secure_password' }) @@ -56,23 +64,153 @@ describe('Users Controller', function () { }) it('should reject empty password', async function () { - const req = await request(server).post('/users/create').send({ username: 'root', password: '' }) + const req = await request(server).post('/users').send({ username: 'root', password: '' }) assert.strictEqual(req.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 0) }) it('should reject short passwords', async function () { - const req = await request(server).post('/users/create').send({ username: 'root', password: 'lol' }) + const req = await request(server).post('/users').send({ username: 'root', password: 'lol' }) assert.strictEqual(req.res.statusCode, 400) assert.strictEqual(await r.table('users').count().run(), 0) }) it('should allow duplicate usernames', async function () { - const req1 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) - const req2 = await request(server).post('/users/create').send({ username: 'root', password: 'a_secure_password' }) + const req1 = await request(server).post('/users').send({ username: 'root', password: 'a_secure_password' }) + const req2 = await request(server).post('/users').send({ username: 'root', password: 'a_secure_password' }) assert.strictEqual(req1.res.statusCode, 200) assert.strictEqual(req2.res.statusCode, 200) assert.strictEqual(await r.table('users').count().run(), 2) }) }) + + describe('Self', function () { + let token + beforeEach(async function () { + token = Buffer.from((await createUser('root', 'root')) + ':root').toString('base64') + }) + + describe('Get', function () { + it('should require authentication', async function () { + const req = await request(server).get('/users/self') + assert.strictEqual(req.res.statusCode, 401) + }) + it('should send current user data', async function () { + const req = await request(server).get('/users/self').set('Authorization', 'Basic ' + token) + const json = JSON.parse(req.res.text) + assert.strictEqual(req.res.statusCode, 200) + assert.deepStrictEqual(Object.keys(json).sort(), ['id', 'username', 'discriminator', 'createdAt', 'servers', 'online'].sort()) + }) + }) + + describe('Update', function () { + it('should require authentication', async function () { + const req = await request(server).patch('/users/self') + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should require authentication (Authorization header)', async function () { + const req = await request(server).patch('/users/self').send({ password: 'root' }) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should require extra authentication (Password)', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should reject invalid password', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ password: 'lol' }) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should update the current user', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'r00t', + password: 'root', + newPassword: 'rooooot' + }) + assert.strictEqual(req.res.statusCode, 200) + }) + + it('should reject empty username', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: '', + password: 'root' + }) + assert.strictEqual(req.res.statusCode, 400) + }) + + it('should reject invalid username', async function () { + const req1 = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'ro:ot', + password: 'root' + }) + const req2 = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'ro#ot', + password: 'root' + }) + assert.strictEqual(req1.res.statusCode, 400) + assert.strictEqual(req2.res.statusCode, 400) + }) + + it('should reject too long usernames', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'rooooooooooooooooooooooooooooooooooooooooooooooooooooot', + password: 'root' + }) + assert.strictEqual(req.res.statusCode, 400) + }) + + it('should reject empty password', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'r00t', + password: 'root', + newPassword: '' + }) + assert.strictEqual(req.res.statusCode, 400) + }) + + it('should reject short passwords', async function () { + const req = await request(server).patch('/users/self').set('Authorization', 'Basic ' + token).send({ + username: 'r00t', + password: 'root', + newPassword: 'a' + }) + assert.strictEqual(req.res.statusCode, 400) + }) + }) + + describe('Delete', function () { + it('should require authentication', async function () { + const req = await request(server).delete('/users/self') + assert.strictEqual(req.res.statusCode, 401) + assert.strictEqual(await r.table('users').count().run(), 1) + }) + + it('should require authentication (Authorization header)', async function () { + const req = await request(server).delete('/users/self').send({ password: 'root' }) + assert.strictEqual(req.res.statusCode, 401) + assert.strictEqual(await r.table('users').count().run(), 1) + }) + + it('should require extra authentication (Password)', async function () { + const req = await request(server).delete('/users/self').set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 401) + assert.strictEqual(await r.table('users').count().run(), 1) + }) + + it('should reject invalid password', async function () { + const req = await request(server).delete('/users/self').set('Authorization', 'Basic ' + token).send({ password: 'lol' }) + assert.strictEqual(req.res.statusCode, 401) + assert.strictEqual(await r.table('users').count().run(), 1) + }) + + it('should delete the current user', async function () { + const req = await request(server).delete('/users/self').set('Authorization', 'Basic ' + token).send({ password: 'root' }) + assert.strictEqual(req.res.statusCode, 204) + assert.strictEqual(await r.table('users').count().run(), 0) + }) + }) + }) }) From 2bcafb019fd4520651a1b2182b2531db877792da Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 13 Oct 2018 19:25:04 +0200 Subject: [PATCH 15/42] Implement server settings (closes #9) --- src/DatabaseHandler.js | 10 ++++++++- src/api_v2/index.js | 6 ++++-- src/api_v2/servers.js | 23 +++++++++++++++++++- src/api_v2/users.js | 4 ++-- test/r_servers.spec.js | 49 ++++++++++++++++++++++++++++++++++++++++++ test/r_users.spec.js | 1 - 6 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/DatabaseHandler.js b/src/DatabaseHandler.js index 697fd11..3bfb542 100644 --- a/src/DatabaseHandler.js +++ b/src/DatabaseHandler.js @@ -41,10 +41,11 @@ export async function createUser (username, password) { return `${username}#${discriminator}` } -export async function updateUser (id, username, password) { +export async function updateUser (id, username, password, avatarUrl) { const discriminator = await rollDiscriminator(username) const update = { username, discriminator } if (password) Object.assign(update, { password: await hash(password, 10) }) + if (avatarUrl) Object.assign(update, avatarUrl) await r.table('users').get(id).update(update) return `${username}#${discriminator}` } @@ -83,6 +84,13 @@ export async function createServer (name, iconUrl, owner) { return getServer(id) } +export async function updateServer (id, name, iconUrl) { + const update = {} + if (name) Object.assign(update, name) + if (iconUrl) Object.assign(update, iconUrl) + await r.table('servers').get(id).update(update) +} + /** * Adds a member to a server * @param {String} username The member to add to the server diff --git a/src/api_v2/index.js b/src/api_v2/index.js index bbb23b9..b96d1a5 100644 --- a/src/api_v2/index.js +++ b/src/api_v2/index.js @@ -8,7 +8,7 @@ import serverExists from '../middlewares/serverExists' import { home, info } from './core' import { create as userCreate, getSelf as userGetSelf, patch as userPatch, remove as userDelete } from './users' import { post as messagePost } from './messages' -import { create as serverCreate, leave as serverLeave, remove as serverDelete } from './servers' +import { create as serverCreate, leave as serverLeave, remove as serverDelete, patch as serverPatch } from './servers' import { create as inviteCreate, accept as inviteAccept } from './invites' const api = express.Router() @@ -32,9 +32,11 @@ api.delete('/users/self', authorize, userDelete) // Server api.post('/servers', authorize, serverCreate) -api.post('/servers/:serverId/messages', serverExists, authorize, messagePost) +api.put('/servers/:serverId([0-9]+)', authorize, serverExists, serverPatch) +api.patch('/servers/:serverId([0-9]+)', authorize, serverExists, serverPatch) api.delete('/servers/:serverId([0-9]+)/leave', authorize, serverExists, serverLeave) api.delete('/servers/:serverId([0-9]+)', authorize, serverExists, serverDelete) +api.post('/servers/:serverId/messages', serverExists, authorize, messagePost) // Invites api.post('/servers/:serverId([0-9]+)/invites', authorize, serverExists, inviteCreate) diff --git a/src/api_v2/servers.js b/src/api_v2/servers.js index fd61028..47eb2be 100644 --- a/src/api_v2/servers.js +++ b/src/api_v2/servers.js @@ -1,6 +1,6 @@ import config from '../../Configuration' import { - createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer + createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer, updateServer } from '../DatabaseHandler' import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' @@ -31,6 +31,27 @@ export async function create (req, res) { } } +export async function patch (req, res) { + const { name, iconUrl } = req.body + + if (!await ownsServer(req.user.id, req.serverId)) { + return res.status(403).json({ error: 'You don\'t own this server' }) + } + + if (name !== undefined) { + if (name.trim().length === 0) { + return res.status(400).json({ error: 'Server name cannot be empty' }) + } + + if (name.trim().length > config.rules.serverCharacterLimit) { + return res.status(400).json({ error: `Username cannot exceed ${config.rules.serverCharacterLimit} characters` }) + } + } + + await updateServer(req.serverId, name, iconUrl) + res.sendStatus(204) +} + export async function leave (req, res) { const { serverId } = req diff --git a/src/api_v2/users.js b/src/api_v2/users.js index fff2466..3e63285 100644 --- a/src/api_v2/users.js +++ b/src/api_v2/users.js @@ -59,7 +59,7 @@ export async function getSelf (req, res) { } export async function patch (req, res) { - const { password, username, newPassword } = req.body + const { password, username, newPassword, avatarUrl } = req.body if (!password || !await compare(password, req.user.password)) { return res.sendStatus(401) } @@ -82,7 +82,7 @@ export async function patch (req, res) { return res.status(400).json({ error: 'Password cannot be empty and must be 5 characters long or more' }) } - const newUser = await updateUser(req.user.id, username || req.user.username, newPassword) + const newUser = await updateUser(req.user.id, username || req.user.username, newPassword, avatarUrl) res.status(200).send(newUser) } diff --git a/test/r_servers.spec.js b/test/r_servers.spec.js index 9f59071..31e936c 100644 --- a/test/r_servers.spec.js +++ b/test/r_servers.spec.js @@ -83,6 +83,55 @@ describe('Servers Controller', function () { }) }) + describe('Update', function () { + let serverId, lambdaToken + beforeEach(async function () { + const username = await createUser('sudo', 'sudo') + const usermeta = username.split('#') + lambdaToken = Buffer.from(username + ':sudo').toString('base64') + const lambdaId = (await r.table('users').filter({ username: usermeta[0], discriminator: usermeta[1] }).nth(0)).id + serverId = (await createServer('A server', 'lol.png', userId)).id + await addMemberToServer(lambdaId, serverId) + }) + + it('should require authentication', async function () { + const req = await request(server).patch('/servers/' + serverId) + assert.strictEqual(req.res.statusCode, 401) + }) + + it('should reject if the server does not exists', async function () { + const req = await request(server).patch('/servers/666').set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 404) + }) + + it('should reject if the user is the server owner', async function () { + const req = await request(server).patch('/servers/' + serverId).set('Authorization', 'Basic ' + lambdaToken) + assert.strictEqual(req.res.statusCode, 403) + }) + + it('should update the server', async function () { + const req = await request(server).patch('/servers/' + serverId).send({ + name: 'A new name', + iconUrl: 'a_new_icon.png' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 204) + }) + + it('should reject empty name', async function () { + const req = await request(server).patch('/servers/' + serverId).send({ + name: '' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 400) + }) + + it('should reject too long names', async function () { + const req = await request(server).patch('/servers/' + serverId).send({ + name: 'A serverrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr' + }).set('Authorization', 'Basic ' + token) + assert.strictEqual(req.res.statusCode, 400) + }) + }) + describe('Leave', function () { let serverId, lambdaId, lambdaToken beforeEach(async function () { diff --git a/test/r_users.spec.js b/test/r_users.spec.js index ddf3193..ac827bb 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -8,7 +8,6 @@ import v2 from '../src/api_v2' import { migrator, drop } from '../migrations/_migrator' import { createUser } from '../src/DatabaseHandler' -import { hash } from 'bcrypt' let server describe('Users Controller', function () { From 0020e7b9b9fcff7065ba51edcd1a4b4e6d51ad26 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Mon, 15 Oct 2018 17:32:40 +0200 Subject: [PATCH 16/42] Finally remove support for API v1 I can't find a clean way to support two versions of the websocket server. Partial support is just a waste of time, so let's do a breaking update --- src/api.js | 5 +-- src/{api_v2 => api}/core.js | 0 src/{api_v2 => api}/index.js | 0 src/{api_v2 => api}/invites.js | 0 src/{api_v2 => api}/messages.js | 0 src/{api_v2 => api}/servers.js | 0 src/{api_v2 => api}/users.js | 0 src/api_v1/core.js | 15 ------- src/api_v1/index.js | 41 ------------------ src/api_v1/invites.js | 56 ------------------------- src/api_v1/messages.js | 38 ----------------- src/api_v1/servers.js | 73 --------------------------------- src/api_v1/users.js | 26 ------------ test/r_core.spec.js | 2 +- test/r_invites.spec.js | 2 +- test/r_messages.spec.js | 2 +- test/r_servers.spec.js | 2 +- test/r_users.spec.js | 2 +- 18 files changed, 7 insertions(+), 257 deletions(-) rename src/{api_v2 => api}/core.js (100%) rename src/{api_v2 => api}/index.js (100%) rename src/{api_v2 => api}/invites.js (100%) rename src/{api_v2 => api}/messages.js (100%) rename src/{api_v2 => api}/servers.js (100%) rename src/{api_v2 => api}/users.js (100%) delete mode 100644 src/api_v1/core.js delete mode 100644 src/api_v1/index.js delete mode 100644 src/api_v1/invites.js delete mode 100644 src/api_v1/messages.js delete mode 100644 src/api_v1/servers.js delete mode 100644 src/api_v1/users.js diff --git a/src/api.js b/src/api.js index 4393271..3bbaabe 100644 --- a/src/api.js +++ b/src/api.js @@ -1,10 +1,9 @@ import express from 'express' -import v1 from './api_v1' -import v2 from './api_v2' +import v2 from './api' const api = express.Router() -api.use('/', v1) +api.use('/', v2) api.use('/v2', v2) export default api diff --git a/src/api_v2/core.js b/src/api/core.js similarity index 100% rename from src/api_v2/core.js rename to src/api/core.js diff --git a/src/api_v2/index.js b/src/api/index.js similarity index 100% rename from src/api_v2/index.js rename to src/api/index.js diff --git a/src/api_v2/invites.js b/src/api/invites.js similarity index 100% rename from src/api_v2/invites.js rename to src/api/invites.js diff --git a/src/api_v2/messages.js b/src/api/messages.js similarity index 100% rename from src/api_v2/messages.js rename to src/api/messages.js diff --git a/src/api_v2/servers.js b/src/api/servers.js similarity index 100% rename from src/api_v2/servers.js rename to src/api/servers.js diff --git a/src/api_v2/users.js b/src/api/users.js similarity index 100% rename from src/api_v2/users.js rename to src/api/users.js diff --git a/src/api_v1/core.js b/src/api_v1/core.js deleted file mode 100644 index bb654ba..0000000 --- a/src/api_v1/core.js +++ /dev/null @@ -1,15 +0,0 @@ -import config from '../../Configuration' - -export function home (req, res) { - res.send('Welcome to the Union API!') - // TODO: Serve docs or something -} - -export function info (req, res) { - res.send({ - apiVersion: 1, - websocket: config.ws.port, - voice: config.voicews.port, - appSettings: config.rules - }) -} diff --git a/src/api_v1/index.js b/src/api_v1/index.js deleted file mode 100644 index a2f04db..0000000 --- a/src/api_v1/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import express from 'express' -import bodyParser from 'body-parser' - -import authorize from '../middlewares/authorize' -import allowCORS from '../middlewares/cors' -import validateServer from '../middlewares/serverExists' - -import { home, info } from './core' -import { create as userCreate } from './users' -import { create as serverCreate, remove as serverRemove, leave as serverLeave } from './servers' -import { create as inviteCreate, accept as inviteAccept } from './invites' -import { post as messagePost } from './messages' - -const api = express.Router() - -// Middlewares -api.use(bodyParser.json()) -api.use(bodyParser.urlencoded({ extended: true })) -api.use(allowCORS) - -// Core -api.get('/', home) -api.get('/info', info) - -// User -api.post('/create', userCreate) -api.patch('/self', (req, res) => res.send('Not done.')) - -// Servers -api.post('/server', authorize, serverCreate) -api.delete('/server/:serverId', validateServer, authorize, serverRemove) -api.delete('/self/server/:serverId', validateServer, authorize, serverLeave) - -api.post('/server/:serverId/invite', validateServer, authorize, inviteCreate) - -api.post('/server/:serverId/messages', validateServer, authorize, messagePost) - -// Invites -api.post('/invites/:inviteId', authorize, inviteAccept) - -export default api diff --git a/src/api_v1/invites.js b/src/api_v1/invites.js deleted file mode 100644 index 086b9fd..0000000 --- a/src/api_v1/invites.js +++ /dev/null @@ -1,56 +0,0 @@ -import { - addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, - ownsServer -} from '../DatabaseHandler' -import { dispatchMember, dispatchServerJoin } from '../Dispatcher' -import { deduplicate, filter, getClientsById } from '../Utils' -import serverExists from '../middlewares/serverExists' - -export async function create (req, res) { - const { serverId } = req - - if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ 'error': 'Only the server owner can generate invites.' }) - } - - const inviteCode = await generateInvite(serverId, req.user.id) - - res.status(200).send({ code: inviteCode }) -} - -export async function accept (req, res) { - const invite = await getInvite(req.params.inviteId) - - if (!invite) { - return res.status(404).json({ 'error': 'Unknown invite' }) - } - - if (!await serverExists(invite.serverId)) { - return res.status(404).json({ 'error': 'Unknown server; invite expired' }) - } - - const { serverId } = invite - - if (await isInServer(req.user.id, serverId)) { - return res.status(200).send() - } - - await addMemberToServer(req.user.id, serverId) - - const clients = await getClientsById(global.server.clients, req.user.id) - const server = await getServer(serverId) - - if (clients.length > 0) { - clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, serverId) }) - dispatchServerJoin(clients, server) - } - - const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId) && ws.user.id !== req.user.id) - const member = await getMember(req.user.id) - - if (members.length > 0) { - dispatchMember(members, serverId, member) - } - - res.status(200).send() -} diff --git a/src/api_v1/messages.js b/src/api_v1/messages.js deleted file mode 100644 index dc2f77d..0000000 --- a/src/api_v1/messages.js +++ /dev/null @@ -1,38 +0,0 @@ -import config from '../../Configuration' - -import { randomBytes } from 'crypto' -import { storeMessage } from '../DatabaseHandler' -import { dispatchMessage } from '../Dispatcher' -import { filter } from '../Utils' - -export async function post (req, res) { - const { serverId } = req - const { content } = req.body - - if (!req.user.servers.includes(serverId)) { - return res.status(400).json({ 'error': 'You cannot send messages to this server' }) - } - - if (!content || content.trim().length === 0) { - return res.status(400).json({ 'error': 'Content must be a string and not empty' }) - } - - if (content.length > config.rules.messageCharacterLimit) { - return res.status(400).json({ 'error': `Content cannot exceed ${config.rules.messageCharacterLimit} characters` }) - } - - const id = randomBytes(15).toString('hex') - storeMessage(id, req.user.id) - - const message = { - id, - server: serverId, - content: content.trim(), - author: req.user.id, - createdAt: Date.now() - } - - const recipients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) - dispatchMessage(recipients, message) - res.status(200).send() -} diff --git a/src/api_v1/servers.js b/src/api_v1/servers.js deleted file mode 100644 index 4fe9e1f..0000000 --- a/src/api_v1/servers.js +++ /dev/null @@ -1,73 +0,0 @@ -import config from '../../Configuration' -import { - createServer, deleteServer, getOwnedServers, isInServer, ownsServer, - removeMemberFromServer -} from '../DatabaseHandler' -import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' -import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' - -export async function create (req, res) { // this feels so inconsistent lul - const { name, iconUrl } = req.body - - if (!name || name.trim().length === 0) { - return res.status(400).json({ 'error': 'Server name cannot be empty.' }) - } - - if (await getOwnedServers(req.user.id) >= config.rules.maxServersPerUser) { - return res.status(400).json({ 'error': `You cannot create more than ${config.rules.maxServersPerUser} servers` }) - } - - const server = await createServer(name, iconUrl, req.user.id) - - res.status(200).send() - - const clients = getClientsById(global.server.clients, req.user.id) - - if (clients.length > 0) { - clients.forEach(ws => { ws.user.servers = deduplicate(ws.user.servers, server.id) }) - dispatchServerJoin(clients, server) - } -} - -export async function leave (req, res) { - const { serverId } = req - - if (!await isInServer(req.user.id, serverId)) { - return res.status(400).json({ 'error': 'You are not in that server' }) - } - - await removeMemberFromServer(req.user.id, serverId) - dispatchServerLeave(getClientsById(global.server.clients, req.user.id), serverId) - - const self = await getClientsById(global.server.clients, req.user.id) - const members = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) - - if (members.length > 0) { - dispatchMemberLeave(members, req.user.id, serverId) - } - - if (self.length > 0) { - self.forEach(ws => remove(ws.user.servers, serverId)) - } - - res.status(200).send() -} - -export async function remove (req, res) { - const { serverId } = req - - if (!await ownsServer(req.user.id, serverId)) { - return res.status(403).json({ 'error': 'You can only delete servers that you own.' }) - } - - await deleteServer(serverId) - - res.status(200).send() - - const clients = filter(global.server.clients, ws => ws.isAuthenticated && ws.user.servers.includes(serverId)) - - if (clients.length > 0) { - clients.forEach(ws => utilRemove(ws.user.servers, serverId)) - dispatchServerLeave(clients, serverId) - } -} diff --git a/src/api_v1/users.js b/src/api_v1/users.js deleted file mode 100644 index 24d9f9b..0000000 --- a/src/api_v1/users.js +++ /dev/null @@ -1,26 +0,0 @@ -import config from '../../Configuration' -import { createUser } from '../DatabaseHandler' - -export async function create (req, res) { - const { username, password } = req.body - - if (!username || username.trim().length === 0) { - return res.status(400).json({ 'error': 'Username cannot be empty.' }) - } - - if (username.trim().length > config.rules.usernameCharacterLimit) { - return res.status(400).json({ 'error': `Username cannot exceed ${config.rules.usernameCharacterLimit} characters.` }) - } - - if ([':', '#'].some(char => username.includes(char))) { - return res.status(400).json({ 'error': 'Your username contains prohibited characters.' }) - } - - if (!password || password.length < 5) { - return res.status(400).json({ 'error': 'Password cannot be empty and must be 5 characters long or more.' }) - } - - createUser(username.trim(), password) - .then(id => res.status(200).send(id)) - .catch(err => res.status(400).json({ 'error': err.message })) -} diff --git a/test/r_core.spec.js b/test/r_core.spec.js index 651bdcb..cde3303 100644 --- a/test/r_core.spec.js +++ b/test/r_core.spec.js @@ -4,7 +4,7 @@ import './_hooks' import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/api_v2' +import v2 from '../src/api' let server describe('Core Controller', function () { diff --git a/test/r_invites.spec.js b/test/r_invites.spec.js index b527d88..6ce49ff 100644 --- a/test/r_invites.spec.js +++ b/test/r_invites.spec.js @@ -4,7 +4,7 @@ import './_hooks' import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/api_v2' +import v2 from '../src/api' import { migrator, drop } from '../migrations/_migrator' import { diff --git a/test/r_messages.spec.js b/test/r_messages.spec.js index e4c026e..9e3634c 100644 --- a/test/r_messages.spec.js +++ b/test/r_messages.spec.js @@ -4,7 +4,7 @@ import './_hooks' import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/api_v2' +import v2 from '../src/api' import { migrator, drop } from '../migrations/_migrator' import { diff --git a/test/r_servers.spec.js b/test/r_servers.spec.js index 31e936c..d0391f7 100644 --- a/test/r_servers.spec.js +++ b/test/r_servers.spec.js @@ -4,7 +4,7 @@ import './_hooks' import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/api_v2' +import v2 from '../src/api' import { migrator, drop } from '../migrations/_migrator' import { diff --git a/test/r_users.spec.js b/test/r_users.spec.js index ac827bb..ee94ebd 100644 --- a/test/r_users.spec.js +++ b/test/r_users.spec.js @@ -4,7 +4,7 @@ import './_hooks' import assert from 'assert' import express from 'express' import request from 'supertest' -import v2 from '../src/api_v2' +import v2 from '../src/api' import { migrator, drop } from '../migrations/_migrator' import { createUser } from '../src/DatabaseHandler' From aa67e788718f22e74fe269b1f8b9d07e8214a22c Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Mon, 15 Oct 2018 17:41:39 +0200 Subject: [PATCH 17/42] Move websocket in a subfolder --- OpCodes.json => opcodes.json | 0 src/EventHandler.js | 2 +- src/api/invites.js | 2 +- src/api/messages.js | 2 +- src/api/servers.js | 2 +- src/{Dispatcher.js => socket/dispatcher.js} | 6 +++--- src/{socket.js => socket/index.js} | 13 ++++++------- src/socket/receiver.js | 0 8 files changed, 13 insertions(+), 14 deletions(-) rename OpCodes.json => opcodes.json (100%) rename src/{Dispatcher.js => socket/dispatcher.js} (92%) rename src/{socket.js => socket/index.js} (87%) create mode 100644 src/socket/receiver.js diff --git a/OpCodes.json b/opcodes.json similarity index 100% rename from OpCodes.json rename to opcodes.json diff --git a/src/EventHandler.js b/src/EventHandler.js index 951c6f4..a93fdc4 100644 --- a/src/EventHandler.js +++ b/src/EventHandler.js @@ -1,5 +1,5 @@ const { getSessionsOf } = require('./Utils.js') -const { dispatchPresenceUpdate } = require('./Dispatcher.js') +const { dispatchPresenceUpdate } = require('./socket/dispatcher.js') const { getUser, updatePresenceOf } = require('./DatabaseHandler.js') async function handlePresenceUpdate (userId, clients) { diff --git a/src/api/invites.js b/src/api/invites.js index 54b1ead..b5c18c9 100644 --- a/src/api/invites.js +++ b/src/api/invites.js @@ -2,7 +2,7 @@ import { addMemberToServer, generateInvite, getInvite, getMember, getServer, isInServer, ownsServer, serverExists } from '../DatabaseHandler' -import { dispatchMember, dispatchServerJoin } from '../Dispatcher' +import { dispatchMember, dispatchServerJoin } from '../socket/dispatcher' import { deduplicate, filter, getClientsById } from '../Utils' export async function create (req, res) { diff --git a/src/api/messages.js b/src/api/messages.js index bc175e1..c307e08 100644 --- a/src/api/messages.js +++ b/src/api/messages.js @@ -2,7 +2,7 @@ import config from '../../Configuration' import { randomBytes } from 'crypto' import { storeMessage } from '../DatabaseHandler' -import { dispatchMessage } from '../Dispatcher' +import { dispatchMessage } from '../socket/dispatcher' import { filter } from '../Utils' export async function post (req, res) { diff --git a/src/api/servers.js b/src/api/servers.js index 47eb2be..2deff81 100644 --- a/src/api/servers.js +++ b/src/api/servers.js @@ -3,7 +3,7 @@ import { createServer, deleteServer, getOwnedServers, isInServer, ownsServer, removeMemberFromServer, updateServer } from '../DatabaseHandler' import { deduplicate, filter, getClientsById, remove as utilRemove } from '../Utils' -import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../Dispatcher' +import { dispatchMemberLeave, dispatchServerJoin, dispatchServerLeave } from '../socket/dispatcher' export async function create (req, res) { const { name, iconUrl } = req.body diff --git a/src/Dispatcher.js b/src/socket/dispatcher.js similarity index 92% rename from src/Dispatcher.js rename to src/socket/dispatcher.js index f83024a..552abe9 100644 --- a/src/Dispatcher.js +++ b/src/socket/dispatcher.js @@ -1,6 +1,6 @@ -const OPCODES = require('../OpCodes.json') -const { getServersOfUser } = require('./DatabaseHandler.js') -const logger = require('./Logger.js') +const OPCODES = require('../../opcodes.json') +const { getServersOfUser } = require('../DatabaseHandler.js') +const logger = require('../logger.js') const WebSocket = require('ws') /** diff --git a/src/socket.js b/src/socket/index.js similarity index 87% rename from src/socket.js rename to src/socket/index.js index 97235fe..936be56 100644 --- a/src/socket.js +++ b/src/socket/index.js @@ -1,14 +1,13 @@ -import config from '../Configuration' +import config from '../../Configuration' import fs from 'fs' import http from 'http' import https from 'https' import WebSocket from 'ws' -/* Server middleware */ -import { authenticate, resetPresenceStates } from './DatabaseHandler.js' -import { dispatchHello } from './Dispatcher.js' -import { handlePresenceUpdate } from './EventHandler.js' -import logger from './Logger.js' +import { authenticate, resetPresenceStates } from '../DatabaseHandler.js' +import { dispatchHello } from './dispatcher.js' +import { handlePresenceUpdate } from '../EventHandler.js' +import logger from '../logger.js' /* Apps */ let server = process.argv.includes('--use-insecure-ws') @@ -18,6 +17,7 @@ let server = process.argv.includes('--use-insecure-ws') key: fs.readFileSync(config.ws.keyPath) }) const socket = new WebSocket.Server({ server }) +global.server = socket socket.on('connection', async (client, req) => { if (!req.headers.authorization) { @@ -62,7 +62,6 @@ process.on('SIGINT', async () => { resetPresenceStates().finally(process.exit) }) -global.server = socket export default server export function socketInit () { diff --git a/src/socket/receiver.js b/src/socket/receiver.js new file mode 100644 index 0000000..e69de29 From e113de43173da7950247de54668d5e701298d28f Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Mon, 15 Oct 2018 23:39:04 +0200 Subject: [PATCH 18/42] Start some docs (hi #12) --- .gitignore | 5 +++ docs/Gemfile | 30 +++++++++++++ docs/Gemfile.lock | 81 ++++++++++++++++++++++++++++++++++ docs/_config.yml | 7 +++ docs/_layouts/docs.html | 79 +++++++++++++++++++++++++++++++++ docs/_pages/index.md | 23 ++++++++++ docs/_pages/users.md | 9 ++++ docs/css/blobcat.min.css | 1 + docs/css/images/arrow.png | Bin 0 -> 3391 bytes docs/css/images/avatar.png | Bin 0 -> 16645 bytes docs/css/images/menu-icon.png | Bin 0 -> 265 bytes docs/css/modesta.min.css | 1 + docs/css/style.css | 15 +++++++ docs/css/twemoji.min.css | 1 + docs/img/union.svg | 18 ++++++++ 15 files changed, 270 insertions(+) create mode 100644 docs/Gemfile create mode 100644 docs/Gemfile.lock create mode 100644 docs/_config.yml create mode 100644 docs/_layouts/docs.html create mode 100644 docs/_pages/index.md create mode 100644 docs/_pages/users.md create mode 100644 docs/css/blobcat.min.css create mode 100644 docs/css/images/arrow.png create mode 100644 docs/css/images/avatar.png create mode 100644 docs/css/images/menu-icon.png create mode 100644 docs/css/modesta.min.css create mode 100644 docs/css/style.css create mode 100644 docs/css/twemoji.min.css create mode 100644 docs/img/union.svg diff --git a/.gitignore b/.gitignore index 0591406..5b31058 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,11 @@ dist /index.html migrations/_migration +# Jekyll stuff +docs/_site +docs/.sass-cache +docs/.jekyll-metadata + # Config & certs Configuration.json *.pem diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 0000000..c22f1f1 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,30 @@ +source "https://rubygems.org" + +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 3.8.4" + +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.0" + +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins + +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.6" +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.0" if Gem.win_platform? + diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 0000000..24c7803 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,81 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + colorator (1.1.0) + concurrent-ruby (1.0.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + eventmachine (1.2.7-x64-mingw32) + ffi (1.9.25-x64-mingw32) + forwardable-extended (2.6.0) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.8.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-feed (0.11.0) + jekyll (~> 3.3) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.5.0) + jekyll (~> 3.3) + jekyll-watch (2.1.1) + listen (~> 3.0) + kramdown (1.17.0) + liquid (4.0.1) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + mercenary (0.3.6) + minima (2.5.0) + jekyll (~> 3.5) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + pathutil (0.16.1) + forwardable-extended (~> 2.6) + public_suffix (3.0.3) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rouge (3.3.0) + ruby_dep (1.5.0) + safe_yaml (1.0.4) + sass (3.6.0) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + tzinfo-data (1.2018.5) + tzinfo (>= 1.0.0) + wdm (0.1.1) + +PLATFORMS + x64-mingw32 + +DEPENDENCIES + jekyll (~> 3.8.4) + jekyll-feed (~> 0.6) + minima (~> 2.0) + tzinfo-data + wdm (~> 0.1.0) + +BUNDLED WITH + 1.16.6 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..a2cbdd1 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,7 @@ +name: Union API Docs +title: Union API Docs +include: ['_pages'] +exclude: ['modesta'] +baseurl: "" +url: "" +markdown: kramdown diff --git a/docs/_layouts/docs.html b/docs/_layouts/docs.html new file mode 100644 index 0000000..4a213bf --- /dev/null +++ b/docs/_layouts/docs.html @@ -0,0 +1,79 @@ + + + {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} + + + + + + {% if page.description %} + + + {% endif %} + + + + + + + + + +
+ {{ content }} +
+ + + + diff --git a/docs/_pages/index.md b/docs/_pages/index.md new file mode 100644 index 0000000..9949a2d --- /dev/null +++ b/docs/_pages/index.md @@ -0,0 +1,23 @@ +--- +layout: docs +permalink: / + +title: Union API Documentation +description: Welcome to the official documentation for Union API! +--- + +# Welcome! + +This place is full of juicy docs and stuff to make an awesome integration with +Union. This documentation only covers the latest version of the API. Old APIs +will not be documented and you should not use them + +## Something is missing!!1!1!11! + +All of these docs are on [GitHub](https://github.com/Union-Chat/Union-Server), and +we would love your help to improve this! <3 + +## Applications and bots + +Currently, bots and applications are not available, but we're working on it! +~~as always you can contribute blablabla~~ diff --git a/docs/_pages/users.md b/docs/_pages/users.md new file mode 100644 index 0000000..8a31c7a --- /dev/null +++ b/docs/_pages/users.md @@ -0,0 +1,9 @@ +--- +layout: docs +permalink: /users + +title: Users +description: Learn how you can abuse of users with our API +--- + +not written yet lol diff --git a/docs/css/blobcat.min.css b/docs/css/blobcat.min.css new file mode 100644 index 0000000..ec7395c --- /dev/null +++ b/docs/css/blobcat.min.css @@ -0,0 +1 @@ +.ablobcatattention{background-image:url("https://cdn.discordapp.com/emojis/434710260314406922.gif")}.ablobcatattentionreverse{background-image:url("https://cdn.discordapp.com/emojis/434710260562001940.gif")}.ablobcatknitsweats{background-image:url("https://cdn.discordapp.com/emojis/449050177760919552.gif")}.ablobcatparhteyboys{background-image:url("https://cdn.discordapp.com/emojis/438381772695404544.gif")}.ablobcatpeek{background-image:url("https://cdn.discordapp.com/emojis/394202056459812864.gif")}.ablobcatrainbow{background-image:url("https://cdn.discordapp.com/emojis/424284925978607622.gif")}.ablobcatrave{background-image:url("https://cdn.discordapp.com/emojis/435456455525793792.gif")}.ablobcatreach{background-image:url("https://cdn.discordapp.com/emojis/434710259899039754.gif")}.ablobcatreachreverse{background-image:url("https://cdn.discordapp.com/emojis/434710260649820160.gif")}.ablobcatsipsweats{background-image:url("https://cdn.discordapp.com/emojis/397082478487011339.gif")}.ablobcatsleep{background-image:url("https://cdn.discordapp.com/emojis/397082489484607490.gif")}.ablobcatthirsty{background-image:url("https://cdn.discordapp.com/emojis/400106086411141131.gif")}.ablobcatwink{background-image:url("https://cdn.discordapp.com/emojis/433382866995380234.gif")}.ageblobcat{background-image:url("https://cdn.discordapp.com/emojis/400035000545705997.gif")}.apartyblobcat{background-image:url("https://cdn.discordapp.com/emojis/432278239805833246.gif")}.beanblobcat{background-image:url("https://cdn.discordapp.com/emojis/400065362043076609.png")}.blobbunnynomblobcat{background-image:url("https://cdn.discordapp.com/emojis/398615176703770627.png")}.blobcat{background-image:url("https://cdn.discordapp.com/emojis/433371448200134669.png")}.blobcataco{background-image:url("https://cdn.discordapp.com/emojis/384825050290978818.png")}.blobcatadorable{background-image:url("https://cdn.discordapp.com/emojis/405851205164597259.png")}.blobcatangel{background-image:url("https://cdn.discordapp.com/emojis/376800560118104074.png")}.blobcatangery{background-image:url("https://cdn.discordapp.com/emojis/383776668751364107.png")}.blobcatangry{background-image:url("https://cdn.discordapp.com/emojis/362214428738846721.png")}.blobcatanimeeyes{background-image:url("https://cdn.discordapp.com/emojis/400073196042453008.png")}.blobcataww{background-image:url("https://cdn.discordapp.com/emojis/424277325979320321.png")}.blobcatb1nzy{background-image:url("https://cdn.discordapp.com/emojis/362212042930651136.png")}.blobcatbaguettehero{background-image:url("https://cdn.discordapp.com/emojis/383388037855313921.png")}.blobcatban{background-image:url("https://cdn.discordapp.com/emojis/383016953226199040.png")}.blobcatbirthday{background-image:url("https://cdn.discordapp.com/emojis/386961486112882688.png")}.blobcatbleach{background-image:url("https://cdn.discordapp.com/emojis/400073196575129600.png")}.blobcatblep{background-image:url("https://cdn.discordapp.com/emojis/400064205547634688.png")}.blobcatblue{background-image:url("https://cdn.discordapp.com/emojis/428184732099149824.png")}.blobcatblush{background-image:url("https://cdn.discordapp.com/emojis/400065362378489856.png")}.blobcatbolb{background-image:url("https://cdn.discordapp.com/emojis/361493491471089664.png")}.blobcatbolbscream{background-image:url("https://cdn.discordapp.com/emojis/404295972424777729.png")}.blobcatbughunter{background-image:url("https://cdn.discordapp.com/emojis/433057159576551437.png")}.blobcatburglar{background-image:url("https://cdn.discordapp.com/emojis/435428182292824064.png")}.blobcatbusiness{background-image:url("https://cdn.discordapp.com/emojis/438064253040066570.png")}.blobcatcactus{background-image:url("https://cdn.discordapp.com/emojis/381655459171401728.png")}.blobcatcaged{background-image:url("https://cdn.discordapp.com/emojis/361917731479617536.png")}.blobcatcheer{background-image:url("https://cdn.discordapp.com/emojis/387334601133260812.png")}.blobcatchristmasglowsticks{background-image:url("https://cdn.discordapp.com/emojis/380412043116675082.png")}.blobcatchristmastreeeee{background-image:url("https://cdn.discordapp.com/emojis/384789555783401473.png")}.blobcatcoffee{background-image:url("https://cdn.discordapp.com/emojis/400064206377844738.png")}.blobcatcookie{background-image:url("https://cdn.discordapp.com/emojis/424081671357333504.png")}.blobcatcooljazz{background-image:url("https://cdn.discordapp.com/emojis/430872001738178574.png")}.blobcatcouple{background-image:url("https://cdn.discordapp.com/emojis/384077830935412737.png")}.blobcatdab{background-image:url("https://cdn.discordapp.com/emojis/400072210628214789.png")}.blobcatdead{background-image:url("https://cdn.discordapp.com/emojis/361918299606614016.png")}.blobcatdeficit{background-image:url("https://cdn.discordapp.com/emojis/438064253216489482.png")}.blobcatdevil{background-image:url("https://cdn.discordapp.com/emojis/366564834059354123.png")}.blobcatdizzy{background-image:url("https://cdn.discordapp.com/emojis/361918777262080004.png")}.blobcatdrool{background-image:url("https://cdn.discordapp.com/emojis/400065362961629188.png")}.blobcatelf{background-image:url("https://cdn.discordapp.com/emojis/378325253727846400.png")}.blobcatevil{background-image:url("https://cdn.discordapp.com/emojis/361493598341955596.png")}.blobcateyes{background-image:url("https://cdn.discordapp.com/emojis/400065362529484811.png")}.blobcatfacepalm{background-image:url("https://cdn.discordapp.com/emojis/366518705456414721.png")}.blobcatfearful{background-image:url("https://cdn.discordapp.com/emojis/370642365431676939.png")}.blobcatfingerguns{background-image:url("https://cdn.discordapp.com/emojis/400065363573735434.png")}.blobcatfireeyes{background-image:url("https://cdn.discordapp.com/emojis/404285038545338378.png")}.blobcatflip{background-image:url("https://cdn.discordapp.com/emojis/386951189729771530.png")}.blobcatfluffangry{background-image:url("https://cdn.discordapp.com/emojis/383813328939515905.png")}.blobcatfluffevil{background-image:url("https://cdn.discordapp.com/emojis/387340697453461504.png")}.blobcatfluffhappy{background-image:url("https://cdn.discordapp.com/emojis/383813329241767936.png")}.blobcatfluffowo{background-image:url("https://cdn.discordapp.com/emojis/387347538245582858.png")}.blobcatfluffpats{background-image:url("https://cdn.discordapp.com/emojis/387338842803273728.png")}.blobcatfluffpout{background-image:url("https://cdn.discordapp.com/emojis/387352845294239744.png")}.blobcatfrowning{background-image:url("https://cdn.discordapp.com/emojis/361918196061700096.png")}.blobcatfrowningbig{background-image:url("https://cdn.discordapp.com/emojis/389445649298620418.png")}.blobcatfunny{background-image:url("https://cdn.discordapp.com/emojis/400064205828521986.png")}.blobcatghostbuster{background-image:url("https://cdn.discordapp.com/emojis/400073196176670723.png")}.blobcatghostdead{background-image:url("https://cdn.discordapp.com/emojis/376825130199941122.png")}.blobcatglare{background-image:url("https://cdn.discordapp.com/emojis/362554149595906048.png")}.blobcatglowsticks{background-image:url("https://cdn.discordapp.com/emojis/380417635134734356.png")}.blobcatgoogly{background-image:url("https://cdn.discordapp.com/emojis/366503681807482881.png")}.blobcatgreen{background-image:url("https://cdn.discordapp.com/emojis/428184732157870100.png")}.blobcatgrimacing{background-image:url("https://cdn.discordapp.com/emojis/446791766935339008.png")}.blobcatgun{background-image:url("https://cdn.discordapp.com/emojis/366503580389343233.png")}.blobcathairflip{background-image:url("https://cdn.discordapp.com/emojis/371388877161758720.png")}.blobcathappy{background-image:url("https://cdn.discordapp.com/emojis/400064206050689024.png")}.blobcatheadphones{background-image:url("https://cdn.discordapp.com/emojis/382941518366900224.png")}.blobcatheart{background-image:url("https://cdn.discordapp.com/emojis/438396305564237844.png")}.blobcatheartbroken{background-image:url("https://cdn.discordapp.com/emojis/400074057376202752.png")}.blobcathearteyes{background-image:url("https://cdn.discordapp.com/emojis/361918045473472522.png")}.blobcathero{background-image:url("https://cdn.discordapp.com/emojis/383388037750456323.png")}.blobcathighfive{background-image:url("https://cdn.discordapp.com/emojis/400065363225608193.png")}.blobcathissing{background-image:url("https://cdn.discordapp.com/emojis/376782221631160330.png")}.blobcathug{background-image:url("https://cdn.discordapp.com/emojis/400072211345702912.png")}.blobcathyper{background-image:url("https://cdn.discordapp.com/emojis/400072211714801674.png")}.blobcatjake{background-image:url("https://cdn.discordapp.com/emojis/366564941064306699.png")}.blobcatjava{background-image:url("https://cdn.discordapp.com/emojis/390251924781400066.png")}.blobcatjoy{background-image:url("https://cdn.discordapp.com/emojis/363338182453886987.png")}.blobcatjoyboxhero{background-image:url("https://cdn.discordapp.com/emojis/387326805851504643.png")}.blobcatjustright{background-image:url("https://cdn.discordapp.com/emojis/440975225182617620.png")}.blobcatkfc{background-image:url("https://cdn.discordapp.com/emojis/386984798008115212.png")}.blobcatkfcsweats{background-image:url("https://cdn.discordapp.com/emojis/392080844316999681.png")}.blobcatkirby{background-image:url("https://cdn.discordapp.com/emojis/366564505079119892.png")}.blobcatkissblush{background-image:url("https://cdn.discordapp.com/emojis/400065363309625355.png")}.blobcatkissheart{background-image:url("https://cdn.discordapp.com/emojis/374014216245608448.png")}.blobcatknife{background-image:url("https://cdn.discordapp.com/emojis/371775422473699328.png")}.blobcatlewd{background-image:url("https://cdn.discordapp.com/emojis/380841134051557376.png")}.blobcatlul{background-image:url("https://cdn.discordapp.com/emojis/373872065033273345.png")}.blobcatmelt{background-image:url("https://cdn.discordapp.com/emojis/366512172563431434.png")}.blobcatmeltnomblobcatmelt{background-image:url("https://cdn.discordapp.com/emojis/435159179150229515.png")}.blobcatmeltthumbsup{background-image:url("https://cdn.discordapp.com/emojis/435159178932387852.png")}.blobcatmlem{background-image:url("https://cdn.discordapp.com/emojis/363027617827258380.png")}.blobcatmorning{background-image:url("https://cdn.discordapp.com/emojis/400064206734622730.png")}.blobcatmorningcoffee{background-image:url("https://cdn.discordapp.com/emojis/405851205320048641.png")}.blobcatmoustache{background-image:url("https://cdn.discordapp.com/emojis/366564624885219328.png")}.blobcatnauseated{background-image:url("https://cdn.discordapp.com/emojis/361493687966105610.png")}.blobcatnerd{background-image:url("https://cdn.discordapp.com/emojis/361493812050264074.png")}.blobcatnervous{background-image:url("https://cdn.discordapp.com/emojis/385790749276635147.png")}.blobcatneutral{background-image:url("https://cdn.discordapp.com/emojis/361916752545972224.png")}.blobcatnight{background-image:url("https://cdn.discordapp.com/emojis/395771690749001748.png")}.blobcatninja{background-image:url("https://cdn.discordapp.com/emojis/361916869248024577.png")}.blobcatninjacaged{background-image:url("https://cdn.discordapp.com/emojis/421731327654625301.png")}.blobcatnomblobbunny{background-image:url("https://cdn.discordapp.com/emojis/398615177265938432.png")}.blobcatnomblobcat{background-image:url("https://cdn.discordapp.com/emojis/366505071116288000.png")}.blobcatnomblobdoggo{background-image:url("https://cdn.discordapp.com/emojis/384771147776131072.png")}.blobcatnomcandycane{background-image:url("https://cdn.discordapp.com/emojis/380841151990726656.png")}.blobcatnomcookie{background-image:url("https://cdn.discordapp.com/emojis/361558714521485313.png")}.blobcatnomcookieblob{background-image:url("https://cdn.discordapp.com/emojis/387320403984384003.png")}.blobcatnomdisk{background-image:url("https://cdn.discordapp.com/emojis/390251925599289344.png")}.blobcatnomeggplant{background-image:url("https://cdn.discordapp.com/emojis/366518379667914752.png")}.blobcatnomfish{background-image:url("https://cdn.discordapp.com/emojis/400064206889811968.png")}.blobcatnomouth{background-image:url("https://cdn.discordapp.com/emojis/361917901676216330.png")}.blobcatnompeach{background-image:url("https://cdn.discordapp.com/emojis/404264088298979329.png")}.blobcatnompizza{background-image:url("https://cdn.discordapp.com/emojis/395348317946511371.png")}.blobcatnompopsicle{background-image:url("https://cdn.discordapp.com/emojis/387328486274105345.png")}.blobcatnomrevenge{background-image:url("https://cdn.discordapp.com/emojis/427484109124927491.png")}.blobcatnomstrawberry{background-image:url("https://cdn.discordapp.com/emojis/404264088399773706.png")}.blobcatnomtomato{background-image:url("https://cdn.discordapp.com/emojis/405851206016040963.png")}.blobcatnomwatermelon{background-image:url("https://cdn.discordapp.com/emojis/405851206041468928.png")}.blobcatnoplease{background-image:url("https://cdn.discordapp.com/emojis/371388877149044748.png")}.blobcatnotlike{background-image:url("https://cdn.discordapp.com/emojis/363029007324807170.png")}.blobcatnotlikethis{background-image:url("https://cdn.discordapp.com/emojis/383813403996848138.png")}.blobcatoh{background-image:url("https://cdn.discordapp.com/emojis/443619626148954112.png")}.blobcatokhand{background-image:url("https://cdn.discordapp.com/emojis/400065363296911361.png")}.blobcatonfire{background-image:url("https://cdn.discordapp.com/emojis/378300490682859522.png")}.blobcatopenmouth{background-image:url("https://cdn.discordapp.com/emojis/366518534521487361.png")}.blobcatorange{background-image:url("https://cdn.discordapp.com/emojis/428184732111601664.png")}.blobcatoutage{background-image:url("https://cdn.discordapp.com/emojis/366565050393034752.png")}.blobcatpats{background-image:url("https://cdn.discordapp.com/emojis/366504744262303754.png")}.blobcatpawtongueleft{background-image:url("https://cdn.discordapp.com/emojis/400065363565346817.png")}.blobcatpawtongueright{background-image:url("https://cdn.discordapp.com/emojis/400065363297173505.png")}.blobcatpeek{background-image:url("https://cdn.discordapp.com/emojis/384824283211497473.png")}.blobcatpeekaboo{background-image:url("https://cdn.discordapp.com/emojis/382675873838399491.png")}.blobcatpenguin{background-image:url("https://cdn.discordapp.com/emojis/400072211337183244.png")}.blobcatpensive{background-image:url("https://cdn.discordapp.com/emojis/380419932820733953.png")}.blobcatphoto{background-image:url("https://cdn.discordapp.com/emojis/366565158857867295.png")}.blobcatpika{background-image:url("https://cdn.discordapp.com/emojis/366538824341520386.png")}.blobcatpinged{background-image:url("https://cdn.discordapp.com/emojis/362965219967893504.png")}.blobcatpingedaww{background-image:url("https://cdn.discordapp.com/emojis/433352238417575976.png")}.blobcatpingedreeee{background-image:url("https://cdn.discordapp.com/emojis/389422733764722699.png")}.blobcatpink{background-image:url("https://cdn.discordapp.com/emojis/428184732275179520.png")}.blobcatpolice{background-image:url("https://cdn.discordapp.com/emojis/376782222130151424.png")}.blobcatpolicedonut{background-image:url("https://cdn.discordapp.com/emojis/386951794569510924.png")}.blobcatpolicepeek{background-image:url("https://cdn.discordapp.com/emojis/376782222553907202.png")}.blobcatpopcorn{background-image:url("https://cdn.discordapp.com/emojis/400072211349635092.png")}.blobcatpopcornsweats{background-image:url("https://cdn.discordapp.com/emojis/392080843637653504.png")}.blobcatpopsicle{background-image:url("https://cdn.discordapp.com/emojis/362959194346749952.png")}.blobcatpout{background-image:url("https://cdn.discordapp.com/emojis/387014571644289034.png")}.blobcatpresentgreen{background-image:url("https://cdn.discordapp.com/emojis/380841189181751301.png")}.blobcatpresentpink{background-image:url("https://cdn.discordapp.com/emojis/380841189223563274.png")}.blobcatpresentred{background-image:url("https://cdn.discordapp.com/emojis/380841189311512579.png")}.blobcatpurple{background-image:url("https://cdn.discordapp.com/emojis/428184731893497857.png")}.blobcatpusheen{background-image:url("https://cdn.discordapp.com/emojis/366538936178311179.png")}.blobcatreach{background-image:url("https://cdn.discordapp.com/emojis/366539036656926721.png")}.blobcatreading{background-image:url("https://cdn.discordapp.com/emojis/404548961987330068.png")}.blobcatred{background-image:url("https://cdn.discordapp.com/emojis/428184732241625088.png")}.blobcatreeeeeee{background-image:url("https://cdn.discordapp.com/emojis/362205487292350464.png")}.blobcatreinderp{background-image:url("https://cdn.discordapp.com/emojis/386951654131630080.png")}.blobcatsad{background-image:url("https://cdn.discordapp.com/emojis/400064206310998035.png")}.blobcatsadpats{background-image:url("https://cdn.discordapp.com/emojis/405763848176533504.png")}.blobcatsaitama{background-image:url("https://cdn.discordapp.com/emojis/400074057980182538.png")}.blobcatsalute{background-image:url("https://cdn.discordapp.com/emojis/381555549097164800.png")}.blobcatsanta{background-image:url("https://cdn.discordapp.com/emojis/378325227169382412.png")}.blobcatscared{background-image:url("https://cdn.discordapp.com/emojis/404298286699249664.png")}.blobcatscience{background-image:url("https://cdn.discordapp.com/emojis/424273608056111104.png")}.blobcatscream{background-image:url("https://cdn.discordapp.com/emojis/361918416942268419.png")}.blobcatshocked{background-image:url("https://cdn.discordapp.com/emojis/370642365595123722.png")}.blobcatshrug{background-image:url("https://cdn.discordapp.com/emojis/400065363578060800.png")}.blobcatshy{background-image:url("https://cdn.discordapp.com/emojis/374014216237088770.png")}.blobcatsick{background-image:url("https://cdn.discordapp.com/emojis/366503464466907136.png")}.blobcatsip{background-image:url("https://cdn.discordapp.com/emojis/370642365674684416.png")}.blobcatsipsweats{background-image:url("https://cdn.discordapp.com/emojis/383813368395333633.png")}.blobcatsleep{background-image:url("https://cdn.discordapp.com/emojis/366511635243728896.png")}.blobcatsleepless{background-image:url("https://cdn.discordapp.com/emojis/400072211681247252.png")}.blobcatsmug{background-image:url("https://cdn.discordapp.com/emojis/400072211152764928.png")}.blobcatsneezing{background-image:url("https://cdn.discordapp.com/emojis/366564393485598720.png")}.blobcatsnuggle{background-image:url("https://cdn.discordapp.com/emojis/368734002359697413.png")}.blobcatsob{background-image:url("https://cdn.discordapp.com/emojis/366511517459415040.png")}.blobcatsparklyeyes{background-image:url("https://cdn.discordapp.com/emojis/404671093744336906.png")}.blobcatstabbystab{background-image:url("https://cdn.discordapp.com/emojis/390642136112168960.png")}.blobcatstalkerpeek{background-image:url("https://cdn.discordapp.com/emojis/427029881088638987.png")}.blobcatsweats{background-image:url("https://cdn.discordapp.com/emojis/400065363443712001.png")}.blobcattableflip{background-image:url("https://cdn.discordapp.com/emojis/376800561108090881.png")}.blobcattea{background-image:url("https://cdn.discordapp.com/emojis/371388877174210560.png")}.blobcatthinking{background-image:url("https://cdn.discordapp.com/emojis/384354961473011712.png")}.blobcatthinkingeyes{background-image:url("https://cdn.discordapp.com/emojis/361473484066390016.png")}.blobcatthinkingglare{background-image:url("https://cdn.discordapp.com/emojis/404671093580759040.png")}.blobcatthinkingsmirk{background-image:url("https://cdn.discordapp.com/emojis/363029138094948352.png")}.blobcatthinkOwO{background-image:url("https://cdn.discordapp.com/emojis/361473376096616449.png")}.blobcatthinksmart{background-image:url("https://cdn.discordapp.com/emojis/434711897799917579.png")}.blobcatthonking{background-image:url("https://cdn.discordapp.com/emojis/376782223631843328.png")}.blobcatthumbsdown{background-image:url("https://cdn.discordapp.com/emojis/366511377537433600.png")}.blobcatthumbsup{background-image:url("https://cdn.discordapp.com/emojis/366511315042172934.png")}.blobcattilt{background-image:url("https://cdn.discordapp.com/emojis/370642365645586432.png")}.blobcattoot{background-image:url("https://cdn.discordapp.com/emojis/433349557993078784.png")}.blobcattriumph{background-image:url("https://cdn.discordapp.com/emojis/400072211139919883.png")}.blobcatunamused{background-image:url("https://cdn.discordapp.com/emojis/400073196457426944.png")}.blobcatupsidedown{background-image:url("https://cdn.discordapp.com/emojis/400065363238453249.png")}.blobcatuwu{background-image:url("https://cdn.discordapp.com/emojis/361917101956399106.png")}.blobcatverysad{background-image:url("https://cdn.discordapp.com/emojis/390637045678342144.png")}.blobcatwaitwhat{background-image:url("https://cdn.discordapp.com/emojis/366564730216906752.png")}.blobcatwave{background-image:url("https://cdn.discordapp.com/emojis/379389328054288404.png")}.blobcatwavereverse{background-image:url("https://cdn.discordapp.com/emojis/378257044890845194.png")}.blobcatwhatsthis{background-image:url("https://cdn.discordapp.com/emojis/400064206289895424.png")}.blobcatwhistle{background-image:url("https://cdn.discordapp.com/emojis/361493910847225857.png")}.blobcatwink{background-image:url("https://cdn.discordapp.com/emojis/433371448355323925.png")}.blobcatwinktongue{background-image:url("https://cdn.discordapp.com/emojis/400064206155546634.png")}.blobcatwoah{background-image:url("https://cdn.discordapp.com/emojis/366538718552522752.png")}.blobcatwolfiri{background-image:url("https://cdn.discordapp.com/emojis/362292809111175168.png")}.blobcatwumpus{background-image:url("https://cdn.discordapp.com/emojis/427484184588845058.png")}.blobcatwut{background-image:url("https://cdn.discordapp.com/emojis/380411965895606275.png")}.blobcatyandere{background-image:url("https://cdn.discordapp.com/emojis/424422567504510987.png")}.blobcatzippermouth{background-image:url("https://cdn.discordapp.com/emojis/400073197069926400.png")}.blobcoolcat{background-image:url("https://cdn.discordapp.com/emojis/400064206688223232.png")}.blobgentlecat{background-image:url("https://cdn.discordapp.com/emojis/369619362090319872.png")}.blobghostcatreach{background-image:url("https://cdn.discordapp.com/emojis/386984776130887691.png")}.geblobcat{background-image:url("https://cdn.discordapp.com/emojis/371388941615759360.png")}.geblobcatshrug{background-image:url("https://cdn.discordapp.com/emojis/400065363577929728.png")}.pats{background-image:url("https://cdn.discordapp.com/emojis/388842338056929290.png")} \ No newline at end of file diff --git a/docs/css/images/arrow.png b/docs/css/images/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..6909ac39a40c949557d7cd998db2c11904d45702 GIT binary patch literal 3391 zcmeH~X;4#H7Jw7d9U3JpE`ui6*kThmLzm<2*}c?fFQ`OM8OawhL;u{1zBtt zMA-r=pn@nnQ4&yg#GtZB8VJe~Xdn;*2_cX7CiIW4ajNFmOjpfR_4#wpQm4*6-#Pck zjdOA!VU}T+ArJ_R%^@pi1VTxnDj~i@DULU}8=DlyDJOeZYlR8_C4Uk4GYMD(;W`z2 z)_UIA!3BX-LVfcs`a6|n%T+OI>KZFnu3D|NW-S({z3%(<8+147ZP~g_f4jksox2Q; zjCT`EO!t_X@3kOW?mu94@Q{tIo&8}4$0N=zu5Ru}Pxzel^*a?17#w=~Oc?oWc*J=M zH7c5Rksbpue!diYknW`E3FTLkKLrPC~^;+Ncs`9PBW2eOhu74cO?OFaegA5bd*6kif z(!{-MeR%buyHzI>yVvx`aP?aqR=1s^jn8-p`*wo7Tkv@Mnb7U`yXrM_4US0%YNP0m zd{JD_{`=eTMXZQ!*mORYQ#t14wYm139YL7P3Q_k4;tQToTjtcef6D@)M7ul(iCjFK zm!qPK(^^(=T);Q}@V1#)6}F3WMT>8UZR4@S?{{qZwelxn(82(TkZES);u!_#9~b5M z-+bB-kN4O!&!cPEzt#w}(9ow%Z1_wOerfa|%9P&~L|q%~RwLmKa>RPb$dNW&1J6`O z&$3{*Y<@@Ez?;4M=4!c?2LPYRim2M_O>psnEXq(V5dBn z0*e)^O)eIidr&4nZ*hn@?H1_b>3-cdfo`8uD4YK!#DN{@g?H)IaC=;MGuY06Tv+^o zlO4OcVQbokhHl%k3!zWVW#v=-#k!i<9iuXmc2#cm=Ke+J%V^aYNYkKfZt#;Sd@a5Q z#ex=LPF7v1$WEz|e7)rYD*7Cpyg(U;Lxbp-(YJ2`b!~-7BItOKrKyZGNiR%1_n~B` z=>)P5f`|d!w<#l)#(LKYOl2vYPp;V2c)>3l=|8OFrw?9g>Nul`b@~a?R4<#>tx0v2 zFt!7*PK4V>qS7bhs@N!0^jR1W$}08n$27B%!^1l7hlm}i3-Wfx3UP8q+hWS-up-|V z($onGNl|{eGyd@Nr*i&IA~_(Xg!RFiY$=_B>k7@P<4%Spv@>>zljkq4{0QzeVs?Oj zfrG&#)FUsDV8>(ML-%bNSK^+>U zpV-a_7bi!NW8>J0%Hfcx6RnG(&~xh2e5g{6`(i<@J?$mYnYYv~@P&#R=bm>q2(aZb zGKX}^)>5t>*T_cxD9||qa}UKB88Ih7zYBdY!bab_j_s~JF#!~@*g{Y5@y?^o{fx{$ z_#k{}qP(3^EKc?qcaX&Xu1$5Xo5U*!3Us7h8D}C4NOr|4k}#N7?>MeTr8)-MiO{nW zhy*?-QyJQrQ3@ogW)0E_OSJ~eSz`Lu-?cP~GU&?{Y>i$yA%djw3xLV2Zne(y0RmH9 z2-I~_NS*ldvH>1Pjob zl1c*8QIUEVtIAR|HUth9qi?;_?qu8)ljjX$b5Bp*J+hpKS{RlCugN$y`5pGXvQz>S zP^28jvpUPO z&WP(!` zdMbpyu@}HJWpdD`xZbE@K@qY?+|8Jfg<48LpV#sDM!zrwGOGmiZTSttWEwuG?j0n7~!y2%?gRD_)A{x7@X5zk7_5O`I~ z&P0qGfc}6bMeyM&K0^8j$!g_!h`Wfcg#Q_{R4)i_{mQwwMhXZCHH%-7C+w4Z{|`B* zkZd@DgA{(XDb*@GIQ-u^;TofXEX7P1^iBcC5sEEUPH@eDf8fKhb7<{?-EgcBW*Rd; z5PYm4RA6mu=P+ChDz`s+;5JN*j{B%>CD6{-gBJxRt+hsisx31>Skf(RS3L3K1?Xj< zS!1nnL};FjIE&6RY41v)tbjk?aX8yvJ=h}=^C>A}+(mpCmTUlt%pA{0TSv)@c+J45 zD&9R|SRanBS?+FDcGDgn5*OCN6`db5j-AXsAwFLsB2qeC1#UTFV0NluOy^T+lg%Bu z>b4%w3Y4;t)A7R{VJSC^-p9v%-#@dKILGyE03G|FQDL=#_L{Y()35vcl6%@qWV?##J!&&+w>_q@?mS0W+2O9%iUd7`YK4FDYa3J3VO(8rPA?+fUI$x2yU z4S)c40MKCo{3}AQ1MorsfK3YkBvJvOcK^_zB?VOwxGNia0YKXE?+=#5O-c(D;##UG zDFDpB-<;;61OSl!PZS>M`kC#_t|ic&XU?Cy+3E$pI>ANyekA#2VQXQF`_Mr9UQNmL zsJ6-HCC5+A2?ReBXAT2B$tZ`pPAGkIXD=!EcH2!qRC`D~HHxqM?MRZc?B3l@Yzv5S z8SI4`hUNlwlF}}MtB4*7>@=#TpT5=F{_pPH9B%H`?CiRb5ZNUn^kBoxn(`FY;oht4 zGJor)z6hte-u1v1w=84JIr4#~v1}TR`NJPoL6v=hEzRfiquCkdeH7mhr1t5qx&BzA zHl3#HemSXV*Row?S076sj(7xl@Eo@IH=hr%`K_bs?)e$C50EjYV<=_(99?N6jb`D#>vSq97FDO zSbuKl7!q=ZMuTM{80d!a9vMyOiv$^xgaoV>v6VQL`90x3z_ZS9*VHOoY)ko1(D=S8 zvY2Oj`w`O}5TOLX$wsq`lHORbp&7NIpsfC{fuv`I!R@IQnx>1&Nd(x>R+0(uGTIji zy-3l)(C8!wldu2dI(X|;I8lDS%KWJ0W)fQ>?3KFp@41(oo^f|62#@T-32=@m!89uX z1O9d6apBK&_WI=CUJo~Lmnl`ouymcMBF+RUf9n|>46(vtd&DsC#^;k=)xv<&koj#x zA)NWb7mO-dbu{?Vx=#_fNA35q@6kYQ@|I2?wVUtT_$a>I4Zf%UER72LWy4byM^z>Z z23UL;Ku<`HJM@0wIx#w;PDv|`_`LDotIhjLn`(E8ALcG}=C~GNcTmGX<;TxgIpM9& z3~#fX{q#<QbsgBLwr3j-e=8StG&=8Q)3{sh+ZGo3 z`u-{@69YboEUjy7<+&hI;pvlqz8?QQ-BBrBh&Z+D**Cxv1<=Yv&4)$Q!D)husJZH+ zw8|LE;Ut_F&!UCWnFbBy?;xuY01?n2Vs3n{LsVF!F{GsIFo1NaVePBQ_>R3Tj;>$@ zOo>PMGxb#F>}o^Vrq-)6ES;zMm7OBb+enk|cnS%Aiv(!R`oWd%j+Y!uhudutew=Fl z!zLf4v1(0FatNd!0%&?q{gAC$&-hk8{nt~RrN!|lkAsZm6CDzHY)%mE{$-q%O|58;8&leJYrctWo>I|kF6~akp{BQ+; ze6*f^UFom!c+GU*#OSiES38=+MTyQq+On4;h5+6z3kL_AeWb>Z*Q7=^PdiSzuG(CK zdJ|S1ji%}|vz(E8+e`pNy^qRd{C!W1(d%nKcHq_9!7)r)C$c>(10Zew@LFVH- zvYGqVtO>ysZMjJ)QFJ9$1)q}r;obCbK&qCqzu|ndm_}T69x62~jWe~SJsc2b6DB4F zRjO(%(54+)TX#}rkDQ`kR(2#y3ZO<0_nZ5Dk8_U(d_ZyKO%Bz%QKPM+x2`asSu&9)n3=n1b4;9TlZ2 z);?jnC-3xcfxVXxm0bJ6ks7O#+RuATyx@*e`FO>WVybFLeZ*nd9sr<{Yd#b)=GGHC!HM>bw6RX3v8S#R4NU`@Q<3G{5@ zVpH0pxvdO`BY!oA1XVR=r(J`Srzs>SwUY_G`EP7MPCov zyfV6a!SSd$g(+(t7MYvCUN4(&eQ5?F^ez2|XOrVEKOW(BkWjHa_BJh7KL`|9)w+T$ zD@@hB`cs~OeXO9_fhP?f3{5+#VEi5j3DTpbD(N)>(sd37g#;17x^E*#^f0$ZHc>*m z|E^0q;c?tAFSq1q7t66!NR2+8_xq51P9@<1DEqg+@QY^Uujop$-TQfV#G-7sBBG}v zFqXZ@imi!F)GKL`?y-4O@J^ZcB>v2FE_{Xx|e}ed7)3qVbl$D=Rw^GmExTju&|UYfdH>T zqJH{EXSaZyddjIODn5DT1!SB(|EM;6%HuqpoUb`5bkf`?>Igt(N&Lal{*v5`< zr1pBQoG1#SE6;FrYnrI}+X~c3#3PT07USbJmWeNhyBP!jTM3 zBUSZP^D~4$7y+rH&t!`6cQbbl#ob%Lb6YNjRWx;j42b7)W6Pla;2IaQ*5L#3Bijhx zY-u$Wi9S^n=uQ&WkD*e>J2hA)ZSA8QlD?K@?8i1Xfdfx&tKH9aebUJfu{|T^tx4DK zVvJeSEC`4IldOqXOtWNS+{iXnEXCDrvQKrR$&&0zMl!8WLAU6@mzA55cSmV~{G<`{ z3(OzvzpM<2;avbE^Ts1NvcBe&5p1%E`8n7NktxA8Tv?zg6p+r;h|56NT936Z@B`(Z z{)Y_wC~U;tl%~1ta&yU!7B&!6*vCT;4h9~0c)4}nAi&9@eKXtlht7Lg;Q8V2nT(jU zK%tXpP@!aWrDO{^287=CRR%Tu2}yVxgMFPWWSnmr^58oxP##Q#tMlbfGlD$%2&~jQ zUnN82B?k*aRL`)`>qCK}D%ifAZg#W{J!C4^fT_Xz4O0e=OU(SQ+uniZBIhvl`bgi) ziz&_*#*1|a@j5%TQg`~S&0!#KcL*O3-z9v$+v`YqKG2Hh*x` z+;fuB(KCY7VQbn~Fs5C>RWp=bH_7hxla%%UPF>3#&Sj79W!jE&0_7-S{pHSnpZGj-0PP- zLv#ExJr4U#ab!%VY4Rlu_|$w^Rlz6-uDI}%{ep_G*VS}amk2bH$J37RfEe9pHmnD;d}2PZ9BAdf!HJRuOro7@!Qzv* zTTeVoZyqx;SD&-z2g1tIW{2#1H9KMqvA-c(XEv(&ooM1}s{MC24Od(IR<~Ac zTP>p^m%%2vBQ?9c(028-(~Zs-Xpj zEM=;;@nZZB4Ga!#v=~cRN@xow824yh574@aM%p@j+B(le(v?gL*hrpbkdcspleW|4 z&spT>M;>2ENeE3rv8d>s;cX%}g0h}L7< zDdoB&XAH7}jr)yL8 z?QC&dJlSL6*n81CJwGVeJZoCdnn5l;=&NB#Aey8`!q+{UB)^x<1a0D2T|Lp&aqX#B zr@)PtXg`o=;xdXcXumgS$sGU&Y1^P<+?7`aX4);@Z_j4q>&$5vr_GOu^+mOEbzRk6{`zs7>6@sHUfZu(@sT zWx0qxc7;f3)5ntf%WQuYNdV8K{LF@HpeU|s{Y@AZUtqxx%x87u{xkt(7t^2Z8mrnF zu0LPe@^HW`t)bhy652^obn4LLRl|&u@5=X^js92W;v34o54pg;uEV$8dcH>cUijPF zAi&6(^y(4MMt_iOeM(#$4Izq;&aGm3%T8O{2@XSuHh=aRG~j%lhmG!2LkWI~TO@ja zHn`(gqpy;3DkW);S0Cr8Ft_2e98~mu_)*8xw58tg-9!^EIew(p#aQR6x1>(XkI0xw zHuIbBT^kE3s`~@Y@9ISzMK@4Q>3kO*TudorvwwIAc>I7L6zt|2+R8J_xXQd(X)HX` z;MYz)P$QGQ*i~|<7yhM9*wMA8%U=9GTyGBu2yVSu6a0iXRF{6T5;6Yppk3PNIy}D_ z_ILip%7AQdcf&a`x>)71_FX^KjS-(cTS!q?+@g5AWS)j`9%k_)@tQv65OkQDzZR1p zAhLXJEy9|j`kp8MB`#WPpqXmq&!~@;79(qNTbh3<<5s} zhqpyO5vjhp^~}in=8rAA!D@tigUG^9^NfLR)(^~iK|1j@MNOK7+Hp0P-M1bcL`q2cL@hHOvw5qbDMM|Nt_SJP1h?#SQo($WnhgJA~@ zpi}kkxB`~Ko7Zdm`B%dlW{hS{ik>kM!F zVSBDjWNrO3^BWY!@>m)LI?`Wa4Yz1@RmThq?V9{;LN-rGria<9kHqn-#=#U;)iA0IBps+ z1i(CvKD}7V){4GT#9nWg@l{&?^t8eFdl|6+Be*3XvxO@xlHz$62x$fKeMW^%A2jgG zw#aM#D9oPmf%cM60hLRiiYA#9PovLWcIV{GS0SF`HckdXk08Vj!w`yCOxeV)2p_U8>16aGRSZY7g#kYPL(_WC7H;32lMmPY8=p|f)X!<|gRc+>Gkn)qFDylU_vc#Z-N=+hLs>CKI6ikKvEP2jVg!to9%E(`xA ziO7&z7HA8_LYG}<+w927ZFe$_bC5BP3G^&2Iz_rK1LG#3ZG z-#p}2M(8zQ`k$|g(SLoZ;o+qi87!jEOH#j2(+uz{zyJP&g@g7Dpi7WA(`NeMZ63uj z@@|$z6t_?+n&`28-{%I;p5M!_#56&w+0l>M4XEo``u)`A_aBCwHu^b3$rqO|YgHND zoffd3?VIe#6ECOFmHzdtdK^7VE0QaCNr#{D5(n(uWEWjBG9P=7**Q5b&%CTJeB4)q z6E?IRPy2iIdde3JPGY<MD3QxDWVA;D;wV2M8Hm{0c5C1k}oHW{vu-EgUab%8T|_*RCr50Gnqel-$BM~%$G>xp%-+8Pi~ZyNz{h~<)06ZgjEbLou5)@o|+8V zk$EhDSU0+fmvW0n@~JyTdVi$>Pkg{FGpoayM6Em6Ak45ItF2z$SVW5Zk`SMoK5LlG z2+6$Oj%;|ygm~=a5JzMhn1NSO1O)2hgW+L=VxLiz6JpSFdXP!kOdUuF+Od&L`A6PQ zb7CiMQ9Vzt0y({y+u~Pv%p%Rdt|%#Q!@zTqx@Fv&K+#5AV5+@Pc|`_QB?iMO@nIm> z*73ybL~*j_%gA+%fU(*`7$Urg0yf@PgBLbx=6G5Lvq&aD<5z}{Zd2s6eLX?g9tIZA zN%Vuv zDgnT+FF5|!&21VuvU_JNa{d9};j_yI%s;S^lyB9MINzLIQ(_xj{5QWK{~7a-Y(L&+bFfAK=in8>8%yx@t`uCAO-{R>7ouR1}oGGup z(jg*O%()>Oam$I;Rdd?>Rm3lQ}zTSN|@xH`pWE( zcV~Qwhx&yl*WJPOV*@;&p$9gk)vtC(@moy)t=lc|MFWk`$!|k<`v-(C)kLgM z)Zig~fywmqaV93`f1N|%cKm1cQwZxXb&K>Hs3V!wU zfZVBa_ZDH6!om*#{ik?kO1`71iF_d(RQ#OWj7&`@bp6IjS|+W~*DHJj6*l#6+>T7X z+wffLo-h?;3I!gwCW9q&eBk6j&YM@zFcVK+_&KNTsSeAtU$T!nN?n@#`a(Rmep3Nw z*t_DPS%a;T`dc3>?*0<)dKOV^81ShRbqk0LmqJ^ju%6d!Ud7%2zOEt^Ynx`TSDvEz z%l^~|aRY?fd*Od(7t(_Ue(NykGl~ybL*GWURh)>!e+Wku8wI+uwNdNu-2M&@h${Dy~u0X!1lN!s#>LsW?2 zvVK~9=N(tM#QH4WlmJl$!HM|6Ug@ga38=sWmJ141R7cq0+#;iK<&7;I$sT^v~E5K$2i(G ziFGZxriof!8l6iuU~$E`@72&3_fy-*MU1Zb^~DrHLS(jgXAvIQ&(ElB^7ue2O1jpk zZcOKS&r;c`b%z`vbjp1v+7Eaan2y{(jmMC)+|r;Kzy{Z%OwmWnwO*O`0NpR~kqds_ z-18W};U6e$&@pvR>iTD*l}SqQob17ERk(K=cI2IrKd4YD*hJA=)9uR`zh1wViF7zx zY~(q5Q!oHlAu3Fj+|)C8%aZR7+&wq|Al$gT_)+CCv=S$)p5?5hnU5puEogEwq$xpU zRf3C8g*;P5!imIN3br`>7PmKjbjW)@#?^~D*s&~V16+c3h_NuruPyM~tVjWQ-lx|# zp%ZgI_>}JcryQw_%X@tUn6#jfB)|4$hLoAEzC+LS>J*5Vqs7$n;Xt1p*f%@l+Lm#! z*n;tIqWy(0cRq&Z@Ab7#;DI+@)pPMg#5hb_pZS!g(wL&c_EmT4q~8Mca{ccuWiJxZ z>wKrrHOo5;-cerM1|Fxn)2FU+dDm(m) zZ}sJ4OZss4j`780$e~=6RR~Nz0`utxnaqH7sYU_kMC^Zr{WV_Gp(7%Z>Z+9z!NSdQ zC=+I02zpXs#q=k)L&)nUi|gk7_9X~ve{d=`z4REb2^kZ1eJrZdk!z-q743d=5l-&# zFq;d_4~C{f(#s?W}^H0uvp)30#`u!9Ts{_AHqZm}_nnF@br zKSa-PT(#xA{2i-S;U5s^A|0}2`9$Ci@sOsTWsh6aGjh5MYzwC;`A#}uK`jw-HSJcE z-|*ojxsUMb!{LFaWaqF7X=%K{+G7QXM|CW&UH0WK5PKxhVWc{Qp+BS<)!(OqHCDQ) ztbsin*h$`(-|_Q7MG65E@-p55#xCc5bx&Uo;J58M>s;Z< zXyE~u7X>=%70vvCq9D*N4ORJ*KnzX9L3jTCEt|Ib+ba9&n)2-SbJF{=73Q_)QwDWe z4L4U&*}+6@A*Ie|2cmVRH0ieDYRoT$Cl7kd#jynz(9yaEma`9_Z5{x+)Q~_|lKO+U z5oKSO(M`jyH|CpO%g0OA7op$niv?i|H};*`M!#CH7TQ)-V{dwP-o5LmC_a2NevDdD zeHyJhyopCzQfbr?Ahde1L^M-@HW_pq-YF_G@n8P=*!EDGPTk>+jnQ@O2k}k(0>*BV z+4+tD!HtXWRoH+j#oM(lDWYtK@%W`C`g-KqZM!KKXf;Ao&E?mCQ zpj4B+6}!`ig8db3E!B*b%iKkiegxgrnaWP@`}{*N*bqZr)RmAGbk&`dX7jR5{pi$enn9`EuytxYplmif6ZIHEg$tZIVfajH8H+#Q(XfUYz2{zx^5y+`J3pA=(pKdq2 z22lftQs+N6$SeQpAEu)@vU=~(Aw-CrLIlh$+^3)bMMIEzU^BqzPvv7{iMu=-&Ux!t z&vh;R<4<<_fTrI4FD-G`96+uo%xB0{vTIeECiC!BGtSt^j~!-C?#le*NUG9lx00$? zt|1ZWWMS#9r3D#DIIx`BZ}H)y)&q}m^B%WWN|*>VSD&XoAd{Z-@=$|F-08nw&>Nmk z7}xNWTTCIpGT>0_Ge?vS<{`*?G%zw4sE7dRjtXK3{7AAL8Uukx7NhxBHPzilCq?Qa zzq7E%Pg}+62P^x&f9VjBRiSqoeVdq(9PGbgtCCw7R8>I6#E`Dmmn|eLiw^>R>J5*x zVu4uf0MfR-cl_Z=Q z2r$aH7vy~?KtrR_N@6%Y|G*DN-{)UcKmzcmIPd7kU5p$kz8|>A294&;&tYn(E28rI z6A+G%HVCNiY(;?ky#@r(t>~U5xBTx^Wz#?nxM5IhDpT0rMq3Iu_)P>u>ce&QD#)R$ z#6E>M;GhFM9;3BPlY=20kY(Rv7RR z4(+89AHudr^_qx+b`N0GN9}->+lc0SZf4SA) z8hq%Cj}4s;Hx610YIdB?YNf%Zvdsx(rO3o7`cBMfG|Zx?5(}EFS>vcXWCHNuqt}^F z$)aRGOmmu^1uFRFP`8h&u_8lTGC=XZ^dG^xL!B0oTg(CAzD1%-yYz_4JsWK z>&e3MLxcPOtvMmUkj5$%s+Z*-jZ#9TLTnaqV)q z*DM}GYBFm(g=}6}(;NNHmM}I9ybb8GPWm1A7tFTBK{b7)wR4CDMhm=lYT-NV4H394 zYDd_7^FcH`Bt)?_CnzOL-^lVMngCD2tA0*u^FI;PSb8?;B$%kGmCz|!JMlqHdI7(k zR7kWygA2@7opMFPg9C=7RO7M@r+7}L{u@WVepRKmKc1va!j3PbYdmbvKqAb}lW1&t zix{o{LAd7Pgu&@J3#%E3N z%nVf~d%@70Ph+H6k~3Mc77*NC0Pzodi*v5Zfu!K6&e=PMN$@0RX)mF0LW9pwu*M>) z#ou1PrAwbFvAPZ^|H$jJf2!u-2NE0!3{ZyeFlUIImNrl6gLRi0h`jv{A~`aHL}lz4 z3I}5b((>GlP9>sa)`$O;KhNn43mua9 z2!~1C}Y-WUaR35HKZ>jRT1Uhw69RRQ?Z;%fLq{f6c$@4r7^d`9T#(XT1V$F z4?}KH%Ngqves+PO5cR0G=|KPzoilG%9rYK&FSUFK<#|GGO&(^=_0Z<=h|;8iFb z>08AcbLu__Vx7wBw_n?-wPy>4&r=2)opZ9&GYm2fj^;OGiKvijQR303%W8tb=V%QB z5;S~BXQw(GGhwpuirufUCkvlhTDY=gDB0EP;jYm8d~(+qPPD}d>hz`&Ga3pf>& zVZRYtU-nvU$gWGeBeb7(P{94mXjtfN$#pK^xsv$l$0?b?TQWK@w=+9K*Sbes;vIW*HcU{DIL(Ys&Org4~A{O za_#U!{Z3L!C>>A|Oe)~naHv_SCcp{^LiPr=D@S0f{W`s}NvKWPE2BK|!tS2jVpFhQGu;zXOaR@#D@^t5wv^ zV0D*7umzsPy&QQgJ5_{5B#`8)3HC1$Wn+{PJNvqk3r7$0K2)-;FIiFysOWb|P_2ou z5102r2ajZ-tGZmo1Anh#%QyY;d&Q<PdD0Zyq@n~|B zhw2)kSbG%wo{n1mr+TM|*74PQz+KKla3f!a_oD8#w4oiltfs&U8#_K--76*W1YC-u z@=?-cbw)jPIC__0uV<sT55zeOKyhlz83C|jY z@J$3C<-|dObA9^Nsj50wTaSpOSU1j)2*jQ)sWgO=q1T6p8L>mnpfD02u9dQNH)F&w zkYqGDpDyaL?#63r*_MPj%-^Sw{G$V1yL%L-cCC(CF6%8cxuplmuIwG1`Btj&kSmtH zER@L%-vf8vaA%V=eCWjMNM%qAYUw3lVB?vJN(}lv_Q4$fS;@IlRr}G|M$47&Yt!?D z?4nnb*OVxxIiYRy9ntx7vf*}-S)tLqc2VJ)&wQtEc-N%Cf1e&(Zz7Z^SupI>LFXII zs(Tbj!Ht8R^6V!oO-?lRqb4P%+uLvHeyVV21~SAGddd&Xp)gt8n0-fR%=bR*&K}Ih z3?H-yi>}in#a2oRSD|A@H!px;Ek4l^5nW+X0V5yd~xL@haF%nfLUw?Uo1JtPOY(Eay@%K{psO1v@#(+C0a; zdK7~(>**d8qId|~R*yNjIbF@#=fsQXTX4UxO|CljE2W79Ry=pxn8D+zg_gYvrpR~J zf#7oy(DjC!mCdK>J~Tz6u~g*`zFgA@$x~MxpPNnrXDwcT z`K`mo>k?xc!^O7*OatEa3*{|o@jfG8r5|4sZ$iN+}|MbNG zxpR3*dmNdX8V^KRgHaPF96Nd|r@!-F;@=sO`5Vtg7^QM(EhMRI^xPd?D=B$_{htj- zt|?4#v1#C+pPA2Fgh1ZkLuOIGg=e#BdKDj?Dk*E=^Yr`qwejy+vx@L8*A5NzhYjgb z^H^p_vh8-S8crWjz(-^Le*MzaPmPVd;wig0(D90SrL5A-I-X65@x2xG$WQv3bHf;W ztFlGnIWG9Kxfn)Ge9fNbdR`*TPb{}zbhhmAMHJ7-w8FWoy3@^z1R`1@LvQ_W|5Ck3#OEbF*-t;&!B=cl&HR8;lGfSw!$@HVvY^|HSa z!+9vRr2?^QfP8uJE62yfo-Lep*UG5cf32Q*{3Q+?)=c_f6WTDH?Gpj1wu0RG4Y{-K zmb@s_|9Uqt*U2;6zV^pS!Vt??Ux@9*CM2FvLN*##_qb_}mgU&Y+IBo0F*>}Z% zmSm_XzJ=nKqXFLW3U0=n7<+LqD}l5s3)q6cs9XiJddoUroiuaC*=f_D-sC`bfTAR; z`21tr;53sQNS?EF7_t}n@gqs}3>~2xnNo4aWaj(YM1%b;hK3(cP+%{7z{*d9N6r5c z2~m|MWNECFVcV25jXN(RjR1Xu7VJ}%`qvQb?mn99ozhM;~YTx^~Z;}G}x z#TVXSwNZ-?d%>|ZgcIxet$r27 z9S6nP?%&=duan`d2aJbuv14Fuy;-WARvPFm1pP@>hB7udcK?-d5$Q%D)x6wmlnw<^ z{E)iU##MakwClB_%5H`c6;&Jo_>bd{JgniE4Olp7wJ1$Rmz~GxK6$SP20v2@8|7z^l( z`7OiMFpe_MptvAG)l&3;99OcyVKc_eXRxjP=?!w=sZOh+H8ix|{kdp}e(Jq5&}w7E`&8^q@@2d*w*WxD=m<1VU`ucJl^}9p7*iO>GY{ z95(6?Me}#o!1Mz$zPAdw&)Puq*k&h`2f;2}=f8hv6~7Gvnh^2rP}b9e&n1n602NiL z-G7t*DR(0WT~U&66oKX}Z?tZrtvLX(R#aR%PU%)UB1_OKLDTuk=Wu-A!4V61s;s4F z%B*%LMe?0bKMl%z=dtvLBY0;>Rv5}b;A>X#x~`)bFtA#` z+xO6gt5Ql101Fh4&Xoqe3QpHx*E44<^zb3P!6=OqA53KLuIlEWp3lEEzyrbp8#s2t zSfGMhBCZL=a?{*Z2`# |Y{408}KT3v%maTyNjIh3Z`x%<(bS<~muz1J0j+?XMg? zkSwNo68kRIYF#0^N{RmP-IGv&rWUW|u!|OldGW7bL{Sz>Nifp%I08 zZNc*EPq&`L7lWMLXusjvZ$F_YQL3*{ji@<2NF0%sSg+$kFD!cFfhNt@>$;0d z&#MPf7`A}q0hV-*zfYf!6-}Qj^AIoyypCR?`U5I`hq6iSenT4>F-@GBGQJb}I za~(9IMJtS%fI_&)46Yg$sU$1dZXcmBj~=Cf%(ESxhMOTW17$|EF^~+9LnXhe&&V=x zX-{5*3bMQKJEQ=;H{^)ffRl7~vAi+7ASB`ujwE*T9;9sVhq{6fH(wv`iHMww)4|)AH)`99qP?P+ z0OiX$CylzQW;Z?r@I;a!Bu3)MyH;Ofql5f?vSjftmvLcCN=KZFRbATgo?iuEOyf^^ zH0HLKmFG=Z!17=pvsb83qH=vgFcxsPw_#&(^G@ovdqs))S36My8#gJA)=jB;jsSFe zYH4h15+4@kqGq7F_R$c1Gg(`eU@B%P#{DmMD!z)oHpqGRQ@1o6ne`9oh5{?27z7+x zv`%gEh}-Pu>N99x9fpHgCB}3j-=Mh^3bop=d{+9Pf_D5xLE~g{rMS6F5ORNj1CE3d ztNMF>5@lQABK;4bcXU$&mm7l%4pL{Y310$DCZZA+%vBwCeq}lWaOdnj|@Ei|@^=_Jp*e~A+v26i9WP-mA(f>nvZ zSc>uhKlIWn>#BGDb8Pts&#;Y9@TXf)w645$wVN@d@x#cK+)Hu5oFFp)7PD5g^<|eF z2XwqD`->@>AG`_qsg^p3g%D(0r8v(>zJ%CEUhzx-8{tX2x&*~AF5UO#;eaJ?cz>BO_@jxl zt8wmQGGupBp?9ZqIGx+C78qc@{p$du&h|qVjt_1P$}aY_-uQ%H{oEoozYlWrPYcZh zA3~E0T!Dnhd>Bp(d?w5PdU?-fZ)~5f>UX<>Z@IZsDVW8SUTasjOI()F`{WN)oLa7i z75l~2Vlc~pCu5Ep3cuWa24U$%}uBuv}F_+Cn zBA)!K-Yq?L3u%=zlkM!n&vEbf6+2Lx|CD#8L)M4G2aQr$5*W9Vd$CqiJjCy)g=GN`!PfTz_PoJ=RDzb+&Xj)fC%sW z;S|)-sGx`8B(>xb=!jjpJm|uh76cvNgC3~Zoo3(ljbO|rqptRqRTKj{wWR#^xh$%h zkK*k;7zi9O<3E^3HR~3NRmqM)=|1&_hd4K^2@9yh9da(;?f_7$|9tn>d`n_HX6KZf z^M(LPhJ5+Kxo!n>v5`JQXML9p&A$=9210U$gshQIzLo%6Ubv>qHhVtzEd`(-l^5dJ$PEmw6Zy*B=t)6y*pppBdF zRQ%whG0pji#<xb`3Z6~x4_$5D~dA;YlLSZbQ zLWI>~S3lHSl~-o;)y8P`)sc_Un8{0kxV=)ihgx#}bk#4(=6OvJ&IqMyw6!!(E_S0X z?(xbtTD=62mm1QQ$O^iCuYJB#$8|LUrSXw&o20PLH>M|_y18^m_0qv|H1GcmI$s%o zx>7f%-cti3lqG!)sk=v{J5YYSys{#XES|q&0?R5c1stfgW5M%dC~J<60S3emUCUWf z!Q8AU4=;Ik>N|IVXFVH`3}s|o)C{Ix_lDLPfL7@N$w|| zx!IO-&$znyfnE%&zE~7BGn9q7zTpri+nAC}w(BowVWi2Y&m-4et9X&_eE!oxsU;wN z6gj$cYoAM5y$H0 zq92i+T*c$WDA{>=S!N)hM=QkQl&By&Am`Ne9%V9m+ftBF`NRa>!s4WQ`fS7@fkWl zrW9--&`^2by)U5>5L2ky?rUwG{2<7J0~=UmdnY}@gmIiINka*rKV!Ls9v(v3$E6~i zVcOFPovc0r^;M1HY|WopqC@sTCSlgPg~bD*eq1lSKoG zSCdWC%qy)OzNh?H>B|i-4t^p$mYA6S$ISN0i*Qz9sxqO}YzS)=N|T#6;~us(I{x(y zFDe2`vzciOjwZlizr*7%Sm)UD@`R!E4Ue1OmoLm;8g_dR8gOx-{P=+v^V9>sN8eX- z(C9=xT&*uB3!ZN}Iey-9e4&~AX{`PG6~+6z$^Os9S-64?S`rJLEBg)w4?`T%cnfN| zpRL*zaP3mD)%x>koAPNJJM`Q-EH3|&-!CN-pzMM7O=YEdx4E z8mhRTY}hp+(#~wQ$a+l9)rRGw~HDQCBBw1 zs{HFFwKnnT2l^ho*pw#HlQ!w1j}e!f@yQG|N!G;;%NQ7nUo?pPILcN5bT)&htDnm{ Hr-UW|$$D49 literal 0 HcmV?d00001 diff --git a/docs/css/modesta.min.css b/docs/css/modesta.min.css new file mode 100644 index 0000000..69eca2b --- /dev/null +++ b/docs/css/modesta.min.css @@ -0,0 +1 @@ +@import url("https://fonts.googleapis.com/css?family=Montserrat|Roboto");html{font-size:62.5%}body{background:#181818;font-size:1.5em;font-weight:400;line-height:1.6}html,body{color:#fff;font-family:'Montserrat',sans-serif;font-weight:normal;margin:0}h1,h2,h3,h4,h5,h6{font-weight:300;margin-bottom:2rem;margin-top:0}input:not([type="checkbox"]),select,textarea{background-color:#fff;border:1px solid #d1d1d1;border-radius:4px;box-shadow:none;box-sizing:border-box;height:38px;padding:6px 10px}input,textarea,select,fieldset{margin-bottom:1.5rem}label,legend{display:block;font-weight:600;margin-bottom:.5rem}p{margin-top:0}a{color:#1eaedb;text-decoration:none}a:hover{color:#0fa0ce}h1{font-size:4rem;letter-spacing:-.1rem;line-height:1.2}h2{font-size:3.6rem;letter-spacing:-.1rem;line-height:1.25}h3{font-size:3rem;letter-spacing:-.1rem;line-height:1.3}h4{font-size:2.4rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.5rem;letter-spacing:0;line-height:1.6}@media(min-width:550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}.box-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.2),0 1px 5px 0 rgba(0,0,0,0.12)}.bold{font-weight:bold !important}.italic{font-style:italic !important}.center-text{text-align:center !important}.left-text{text-align:left !important}.right-text{text-align:right !important}.full-width{width:100% !important}.half-width{width:50% !important}.no-border{border:0 !important}.no-border-radius{border-radius:0 !important}.no-box-shadow{box-shadow:none !important}.no-margin{margin:0 !important}.no-padding{padding:0 !important}.no-select{user-select:none;-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none;-o-user-select:none}.no-text-shadow{text-shadow:none !important}.relative{position:relative !important}.transparent-bg{background:transparent !important}.center{display:block !important;margin:0 auto !important;text-align:center !important}.alizarin{background-color:#e74c3c !important}.alizarin-text{color:#e74c3c !important}.alizarin-dropshadow{background-color:#e74c3c;filter:drop-shadow(5px 5px 0 rgba(231,76,60,0.6))}.amethyst{background-color:#9b59b6 !important}.amethyst-text{color:#9b59b6 !important}.amethyst-dropshadow{background-color:#9b59b6;filter:drop-shadow(5px 5px 0 rgba(155,89,182,0.6))}.asbestos{background-color:#7f8c8d !important}.asbestos-text{color:#7f8c8d !important}.asbestos-dropshadow{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.belize-hole{background-color:#2980b9 !important}.belize-hole-text{color:#2980b9 !important}.belize-hole-dropshadow{background-color:#2980b9;filter:drop-shadow(5px 5px 0 rgba(41,128,185,0.6))}.black{background-color:#000 !important}.black-text{color:#000 !important}.black-dropshadow{background-color:#000;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.carrot{background-color:#e67e22 !important}.carrot-text{color:#e67e22 !important}.carrot-dropshadow{background-color:#e67e22;filter:drop-shadow(5px 5px 0 rgba(230,126,34,0.6))}.clouds{background-color:#ecf0f1 !important}.clouds-text{color:#ecf0f1 !important}.clouds-dropshadow{background-color:#ecf0f1;filter:drop-shadow(5px 5px 0 rgba(236,240,241,0.6))}.concrete{background-color:#95a5a6 !important}.concrete-text{color:#95a5a6 !important}.concrete-dropshadow{background-color:#95a5a6;filter:drop-shadow(5px 5px 0 rgba(149,165,166,0.6))}.emerald{background-color:#2ecc71 !important}.emerald-text{color:#2ecc71 !important}.emerald-dropshadow{background-color:#2ecc71;filter:drop-shadow(5px 5px 0 rgba(46,204,113,0.6))}.green-sea{background-color:#16a085 !important}.green-sea-text{color:#16a085 !important}.green-sea-dropshadow{background-color:#16a085;filter:drop-shadow(5px 5px 0 rgba(22,160,133,0.6))}.highlight{background-color:#181818 !important}.highlight-text{color:#181818 !important}.highlight-dropshadow{background-color:#181818;filter:drop-shadow(5px 5px 0 rgba(24,24,24,0.6))}.midnight-blue{background-color:#2c3e50 !important}.midnight-blue-text{color:#2c3e50 !important}.midnight-blue-dropshadow{background-color:#2c3e50;filter:drop-shadow(5px 5px 0 rgba(44,62,80,0.6))}.nephritis{background-color:#27ae60 !important}.nephritis-text{color:#27ae60 !important}.nephritis-dropshadow{background-color:#27ae60;filter:drop-shadow(5px 5px 0 rgba(39,174,96,0.6))}.orange{background-color:#f39c12 !important}.orange-text{color:#f39c12 !important}.orange-dropshadow{background-color:#f39c12;filter:drop-shadow(5px 5px 0 rgba(243,156,18,0.6))}.peter-river{background-color:#3498db !important}.peter-river-text{color:#3498db !important}.peter-river-dropshadow{background-color:#3498db;filter:drop-shadow(5px 5px 0 rgba(52,152,219,0.6))}.pomegranate{background-color:#c0392b !important}.pomegranate-text{color:#c0392b !important}.pomegranate-dropshadow{background-color:#c0392b;filter:drop-shadow(5px 5px 0 rgba(192,57,43,0.6))}.primary{background-color:#282828 !important}.primary-text{color:#282828 !important}.primary-dropshadow{background-color:#282828;filter:drop-shadow(5px 5px 0 rgba(40,40,40,0.6))}.pumpkin{background-color:#d35400 !important}.pumpkin-text{color:#d35400 !important}.pumpkin-dropshadow{background-color:#d35400;filter:drop-shadow(5px 5px 0 rgba(211,84,0,0.6))}.secondary{background-color:#303030 !important}.secondary-text{color:#303030 !important}.secondary-dropshadow{background-color:#303030;filter:drop-shadow(5px 5px 0 rgba(48,48,48,0.6))}.silver{background-color:#7f8c8d !important}.silver-text{color:#7f8c8d !important}.silver-dropshadow{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.sun-flower{background-color:#f1c40f !important}.sun-flower-text{color:#f1c40f !important}.sun-flower-dropshadow{background-color:#f1c40f;filter:drop-shadow(5px 5px 0 rgba(241,196,15,0.6))}.transparent{background-color:transparent !important}.transparent-text{color:transparent !important}.transparent-dropshadow{background-color:transparent;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.turquoise{background-color:#1abc9c !important}.turquoise-text{color:#1abc9c !important}.turquoise-dropshadow{background-color:#1abc9c;filter:drop-shadow(5px 5px 0 rgba(26,188,156,0.6))}.wet-asphalt{background-color:#34495e !important}.wet-asphalt-text{color:#34495e !important}.wet-asphalt-dropshadow{background-color:#34495e;filter:drop-shadow(5px 5px 0 rgba(52,73,94,0.6))}.white{background-color:#fff !important}.white-text{color:#fff !important}.white-dropshadow{background-color:#fff;filter:drop-shadow(5px 5px 0 rgba(255,255,255,0.6))}.wisteria{background-color:#8e44ad !important}.wisteria-text{color:#8e44ad !important}.wisteria-dropshadow{background-color:#8e44ad;filter:drop-shadow(5px 5px 0 rgba(142,68,173,0.6))}.devianart{background-color:#f95119 !important}.devianart-text{color:#f95119 !important}.devianart-dropshadow{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.discord{background-color:#7289da !important}.discord-text{color:#7289da !important}.discord-dropshadow{background-color:#7289da;filter:drop-shadow(5px 5px 0 rgba(114,137,218,0.6))}.facebook{background-color:#475595 !important}.facebook-text{color:#475595 !important}.facebook-dropshadow{background-color:#475595;filter:drop-shadow(5px 5px 0 rgba(71,85,149,0.6))}.github{background-color:#292d32 !important}.github-text{color:#292d32 !important}.github-dropshadow{background-color:#292d32;filter:drop-shadow(5px 5px 0 rgba(41,45,50,0.6))}.googleplus{background-color:#d75545 !important}.googleplus-text{color:#d75545 !important}.googleplus-dropshadow{background-color:#d75545;filter:drop-shadow(5px 5px 0 rgba(215,85,69,0.6))}.linkedin{background-color:#3275b3 !important}.linkedin-text{color:#3275b3 !important}.linkedin-dropshadow{background-color:#3275b3;filter:drop-shadow(5px 5px 0 rgba(50,117,179,0.6))}.patreon{background-color:#f96854 !important}.patreon-text{color:#f96854 !important}.patreon-dropshadow{background-color:#f96854;filter:drop-shadow(5px 5px 0 rgba(249,104,84,0.6))}.paypal{background-color:#1e9cd7 !important}.paypal-text{color:#1e9cd7 !important}.paypal-dropshadow{background-color:#1e9cd7;filter:drop-shadow(5px 5px 0 rgba(30,156,215,0.6))}.pinterest{background-color:#c5302f !important}.pinterest-text{color:#c5302f !important}.pinterest-dropshadow{background-color:#c5302f;filter:drop-shadow(5px 5px 0 rgba(197,48,47,0.6))}.reddit{background-color:#f95119 !important}.reddit-text{color:#f95119 !important}.reddit-dropshadow{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.skype{background-color:#41a6ed !important}.skype-text{color:#41a6ed !important}.skype-dropshadow{background-color:#41a6ed;filter:drop-shadow(5px 5px 0 rgba(65,166,237,0.6))}.snapchat{background-color:#f8ff2f !important}.snapchat-text{color:#f8ff2f !important}.snapchat-dropshadow{background-color:#f8ff2f;filter:drop-shadow(5px 5px 0 rgba(248,255,47,0.6))}.soundcloud{background-color:#f95f1b !important}.soundcloud-text{color:#f95f1b !important}.soundcloud-dropshadow{background-color:#f95f1b;filter:drop-shadow(5px 5px 0 rgba(249,95,27,0.6))}.telegram{background-color:#4da8df !important}.telegram-text{color:#4da8df !important}.telegram-dropshadow{background-color:#4da8df;filter:drop-shadow(5px 5px 0 rgba(77,168,223,0.6))}.tumblr{background-color:#394e6b !important}.tumblr-text{color:#394e6b !important}.tumblr-dropshadow{background-color:#394e6b;filter:drop-shadow(5px 5px 0 rgba(57,78,107,0.6))}.twitter{background-color:#4999f0 !important}.twitter-text{color:#4999f0 !important}.twitter-dropshadow{background-color:#4999f0;filter:drop-shadow(5px 5px 0 rgba(73,153,240,0.6))}.viber{background-color:#7f4d9b !important}.viber-text{color:#7f4d9b !important}.viber-dropshadow{background-color:#7f4d9b;filter:drop-shadow(5px 5px 0 rgba(127,77,155,0.6))}.youtube{background-color:#f00 !important}.youtube-text{color:#f00 !important}.youtube-dropshadow{background-color:red;filter:drop-shadow(5px 5px 0 rgba(255,0,0,0.6))}@-webkit-keyframes button-enter{from{opacity:0;top:2em}70%{top:-.3em}100%{opacity:1;top:0}}@-moz-keyframes button-enter{from{opacity:0;top:2em}70%{top:-.3em}100%{opacity:1;top:0}}@-o-keyframes button-enter{from{opacity:0;top:2em}70%{top:-.3em}100%{opacity:1;top:0}}@keyframes button-enter{from{opacity:0;top:2em}70%{top:-.3em}100%{opacity:1;top:0}}@-webkit-keyframes bounce-button{from,20%,53%,80%,to{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}40%,43%{transform:translate3d(0,-30px,0);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);-moz-transform:translate3d(0,-30px,0);-o-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}70%{transform:translate3d(0,-15px,0);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);-moz-transform:translate3d(0,-15px,0);-o-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}90%{transform:translate3d(0,-4px,0);-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);-moz-transform:translate3d(0,-4px,0);-o-transform:translate3d(0,-4px,0)}}@-moz-keyframes bounce-button{from,20%,53%,80%,to{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}40%,43%{transform:translate3d(0,-30px,0);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);-moz-transform:translate3d(0,-30px,0);-o-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}70%{transform:translate3d(0,-15px,0);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);-moz-transform:translate3d(0,-15px,0);-o-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}90%{transform:translate3d(0,-4px,0);-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);-moz-transform:translate3d(0,-4px,0);-o-transform:translate3d(0,-4px,0)}}@-o-keyframes bounce-button{from,20%,53%,80%,to{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}40%,43%{transform:translate3d(0,-30px,0);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);-moz-transform:translate3d(0,-30px,0);-o-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}70%{transform:translate3d(0,-15px,0);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);-moz-transform:translate3d(0,-15px,0);-o-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}90%{transform:translate3d(0,-4px,0);-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);-moz-transform:translate3d(0,-4px,0);-o-transform:translate3d(0,-4px,0)}}@keyframes bounce-button{from,20%,53%,80%,to{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}40%,43%{transform:translate3d(0,-30px,0);-webkit-transform:translate3d(0,-30px,0);-ms-transform:translate3d(0,-30px,0);-moz-transform:translate3d(0,-30px,0);-o-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}70%{transform:translate3d(0,-15px,0);-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);-moz-transform:translate3d(0,-15px,0);-o-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(0.755,0.05,0.855,0.06)}90%{transform:translate3d(0,-4px,0);-webkit-transform:translate3d(0,-4px,0);-ms-transform:translate3d(0,-4px,0);-moz-transform:translate3d(0,-4px,0);-o-transform:translate3d(0,-4px,0)}}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0)}40%{transform:translateY(-30px);-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);-moz-transform:translateY(-30px);-o-transform:translateY(-30px)}60%{transform:translateY(-15px);-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);-moz-transform:translateY(-15px);-o-transform:translateY(-15px)}}@-moz-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0)}40%{transform:translateY(-30px);-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);-moz-transform:translateY(-30px);-o-transform:translateY(-30px)}60%{transform:translateY(-15px);-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);-moz-transform:translateY(-15px);-o-transform:translateY(-15px)}}@-o-keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0)}40%{transform:translateY(-30px);-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);-moz-transform:translateY(-30px);-o-transform:translateY(-30px)}60%{transform:translateY(-15px);-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);-moz-transform:translateY(-15px);-o-transform:translateY(-15px)}}@keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0)}40%{transform:translateY(-30px);-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);-moz-transform:translateY(-30px);-o-transform:translateY(-30px)}60%{transform:translateY(-15px);-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);-moz-transform:translateY(-15px);-o-transform:translateY(-15px)}}@-webkit-keyframes fading{from{opacity:0}100%{opacity:1}}@-moz-keyframes fading{from{opacity:0}100%{opacity:1}}@-o-keyframes fading{from{opacity:0}100%{opacity:1}}@keyframes fading{from{opacity:0}100%{opacity:1}}@-webkit-keyframes load{0%,80%,100%{box-shadow:0 0,0 0;height:4em}40%{box-shadow:0 -1.75em,0 1.75em;height:4em}}@-moz-keyframes load{0%,80%,100%{box-shadow:0 0,0 0;height:4em}40%{box-shadow:0 -1.75em,0 1.75em;height:4em}}@-o-keyframes load{0%,80%,100%{box-shadow:0 0,0 0;height:4em}40%{box-shadow:0 -1.75em,0 1.75em;height:4em}}@keyframes load{0%,80%,100%{box-shadow:0 0,0 0;height:4em}40%{box-shadow:0 -1.75em,0 1.75em;height:4em}}@-webkit-keyframes fading-modal-in{from{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);opacity:0}70%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}}@-moz-keyframes fading-modal-in{from{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);opacity:0}70%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}}@-o-keyframes fading-modal-in{from{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);opacity:0}70%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}}@keyframes fading-modal-in{from{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);opacity:0}70%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}}@-webkit-keyframes fading-modal-out{from{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}30%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);display:none;opacity:0}}@-moz-keyframes fading-modal-out{from{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}30%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);display:none;opacity:0}}@-o-keyframes fading-modal-out{from{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}30%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);display:none;opacity:0}}@keyframes fading-modal-out{from{transform:translateY(0);-webkit-transform:translateY(0);-ms-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);opacity:1}30%{transform:translateY(-1em);-webkit-transform:translateY(-1em);-ms-transform:translateY(-1em);-moz-transform:translateY(-1em);-o-transform:translateY(-1em)}100%{transform:translateY(3.5em);-webkit-transform:translateY(3.5em);-ms-transform:translateY(3.5em);-moz-transform:translateY(3.5em);-o-transform:translateY(3.5em);display:none;opacity:0}}.markdown-container a,.markdown-container html,.markdown-container body,.markdown-container h1,.markdown-container h2,.markdown-container h3,.markdown-container h4,.markdown-container p{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol" !important}.markdown-container{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol" !important;padding:2em 10em}.markdown-container img{border-style:none;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.5));max-width:100%}.markdown-container a:not(.btn){background-color:transparent}.markdown-container a:not(.btn):active,.markdown-container a:not(.btn):hover{outline-width:0}.markdown-container strong{font-weight:inherit;font-weight:bolder}.markdown-container h1{font-size:2em;margin:.67em 0}.markdown-container code,.markdown-container kbd,.markdown-container pre{font-family:monospace,monospace;font-size:1em}.markdown-container pre>code{display:block;padding:1rem 1.5rem;white-space:pre}.markdown-container hr{box-sizing:content-box;height:0;overflow:visible}.markdown-container input{font:inherit;margin:0;overflow:visible}.markdown-container [type="checkbox"]{box-sizing:border-box;padding:0}.markdown-container *{box-sizing:border-box}.markdown-container input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-container a:not(.btn){color:#0366d6;text-decoration:none}.markdown-container a:not(.btn):hover{text-decoration:underline}.markdown-container strong{font-weight:600}.markdown-container hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #dfe2e5}.markdown-container hr::before{display:table;content:""}.markdown-container hr::after{display:table;clear:both;content:""}.markdown-container table{border-spacing:0;border-collapse:collapse;width:100%;overflow:auto;color:#ccc}.markdown-container tr{border-top:1px solid #c6cbd1}.markdown-container td,.markdown-container th{padding:6px 13px;border:1px solid #dfe2e5}.markdown-container h1,.markdown-container h2,.markdown-container h3,.markdown-container h4,.markdown-container h5,.markdown-container h6{margin-top:1em;margin-bottom:0}.markdown-container h1{font-size:32px;font-weight:600}.markdown-container h2{font-size:24px;font-weight:600}.markdown-container h3{font-size:20px;font-weight:600}.markdown-container h4{font-size:16px;font-weight:600}.markdown-container h5{font-size:14px;font-weight:600}.markdown-container h6{font-size:12px;font-weight:600}.markdown-container p{margin-top:0;margin-bottom:10px}.markdown-container blockquote{border-left:.25em solid #dfe2e5;background:rgba(223,226,229,0.05);color:#a6aaae;margin:0;padding:0 1em}.markdown-container ul{list-style:circle inside;margin-bottom:0;margin-top:0;padding-left:0}.markdown-container ol{padding-left:0;margin-top:0;margin-bottom:0}.markdown-container ol ol{list-style-type:lower-roman}.markdown-container ul ol{list-style-type:lower-roman}.markdown-container ul ul ol,.markdown-container ul ol ol{list-style-type:lower-alpha}.markdown-container ol ul ol,.markdown-container ol ol ol{list-style-type:lower-alpha}.markdown-container dd{margin-left:0}.markdown-container code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.markdown-container pre{margin-top:0;margin-bottom:0;font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px}.markdown-container .highlight{background:#282828 !important;color:#d0d0d0}.markdown-container .highlight table td{padding:5px}.markdown-container .highlight table pre{margin:0}.markdown-container .highlight .err{background-color:#ac4142;color:#151515}.markdown-container .highlight .w{color:#d0d0d0}.markdown-container .highlight .c,.markdown-container .highlight .cd,.markdown-container .highlight .cm,.markdown-container .highlight .c1,.markdown-container .highlight .cs{color:#888}.markdown-container .highlight .cp,.markdown-container .highlight .nt{color:#f4bf75}.markdown-container .highlight .o,.markdown-container .highlight .ow,.markdown-container .highlight .p,.markdown-container .highlight .pi{color:#cb4b16}.markdown-container .highlight .gi{color:#90a959}.markdown-container .highlight .gd{color:#ac4142}.markdown-container .highlight .gh{color:#268bd2;font-weight:bold}.markdown-container .highlight .k,.markdown-container .highlight .kn,.markdown-container .highlight .kp,.markdown-container .highlight .kr,.markdown-container .highlight .kv{color:#aa759f}.markdown-container .highlight .kc,.markdown-container .highlight .kt,.markdown-container .highlight .kd{color:#d28445}.markdown-container .highlight .s,.markdown-container .highlight .sb,.markdown-container .highlight .sc,.markdown-container .highlight .sd,.markdown-container .highlight .s2,.markdown-container .highlight .sh,.markdown-container .highlight .sx,.markdown-container .highlight .s1{color:#90a959}.markdown-container .highlight .sr{color:#75b5aa}.markdown-container .highlight .si,.markdown-container .highlight .se{color:#8f5536}.markdown-container .highlight .nn,.markdown-container .highlight .nc,.markdown-container .highlight .no{color:#f4bf75}.markdown-container .highlight .na{color:#6a9fb5}.markdown-container .highlight .m,.markdown-container .highlight .mf,.markdown-container .highlight .mh,.markdown-container .highlight .mi,.markdown-container .highlight .il,.markdown-container .highlight .mo,.markdown-container .highlight .mb,.markdown-container .highlight .mx,.markdown-container .highlight .ss{color:#90a959}.markdown-container .highlighter-rouge{background:#212121 !important;border:1px solid #212121;color:#d0d0d0}.markdown-container pre.highlight code{background:transparent;border:0;word-wrap:break-word;white-space:pre-wrap}.arrow{transition:all 300ms ease;-webkit-transition:all 300ms ease;-ms-transition:all 300ms ease;-moz-transition:all 300ms ease;-o-transition:all 300ms ease;animation:fading 300ms ease-in-out;-webkit-animation:fading 300ms ease-in-out;-ms-animation:fading 300ms ease-in-out;-moz-animation:fading 300ms ease-in-out;-o-animation:fading 300ms ease-in-out;background-image:url("https://modesta.alexflipnote.xyz/css/images/arrow.png");background-size:contain;bottom:20px;height:40px;left:50%;margin-left:-20px;position:absolute;width:40px}.arrow--scrolled{animation:fading 300ms ease-in-out;-webkit-animation:fading 300ms ease-in-out;-ms-animation:fading 300ms ease-in-out;-moz-animation:fading 300ms ease-in-out;-o-animation:fading 300ms ease-in-out;cursor:default;opacity:0}.bounce{animation:bounce 2s infinite;-webkit-animation:bounce 2s infinite;-ms-animation:bounce 2s infinite;-moz-animation:bounce 2s infinite;-o-animation:bounce 2s infinite}.buttons{align-items:center;display:flex;justify-content:center;text-align:center}button{background-color:#464646;border:0;box-shadow:0 3px 0 0 #141414;color:#fff;margin:.5em;outline:0;padding:.3em .8em;position:relative}button:active{box-shadow:none !important;top:2}button.alizarin{background-color:#e74c3c;box-shadow:0 3px 0 0 rgba(231,76,60,0.5)}button.amethyst{background-color:#9b59b6;box-shadow:0 3px 0 0 rgba(155,89,182,0.5)}button.asbestos{background-color:#7f8c8d;box-shadow:0 3px 0 0 rgba(127,140,141,0.5)}button.belize-hole{background-color:#2980b9;box-shadow:0 3px 0 0 rgba(41,128,185,0.5)}button.black{background-color:#000;box-shadow:0 3px 0 0 rgba(0,0,0,0.5)}button.carrot{background-color:#e67e22;box-shadow:0 3px 0 0 rgba(230,126,34,0.5)}button.clouds{background-color:#ecf0f1;box-shadow:0 3px 0 0 rgba(236,240,241,0.5)}button.concrete{background-color:#95a5a6;box-shadow:0 3px 0 0 rgba(149,165,166,0.5)}button.emerald{background-color:#2ecc71;box-shadow:0 3px 0 0 rgba(46,204,113,0.5)}button.green-sea{background-color:#16a085;box-shadow:0 3px 0 0 rgba(22,160,133,0.5)}button.highlight{background-color:#181818;box-shadow:0 3px 0 0 rgba(24,24,24,0.5)}button.midnight-blue{background-color:#2c3e50;box-shadow:0 3px 0 0 rgba(44,62,80,0.5)}button.nephritis{background-color:#27ae60;box-shadow:0 3px 0 0 rgba(39,174,96,0.5)}button.orange{background-color:#f39c12;box-shadow:0 3px 0 0 rgba(243,156,18,0.5)}button.peter-river{background-color:#3498db;box-shadow:0 3px 0 0 rgba(52,152,219,0.5)}button.pomegranate{background-color:#c0392b;box-shadow:0 3px 0 0 rgba(192,57,43,0.5)}button.primary{background-color:#282828;box-shadow:0 3px 0 0 rgba(40,40,40,0.5)}button.pumpkin{background-color:#d35400;box-shadow:0 3px 0 0 rgba(211,84,0,0.5)}button.secondary{background-color:#303030;box-shadow:0 3px 0 0 rgba(48,48,48,0.5)}button.silver{background-color:#7f8c8d;box-shadow:0 3px 0 0 rgba(127,140,141,0.5)}button.sun-flower{background-color:#f1c40f;box-shadow:0 3px 0 0 rgba(241,196,15,0.5)}button.transparent{background-color:transparent;box-shadow:0 3px 0 0 rgba(0,0,0,0.5)}button.turquoise{background-color:#1abc9c;box-shadow:0 3px 0 0 rgba(26,188,156,0.5)}button.wet-asphalt{background-color:#34495e;box-shadow:0 3px 0 0 rgba(52,73,94,0.5)}button.white{background-color:#fff;box-shadow:0 3px 0 0 rgba(255,255,255,0.5)}button.wisteria{background-color:#8e44ad;box-shadow:0 3px 0 0 rgba(142,68,173,0.5)}button.devianart{background-color:#f95119;box-shadow:0 3px 0 0 rgba(249,81,25,0.5)}button.discord{background-color:#7289da;box-shadow:0 3px 0 0 rgba(114,137,218,0.5)}button.facebook{background-color:#475595;box-shadow:0 3px 0 0 rgba(71,85,149,0.5)}button.github{background-color:#292d32;box-shadow:0 3px 0 0 rgba(41,45,50,0.5)}button.googleplus{background-color:#d75545;box-shadow:0 3px 0 0 rgba(215,85,69,0.5)}button.linkedin{background-color:#3275b3;box-shadow:0 3px 0 0 rgba(50,117,179,0.5)}button.patreon{background-color:#f96854;box-shadow:0 3px 0 0 rgba(249,104,84,0.5)}button.paypal{background-color:#1e9cd7;box-shadow:0 3px 0 0 rgba(30,156,215,0.5)}button.pinterest{background-color:#c5302f;box-shadow:0 3px 0 0 rgba(197,48,47,0.5)}button.reddit{background-color:#f95119;box-shadow:0 3px 0 0 rgba(249,81,25,0.5)}button.skype{background-color:#41a6ed;box-shadow:0 3px 0 0 rgba(65,166,237,0.5)}button.snapchat{background-color:#f8ff2f;box-shadow:0 3px 0 0 rgba(248,255,47,0.5)}button.soundcloud{background-color:#f95f1b;box-shadow:0 3px 0 0 rgba(249,95,27,0.5)}button.telegram{background-color:#4da8df;box-shadow:0 3px 0 0 rgba(77,168,223,0.5)}button.tumblr{background-color:#394e6b;box-shadow:0 3px 0 0 rgba(57,78,107,0.5)}button.twitter{background-color:#4999f0;box-shadow:0 3px 0 0 rgba(73,153,240,0.5)}button.viber{background-color:#7f4d9b;box-shadow:0 3px 0 0 rgba(127,77,155,0.5)}button.youtube{background-color:red;box-shadow:0 3px 0 0 rgba(255,0,0,0.5)}.btn,input[type="submit"]{backface-visibility:hidden;-webkit-backface-visibility:hidden;-ms-backface-visibility:hidden;-moz-backface-visibility:hidden;-o-backface-visibility:hidden;transition:all 350ms ease;-webkit-transition:all 350ms ease;-ms-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease;background-color:transparent;border:0;border-radius:5px;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:'Roboto',sans-serif;font-size:16px;font-weight:500;height:38px;line-height:38px;margin:.5em;padding:0 30px;text-align:center;text-decoration:none;white-space:nowrap}.btn.alizarin,input[type="submit"].alizarin{background-color:#e74c3c;filter:drop-shadow(5px 5px 0 rgba(231,76,60,0.6))}.btn.alizarin:hover,input[type="submit"].alizarin:hover{filter:drop-shadow(5px 5px 0 rgba(231,76,60,0.6)) brightness(50%)}.btn.alizarin-border,input[type="submit"].alizarin-border{border:3px solid #e74c3c;height:auto}.btn.alizarin-border:hover,input[type="submit"].alizarin-border:hover{filter:brightness(50%)}.btn.alizarin-border-nodark,input[type="submit"].alizarin-border-nodark{border:3px solid #e74c3c;height:auto}.btn.alizarin-nodark,input[type="submit"].alizarin-nodark{background-color:#e74c3c;filter:drop-shadow(5px 5px 0 rgba(231,76,60,0.6))}.btn.alizarin-flat,input[type="submit"].alizarin-flat{background-color:#e74c3c}.btn.alizarin-flat:hover,input[type="submit"].alizarin-flat:hover{filter:brightness(50%)}.btn.alizarin-flat-nodark,input[type="submit"].alizarin-flat-nodark{background-color:#e74c3c}.btn.amethyst,input[type="submit"].amethyst{background-color:#9b59b6;filter:drop-shadow(5px 5px 0 rgba(155,89,182,0.6))}.btn.amethyst:hover,input[type="submit"].amethyst:hover{filter:drop-shadow(5px 5px 0 rgba(155,89,182,0.6)) brightness(50%)}.btn.amethyst-border,input[type="submit"].amethyst-border{border:3px solid #9b59b6;height:auto}.btn.amethyst-border:hover,input[type="submit"].amethyst-border:hover{filter:brightness(50%)}.btn.amethyst-border-nodark,input[type="submit"].amethyst-border-nodark{border:3px solid #9b59b6;height:auto}.btn.amethyst-nodark,input[type="submit"].amethyst-nodark{background-color:#9b59b6;filter:drop-shadow(5px 5px 0 rgba(155,89,182,0.6))}.btn.amethyst-flat,input[type="submit"].amethyst-flat{background-color:#9b59b6}.btn.amethyst-flat:hover,input[type="submit"].amethyst-flat:hover{filter:brightness(50%)}.btn.amethyst-flat-nodark,input[type="submit"].amethyst-flat-nodark{background-color:#9b59b6}.btn.asbestos,input[type="submit"].asbestos{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.btn.asbestos:hover,input[type="submit"].asbestos:hover{filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6)) brightness(50%)}.btn.asbestos-border,input[type="submit"].asbestos-border{border:3px solid #7f8c8d;height:auto}.btn.asbestos-border:hover,input[type="submit"].asbestos-border:hover{filter:brightness(50%)}.btn.asbestos-border-nodark,input[type="submit"].asbestos-border-nodark{border:3px solid #7f8c8d;height:auto}.btn.asbestos-nodark,input[type="submit"].asbestos-nodark{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.btn.asbestos-flat,input[type="submit"].asbestos-flat{background-color:#7f8c8d}.btn.asbestos-flat:hover,input[type="submit"].asbestos-flat:hover{filter:brightness(50%)}.btn.asbestos-flat-nodark,input[type="submit"].asbestos-flat-nodark{background-color:#7f8c8d}.btn.belize-hole,input[type="submit"].belize-hole{background-color:#2980b9;filter:drop-shadow(5px 5px 0 rgba(41,128,185,0.6))}.btn.belize-hole:hover,input[type="submit"].belize-hole:hover{filter:drop-shadow(5px 5px 0 rgba(41,128,185,0.6)) brightness(50%)}.btn.belize-hole-border,input[type="submit"].belize-hole-border{border:3px solid #2980b9;height:auto}.btn.belize-hole-border:hover,input[type="submit"].belize-hole-border:hover{filter:brightness(50%)}.btn.belize-hole-border-nodark,input[type="submit"].belize-hole-border-nodark{border:3px solid #2980b9;height:auto}.btn.belize-hole-nodark,input[type="submit"].belize-hole-nodark{background-color:#2980b9;filter:drop-shadow(5px 5px 0 rgba(41,128,185,0.6))}.btn.belize-hole-flat,input[type="submit"].belize-hole-flat{background-color:#2980b9}.btn.belize-hole-flat:hover,input[type="submit"].belize-hole-flat:hover{filter:brightness(50%)}.btn.belize-hole-flat-nodark,input[type="submit"].belize-hole-flat-nodark{background-color:#2980b9}.btn.black,input[type="submit"].black{background-color:#000;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.btn.black:hover,input[type="submit"].black:hover{filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6)) brightness(50%)}.btn.black-border,input[type="submit"].black-border{border:3px solid #000;height:auto}.btn.black-border:hover,input[type="submit"].black-border:hover{filter:brightness(50%)}.btn.black-border-nodark,input[type="submit"].black-border-nodark{border:3px solid #000;height:auto}.btn.black-nodark,input[type="submit"].black-nodark{background-color:#000;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.btn.black-flat,input[type="submit"].black-flat{background-color:#000}.btn.black-flat:hover,input[type="submit"].black-flat:hover{filter:brightness(50%)}.btn.black-flat-nodark,input[type="submit"].black-flat-nodark{background-color:#000}.btn.carrot,input[type="submit"].carrot{background-color:#e67e22;filter:drop-shadow(5px 5px 0 rgba(230,126,34,0.6))}.btn.carrot:hover,input[type="submit"].carrot:hover{filter:drop-shadow(5px 5px 0 rgba(230,126,34,0.6)) brightness(50%)}.btn.carrot-border,input[type="submit"].carrot-border{border:3px solid #e67e22;height:auto}.btn.carrot-border:hover,input[type="submit"].carrot-border:hover{filter:brightness(50%)}.btn.carrot-border-nodark,input[type="submit"].carrot-border-nodark{border:3px solid #e67e22;height:auto}.btn.carrot-nodark,input[type="submit"].carrot-nodark{background-color:#e67e22;filter:drop-shadow(5px 5px 0 rgba(230,126,34,0.6))}.btn.carrot-flat,input[type="submit"].carrot-flat{background-color:#e67e22}.btn.carrot-flat:hover,input[type="submit"].carrot-flat:hover{filter:brightness(50%)}.btn.carrot-flat-nodark,input[type="submit"].carrot-flat-nodark{background-color:#e67e22}.btn.clouds,input[type="submit"].clouds{background-color:#ecf0f1;filter:drop-shadow(5px 5px 0 rgba(236,240,241,0.6))}.btn.clouds:hover,input[type="submit"].clouds:hover{filter:drop-shadow(5px 5px 0 rgba(236,240,241,0.6)) brightness(50%)}.btn.clouds-border,input[type="submit"].clouds-border{border:3px solid #ecf0f1;height:auto}.btn.clouds-border:hover,input[type="submit"].clouds-border:hover{filter:brightness(50%)}.btn.clouds-border-nodark,input[type="submit"].clouds-border-nodark{border:3px solid #ecf0f1;height:auto}.btn.clouds-nodark,input[type="submit"].clouds-nodark{background-color:#ecf0f1;filter:drop-shadow(5px 5px 0 rgba(236,240,241,0.6))}.btn.clouds-flat,input[type="submit"].clouds-flat{background-color:#ecf0f1}.btn.clouds-flat:hover,input[type="submit"].clouds-flat:hover{filter:brightness(50%)}.btn.clouds-flat-nodark,input[type="submit"].clouds-flat-nodark{background-color:#ecf0f1}.btn.concrete,input[type="submit"].concrete{background-color:#95a5a6;filter:drop-shadow(5px 5px 0 rgba(149,165,166,0.6))}.btn.concrete:hover,input[type="submit"].concrete:hover{filter:drop-shadow(5px 5px 0 rgba(149,165,166,0.6)) brightness(50%)}.btn.concrete-border,input[type="submit"].concrete-border{border:3px solid #95a5a6;height:auto}.btn.concrete-border:hover,input[type="submit"].concrete-border:hover{filter:brightness(50%)}.btn.concrete-border-nodark,input[type="submit"].concrete-border-nodark{border:3px solid #95a5a6;height:auto}.btn.concrete-nodark,input[type="submit"].concrete-nodark{background-color:#95a5a6;filter:drop-shadow(5px 5px 0 rgba(149,165,166,0.6))}.btn.concrete-flat,input[type="submit"].concrete-flat{background-color:#95a5a6}.btn.concrete-flat:hover,input[type="submit"].concrete-flat:hover{filter:brightness(50%)}.btn.concrete-flat-nodark,input[type="submit"].concrete-flat-nodark{background-color:#95a5a6}.btn.emerald,input[type="submit"].emerald{background-color:#2ecc71;filter:drop-shadow(5px 5px 0 rgba(46,204,113,0.6))}.btn.emerald:hover,input[type="submit"].emerald:hover{filter:drop-shadow(5px 5px 0 rgba(46,204,113,0.6)) brightness(50%)}.btn.emerald-border,input[type="submit"].emerald-border{border:3px solid #2ecc71;height:auto}.btn.emerald-border:hover,input[type="submit"].emerald-border:hover{filter:brightness(50%)}.btn.emerald-border-nodark,input[type="submit"].emerald-border-nodark{border:3px solid #2ecc71;height:auto}.btn.emerald-nodark,input[type="submit"].emerald-nodark{background-color:#2ecc71;filter:drop-shadow(5px 5px 0 rgba(46,204,113,0.6))}.btn.emerald-flat,input[type="submit"].emerald-flat{background-color:#2ecc71}.btn.emerald-flat:hover,input[type="submit"].emerald-flat:hover{filter:brightness(50%)}.btn.emerald-flat-nodark,input[type="submit"].emerald-flat-nodark{background-color:#2ecc71}.btn.green-sea,input[type="submit"].green-sea{background-color:#16a085;filter:drop-shadow(5px 5px 0 rgba(22,160,133,0.6))}.btn.green-sea:hover,input[type="submit"].green-sea:hover{filter:drop-shadow(5px 5px 0 rgba(22,160,133,0.6)) brightness(50%)}.btn.green-sea-border,input[type="submit"].green-sea-border{border:3px solid #16a085;height:auto}.btn.green-sea-border:hover,input[type="submit"].green-sea-border:hover{filter:brightness(50%)}.btn.green-sea-border-nodark,input[type="submit"].green-sea-border-nodark{border:3px solid #16a085;height:auto}.btn.green-sea-nodark,input[type="submit"].green-sea-nodark{background-color:#16a085;filter:drop-shadow(5px 5px 0 rgba(22,160,133,0.6))}.btn.green-sea-flat,input[type="submit"].green-sea-flat{background-color:#16a085}.btn.green-sea-flat:hover,input[type="submit"].green-sea-flat:hover{filter:brightness(50%)}.btn.green-sea-flat-nodark,input[type="submit"].green-sea-flat-nodark{background-color:#16a085}.btn.highlight,input[type="submit"].highlight{background-color:#181818;filter:drop-shadow(5px 5px 0 rgba(24,24,24,0.6))}.btn.highlight:hover,input[type="submit"].highlight:hover{filter:drop-shadow(5px 5px 0 rgba(24,24,24,0.6)) brightness(50%)}.btn.highlight-border,input[type="submit"].highlight-border{border:3px solid #181818;height:auto}.btn.highlight-border:hover,input[type="submit"].highlight-border:hover{filter:brightness(50%)}.btn.highlight-border-nodark,input[type="submit"].highlight-border-nodark{border:3px solid #181818;height:auto}.btn.highlight-nodark,input[type="submit"].highlight-nodark{background-color:#181818;filter:drop-shadow(5px 5px 0 rgba(24,24,24,0.6))}.btn.highlight-flat,input[type="submit"].highlight-flat{background-color:#181818}.btn.highlight-flat:hover,input[type="submit"].highlight-flat:hover{filter:brightness(50%)}.btn.highlight-flat-nodark,input[type="submit"].highlight-flat-nodark{background-color:#181818}.btn.midnight-blue,input[type="submit"].midnight-blue{background-color:#2c3e50;filter:drop-shadow(5px 5px 0 rgba(44,62,80,0.6))}.btn.midnight-blue:hover,input[type="submit"].midnight-blue:hover{filter:drop-shadow(5px 5px 0 rgba(44,62,80,0.6)) brightness(50%)}.btn.midnight-blue-border,input[type="submit"].midnight-blue-border{border:3px solid #2c3e50;height:auto}.btn.midnight-blue-border:hover,input[type="submit"].midnight-blue-border:hover{filter:brightness(50%)}.btn.midnight-blue-border-nodark,input[type="submit"].midnight-blue-border-nodark{border:3px solid #2c3e50;height:auto}.btn.midnight-blue-nodark,input[type="submit"].midnight-blue-nodark{background-color:#2c3e50;filter:drop-shadow(5px 5px 0 rgba(44,62,80,0.6))}.btn.midnight-blue-flat,input[type="submit"].midnight-blue-flat{background-color:#2c3e50}.btn.midnight-blue-flat:hover,input[type="submit"].midnight-blue-flat:hover{filter:brightness(50%)}.btn.midnight-blue-flat-nodark,input[type="submit"].midnight-blue-flat-nodark{background-color:#2c3e50}.btn.nephritis,input[type="submit"].nephritis{background-color:#27ae60;filter:drop-shadow(5px 5px 0 rgba(39,174,96,0.6))}.btn.nephritis:hover,input[type="submit"].nephritis:hover{filter:drop-shadow(5px 5px 0 rgba(39,174,96,0.6)) brightness(50%)}.btn.nephritis-border,input[type="submit"].nephritis-border{border:3px solid #27ae60;height:auto}.btn.nephritis-border:hover,input[type="submit"].nephritis-border:hover{filter:brightness(50%)}.btn.nephritis-border-nodark,input[type="submit"].nephritis-border-nodark{border:3px solid #27ae60;height:auto}.btn.nephritis-nodark,input[type="submit"].nephritis-nodark{background-color:#27ae60;filter:drop-shadow(5px 5px 0 rgba(39,174,96,0.6))}.btn.nephritis-flat,input[type="submit"].nephritis-flat{background-color:#27ae60}.btn.nephritis-flat:hover,input[type="submit"].nephritis-flat:hover{filter:brightness(50%)}.btn.nephritis-flat-nodark,input[type="submit"].nephritis-flat-nodark{background-color:#27ae60}.btn.orange,input[type="submit"].orange{background-color:#f39c12;filter:drop-shadow(5px 5px 0 rgba(243,156,18,0.6))}.btn.orange:hover,input[type="submit"].orange:hover{filter:drop-shadow(5px 5px 0 rgba(243,156,18,0.6)) brightness(50%)}.btn.orange-border,input[type="submit"].orange-border{border:3px solid #f39c12;height:auto}.btn.orange-border:hover,input[type="submit"].orange-border:hover{filter:brightness(50%)}.btn.orange-border-nodark,input[type="submit"].orange-border-nodark{border:3px solid #f39c12;height:auto}.btn.orange-nodark,input[type="submit"].orange-nodark{background-color:#f39c12;filter:drop-shadow(5px 5px 0 rgba(243,156,18,0.6))}.btn.orange-flat,input[type="submit"].orange-flat{background-color:#f39c12}.btn.orange-flat:hover,input[type="submit"].orange-flat:hover{filter:brightness(50%)}.btn.orange-flat-nodark,input[type="submit"].orange-flat-nodark{background-color:#f39c12}.btn.peter-river,input[type="submit"].peter-river{background-color:#3498db;filter:drop-shadow(5px 5px 0 rgba(52,152,219,0.6))}.btn.peter-river:hover,input[type="submit"].peter-river:hover{filter:drop-shadow(5px 5px 0 rgba(52,152,219,0.6)) brightness(50%)}.btn.peter-river-border,input[type="submit"].peter-river-border{border:3px solid #3498db;height:auto}.btn.peter-river-border:hover,input[type="submit"].peter-river-border:hover{filter:brightness(50%)}.btn.peter-river-border-nodark,input[type="submit"].peter-river-border-nodark{border:3px solid #3498db;height:auto}.btn.peter-river-nodark,input[type="submit"].peter-river-nodark{background-color:#3498db;filter:drop-shadow(5px 5px 0 rgba(52,152,219,0.6))}.btn.peter-river-flat,input[type="submit"].peter-river-flat{background-color:#3498db}.btn.peter-river-flat:hover,input[type="submit"].peter-river-flat:hover{filter:brightness(50%)}.btn.peter-river-flat-nodark,input[type="submit"].peter-river-flat-nodark{background-color:#3498db}.btn.pomegranate,input[type="submit"].pomegranate{background-color:#c0392b;filter:drop-shadow(5px 5px 0 rgba(192,57,43,0.6))}.btn.pomegranate:hover,input[type="submit"].pomegranate:hover{filter:drop-shadow(5px 5px 0 rgba(192,57,43,0.6)) brightness(50%)}.btn.pomegranate-border,input[type="submit"].pomegranate-border{border:3px solid #c0392b;height:auto}.btn.pomegranate-border:hover,input[type="submit"].pomegranate-border:hover{filter:brightness(50%)}.btn.pomegranate-border-nodark,input[type="submit"].pomegranate-border-nodark{border:3px solid #c0392b;height:auto}.btn.pomegranate-nodark,input[type="submit"].pomegranate-nodark{background-color:#c0392b;filter:drop-shadow(5px 5px 0 rgba(192,57,43,0.6))}.btn.pomegranate-flat,input[type="submit"].pomegranate-flat{background-color:#c0392b}.btn.pomegranate-flat:hover,input[type="submit"].pomegranate-flat:hover{filter:brightness(50%)}.btn.pomegranate-flat-nodark,input[type="submit"].pomegranate-flat-nodark{background-color:#c0392b}.btn.primary,input[type="submit"].primary{background-color:#282828;filter:drop-shadow(5px 5px 0 rgba(40,40,40,0.6))}.btn.primary:hover,input[type="submit"].primary:hover{filter:drop-shadow(5px 5px 0 rgba(40,40,40,0.6)) brightness(50%)}.btn.primary-border,input[type="submit"].primary-border{border:3px solid #282828;height:auto}.btn.primary-border:hover,input[type="submit"].primary-border:hover{filter:brightness(50%)}.btn.primary-border-nodark,input[type="submit"].primary-border-nodark{border:3px solid #282828;height:auto}.btn.primary-nodark,input[type="submit"].primary-nodark{background-color:#282828;filter:drop-shadow(5px 5px 0 rgba(40,40,40,0.6))}.btn.primary-flat,input[type="submit"].primary-flat{background-color:#282828}.btn.primary-flat:hover,input[type="submit"].primary-flat:hover{filter:brightness(50%)}.btn.primary-flat-nodark,input[type="submit"].primary-flat-nodark{background-color:#282828}.btn.pumpkin,input[type="submit"].pumpkin{background-color:#d35400;filter:drop-shadow(5px 5px 0 rgba(211,84,0,0.6))}.btn.pumpkin:hover,input[type="submit"].pumpkin:hover{filter:drop-shadow(5px 5px 0 rgba(211,84,0,0.6)) brightness(50%)}.btn.pumpkin-border,input[type="submit"].pumpkin-border{border:3px solid #d35400;height:auto}.btn.pumpkin-border:hover,input[type="submit"].pumpkin-border:hover{filter:brightness(50%)}.btn.pumpkin-border-nodark,input[type="submit"].pumpkin-border-nodark{border:3px solid #d35400;height:auto}.btn.pumpkin-nodark,input[type="submit"].pumpkin-nodark{background-color:#d35400;filter:drop-shadow(5px 5px 0 rgba(211,84,0,0.6))}.btn.pumpkin-flat,input[type="submit"].pumpkin-flat{background-color:#d35400}.btn.pumpkin-flat:hover,input[type="submit"].pumpkin-flat:hover{filter:brightness(50%)}.btn.pumpkin-flat-nodark,input[type="submit"].pumpkin-flat-nodark{background-color:#d35400}.btn.secondary,input[type="submit"].secondary{background-color:#303030;filter:drop-shadow(5px 5px 0 rgba(48,48,48,0.6))}.btn.secondary:hover,input[type="submit"].secondary:hover{filter:drop-shadow(5px 5px 0 rgba(48,48,48,0.6)) brightness(50%)}.btn.secondary-border,input[type="submit"].secondary-border{border:3px solid #303030;height:auto}.btn.secondary-border:hover,input[type="submit"].secondary-border:hover{filter:brightness(50%)}.btn.secondary-border-nodark,input[type="submit"].secondary-border-nodark{border:3px solid #303030;height:auto}.btn.secondary-nodark,input[type="submit"].secondary-nodark{background-color:#303030;filter:drop-shadow(5px 5px 0 rgba(48,48,48,0.6))}.btn.secondary-flat,input[type="submit"].secondary-flat{background-color:#303030}.btn.secondary-flat:hover,input[type="submit"].secondary-flat:hover{filter:brightness(50%)}.btn.secondary-flat-nodark,input[type="submit"].secondary-flat-nodark{background-color:#303030}.btn.silver,input[type="submit"].silver{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.btn.silver:hover,input[type="submit"].silver:hover{filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6)) brightness(50%)}.btn.silver-border,input[type="submit"].silver-border{border:3px solid #7f8c8d;height:auto}.btn.silver-border:hover,input[type="submit"].silver-border:hover{filter:brightness(50%)}.btn.silver-border-nodark,input[type="submit"].silver-border-nodark{border:3px solid #7f8c8d;height:auto}.btn.silver-nodark,input[type="submit"].silver-nodark{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.btn.silver-flat,input[type="submit"].silver-flat{background-color:#7f8c8d}.btn.silver-flat:hover,input[type="submit"].silver-flat:hover{filter:brightness(50%)}.btn.silver-flat-nodark,input[type="submit"].silver-flat-nodark{background-color:#7f8c8d}.btn.sun-flower,input[type="submit"].sun-flower{background-color:#f1c40f;filter:drop-shadow(5px 5px 0 rgba(241,196,15,0.6))}.btn.sun-flower:hover,input[type="submit"].sun-flower:hover{filter:drop-shadow(5px 5px 0 rgba(241,196,15,0.6)) brightness(50%)}.btn.sun-flower-border,input[type="submit"].sun-flower-border{border:3px solid #f1c40f;height:auto}.btn.sun-flower-border:hover,input[type="submit"].sun-flower-border:hover{filter:brightness(50%)}.btn.sun-flower-border-nodark,input[type="submit"].sun-flower-border-nodark{border:3px solid #f1c40f;height:auto}.btn.sun-flower-nodark,input[type="submit"].sun-flower-nodark{background-color:#f1c40f;filter:drop-shadow(5px 5px 0 rgba(241,196,15,0.6))}.btn.sun-flower-flat,input[type="submit"].sun-flower-flat{background-color:#f1c40f}.btn.sun-flower-flat:hover,input[type="submit"].sun-flower-flat:hover{filter:brightness(50%)}.btn.sun-flower-flat-nodark,input[type="submit"].sun-flower-flat-nodark{background-color:#f1c40f}.btn.transparent,input[type="submit"].transparent{background-color:transparent;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.btn.transparent:hover,input[type="submit"].transparent:hover{filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6)) brightness(50%)}.btn.transparent-border,input[type="submit"].transparent-border{border:3px solid transparent;height:auto}.btn.transparent-border:hover,input[type="submit"].transparent-border:hover{filter:brightness(50%)}.btn.transparent-border-nodark,input[type="submit"].transparent-border-nodark{border:3px solid transparent;height:auto}.btn.transparent-nodark,input[type="submit"].transparent-nodark{background-color:transparent;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.btn.transparent-flat,input[type="submit"].transparent-flat{background-color:transparent}.btn.transparent-flat:hover,input[type="submit"].transparent-flat:hover{filter:brightness(50%)}.btn.transparent-flat-nodark,input[type="submit"].transparent-flat-nodark{background-color:transparent}.btn.turquoise,input[type="submit"].turquoise{background-color:#1abc9c;filter:drop-shadow(5px 5px 0 rgba(26,188,156,0.6))}.btn.turquoise:hover,input[type="submit"].turquoise:hover{filter:drop-shadow(5px 5px 0 rgba(26,188,156,0.6)) brightness(50%)}.btn.turquoise-border,input[type="submit"].turquoise-border{border:3px solid #1abc9c;height:auto}.btn.turquoise-border:hover,input[type="submit"].turquoise-border:hover{filter:brightness(50%)}.btn.turquoise-border-nodark,input[type="submit"].turquoise-border-nodark{border:3px solid #1abc9c;height:auto}.btn.turquoise-nodark,input[type="submit"].turquoise-nodark{background-color:#1abc9c;filter:drop-shadow(5px 5px 0 rgba(26,188,156,0.6))}.btn.turquoise-flat,input[type="submit"].turquoise-flat{background-color:#1abc9c}.btn.turquoise-flat:hover,input[type="submit"].turquoise-flat:hover{filter:brightness(50%)}.btn.turquoise-flat-nodark,input[type="submit"].turquoise-flat-nodark{background-color:#1abc9c}.btn.wet-asphalt,input[type="submit"].wet-asphalt{background-color:#34495e;filter:drop-shadow(5px 5px 0 rgba(52,73,94,0.6))}.btn.wet-asphalt:hover,input[type="submit"].wet-asphalt:hover{filter:drop-shadow(5px 5px 0 rgba(52,73,94,0.6)) brightness(50%)}.btn.wet-asphalt-border,input[type="submit"].wet-asphalt-border{border:3px solid #34495e;height:auto}.btn.wet-asphalt-border:hover,input[type="submit"].wet-asphalt-border:hover{filter:brightness(50%)}.btn.wet-asphalt-border-nodark,input[type="submit"].wet-asphalt-border-nodark{border:3px solid #34495e;height:auto}.btn.wet-asphalt-nodark,input[type="submit"].wet-asphalt-nodark{background-color:#34495e;filter:drop-shadow(5px 5px 0 rgba(52,73,94,0.6))}.btn.wet-asphalt-flat,input[type="submit"].wet-asphalt-flat{background-color:#34495e}.btn.wet-asphalt-flat:hover,input[type="submit"].wet-asphalt-flat:hover{filter:brightness(50%)}.btn.wet-asphalt-flat-nodark,input[type="submit"].wet-asphalt-flat-nodark{background-color:#34495e}.btn.white,input[type="submit"].white{background-color:#fff;filter:drop-shadow(5px 5px 0 rgba(255,255,255,0.6))}.btn.white:hover,input[type="submit"].white:hover{filter:drop-shadow(5px 5px 0 rgba(255,255,255,0.6)) brightness(50%)}.btn.white-border,input[type="submit"].white-border{border:3px solid #fff;height:auto}.btn.white-border:hover,input[type="submit"].white-border:hover{filter:brightness(50%)}.btn.white-border-nodark,input[type="submit"].white-border-nodark{border:3px solid #fff;height:auto}.btn.white-nodark,input[type="submit"].white-nodark{background-color:#fff;filter:drop-shadow(5px 5px 0 rgba(255,255,255,0.6))}.btn.white-flat,input[type="submit"].white-flat{background-color:#fff}.btn.white-flat:hover,input[type="submit"].white-flat:hover{filter:brightness(50%)}.btn.white-flat-nodark,input[type="submit"].white-flat-nodark{background-color:#fff}.btn.wisteria,input[type="submit"].wisteria{background-color:#8e44ad;filter:drop-shadow(5px 5px 0 rgba(142,68,173,0.6))}.btn.wisteria:hover,input[type="submit"].wisteria:hover{filter:drop-shadow(5px 5px 0 rgba(142,68,173,0.6)) brightness(50%)}.btn.wisteria-border,input[type="submit"].wisteria-border{border:3px solid #8e44ad;height:auto}.btn.wisteria-border:hover,input[type="submit"].wisteria-border:hover{filter:brightness(50%)}.btn.wisteria-border-nodark,input[type="submit"].wisteria-border-nodark{border:3px solid #8e44ad;height:auto}.btn.wisteria-nodark,input[type="submit"].wisteria-nodark{background-color:#8e44ad;filter:drop-shadow(5px 5px 0 rgba(142,68,173,0.6))}.btn.wisteria-flat,input[type="submit"].wisteria-flat{background-color:#8e44ad}.btn.wisteria-flat:hover,input[type="submit"].wisteria-flat:hover{filter:brightness(50%)}.btn.wisteria-flat-nodark,input[type="submit"].wisteria-flat-nodark{background-color:#8e44ad}.btn.devianart,input[type="submit"].devianart{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.btn.devianart:hover,input[type="submit"].devianart:hover{filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6)) brightness(50%)}.btn.devianart-border,input[type="submit"].devianart-border{border:3px solid #f95119;height:auto}.btn.devianart-border:hover,input[type="submit"].devianart-border:hover{filter:brightness(50%)}.btn.devianart-border-nodark,input[type="submit"].devianart-border-nodark{border:3px solid #f95119;height:auto}.btn.devianart-nodark,input[type="submit"].devianart-nodark{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.btn.devianart-flat,input[type="submit"].devianart-flat{background-color:#f95119}.btn.devianart-flat:hover,input[type="submit"].devianart-flat:hover{filter:brightness(50%)}.btn.devianart-flat-nodark,input[type="submit"].devianart-flat-nodark{background-color:#f95119}.btn.discord,input[type="submit"].discord{background-color:#7289da;filter:drop-shadow(5px 5px 0 rgba(114,137,218,0.6))}.btn.discord:hover,input[type="submit"].discord:hover{filter:drop-shadow(5px 5px 0 rgba(114,137,218,0.6)) brightness(50%)}.btn.discord-border,input[type="submit"].discord-border{border:3px solid #7289da;height:auto}.btn.discord-border:hover,input[type="submit"].discord-border:hover{filter:brightness(50%)}.btn.discord-border-nodark,input[type="submit"].discord-border-nodark{border:3px solid #7289da;height:auto}.btn.discord-nodark,input[type="submit"].discord-nodark{background-color:#7289da;filter:drop-shadow(5px 5px 0 rgba(114,137,218,0.6))}.btn.discord-flat,input[type="submit"].discord-flat{background-color:#7289da}.btn.discord-flat:hover,input[type="submit"].discord-flat:hover{filter:brightness(50%)}.btn.discord-flat-nodark,input[type="submit"].discord-flat-nodark{background-color:#7289da}.btn.facebook,input[type="submit"].facebook{background-color:#475595;filter:drop-shadow(5px 5px 0 rgba(71,85,149,0.6))}.btn.facebook:hover,input[type="submit"].facebook:hover{filter:drop-shadow(5px 5px 0 rgba(71,85,149,0.6)) brightness(50%)}.btn.facebook-border,input[type="submit"].facebook-border{border:3px solid #475595;height:auto}.btn.facebook-border:hover,input[type="submit"].facebook-border:hover{filter:brightness(50%)}.btn.facebook-border-nodark,input[type="submit"].facebook-border-nodark{border:3px solid #475595;height:auto}.btn.facebook-nodark,input[type="submit"].facebook-nodark{background-color:#475595;filter:drop-shadow(5px 5px 0 rgba(71,85,149,0.6))}.btn.facebook-flat,input[type="submit"].facebook-flat{background-color:#475595}.btn.facebook-flat:hover,input[type="submit"].facebook-flat:hover{filter:brightness(50%)}.btn.facebook-flat-nodark,input[type="submit"].facebook-flat-nodark{background-color:#475595}.btn.github,input[type="submit"].github{background-color:#292d32;filter:drop-shadow(5px 5px 0 rgba(41,45,50,0.6))}.btn.github:hover,input[type="submit"].github:hover{filter:drop-shadow(5px 5px 0 rgba(41,45,50,0.6)) brightness(50%)}.btn.github-border,input[type="submit"].github-border{border:3px solid #292d32;height:auto}.btn.github-border:hover,input[type="submit"].github-border:hover{filter:brightness(50%)}.btn.github-border-nodark,input[type="submit"].github-border-nodark{border:3px solid #292d32;height:auto}.btn.github-nodark,input[type="submit"].github-nodark{background-color:#292d32;filter:drop-shadow(5px 5px 0 rgba(41,45,50,0.6))}.btn.github-flat,input[type="submit"].github-flat{background-color:#292d32}.btn.github-flat:hover,input[type="submit"].github-flat:hover{filter:brightness(50%)}.btn.github-flat-nodark,input[type="submit"].github-flat-nodark{background-color:#292d32}.btn.googleplus,input[type="submit"].googleplus{background-color:#d75545;filter:drop-shadow(5px 5px 0 rgba(215,85,69,0.6))}.btn.googleplus:hover,input[type="submit"].googleplus:hover{filter:drop-shadow(5px 5px 0 rgba(215,85,69,0.6)) brightness(50%)}.btn.googleplus-border,input[type="submit"].googleplus-border{border:3px solid #d75545;height:auto}.btn.googleplus-border:hover,input[type="submit"].googleplus-border:hover{filter:brightness(50%)}.btn.googleplus-border-nodark,input[type="submit"].googleplus-border-nodark{border:3px solid #d75545;height:auto}.btn.googleplus-nodark,input[type="submit"].googleplus-nodark{background-color:#d75545;filter:drop-shadow(5px 5px 0 rgba(215,85,69,0.6))}.btn.googleplus-flat,input[type="submit"].googleplus-flat{background-color:#d75545}.btn.googleplus-flat:hover,input[type="submit"].googleplus-flat:hover{filter:brightness(50%)}.btn.googleplus-flat-nodark,input[type="submit"].googleplus-flat-nodark{background-color:#d75545}.btn.linkedin,input[type="submit"].linkedin{background-color:#3275b3;filter:drop-shadow(5px 5px 0 rgba(50,117,179,0.6))}.btn.linkedin:hover,input[type="submit"].linkedin:hover{filter:drop-shadow(5px 5px 0 rgba(50,117,179,0.6)) brightness(50%)}.btn.linkedin-border,input[type="submit"].linkedin-border{border:3px solid #3275b3;height:auto}.btn.linkedin-border:hover,input[type="submit"].linkedin-border:hover{filter:brightness(50%)}.btn.linkedin-border-nodark,input[type="submit"].linkedin-border-nodark{border:3px solid #3275b3;height:auto}.btn.linkedin-nodark,input[type="submit"].linkedin-nodark{background-color:#3275b3;filter:drop-shadow(5px 5px 0 rgba(50,117,179,0.6))}.btn.linkedin-flat,input[type="submit"].linkedin-flat{background-color:#3275b3}.btn.linkedin-flat:hover,input[type="submit"].linkedin-flat:hover{filter:brightness(50%)}.btn.linkedin-flat-nodark,input[type="submit"].linkedin-flat-nodark{background-color:#3275b3}.btn.patreon,input[type="submit"].patreon{background-color:#f96854;filter:drop-shadow(5px 5px 0 rgba(249,104,84,0.6))}.btn.patreon:hover,input[type="submit"].patreon:hover{filter:drop-shadow(5px 5px 0 rgba(249,104,84,0.6)) brightness(50%)}.btn.patreon-border,input[type="submit"].patreon-border{border:3px solid #f96854;height:auto}.btn.patreon-border:hover,input[type="submit"].patreon-border:hover{filter:brightness(50%)}.btn.patreon-border-nodark,input[type="submit"].patreon-border-nodark{border:3px solid #f96854;height:auto}.btn.patreon-nodark,input[type="submit"].patreon-nodark{background-color:#f96854;filter:drop-shadow(5px 5px 0 rgba(249,104,84,0.6))}.btn.patreon-flat,input[type="submit"].patreon-flat{background-color:#f96854}.btn.patreon-flat:hover,input[type="submit"].patreon-flat:hover{filter:brightness(50%)}.btn.patreon-flat-nodark,input[type="submit"].patreon-flat-nodark{background-color:#f96854}.btn.paypal,input[type="submit"].paypal{background-color:#1e9cd7;filter:drop-shadow(5px 5px 0 rgba(30,156,215,0.6))}.btn.paypal:hover,input[type="submit"].paypal:hover{filter:drop-shadow(5px 5px 0 rgba(30,156,215,0.6)) brightness(50%)}.btn.paypal-border,input[type="submit"].paypal-border{border:3px solid #1e9cd7;height:auto}.btn.paypal-border:hover,input[type="submit"].paypal-border:hover{filter:brightness(50%)}.btn.paypal-border-nodark,input[type="submit"].paypal-border-nodark{border:3px solid #1e9cd7;height:auto}.btn.paypal-nodark,input[type="submit"].paypal-nodark{background-color:#1e9cd7;filter:drop-shadow(5px 5px 0 rgba(30,156,215,0.6))}.btn.paypal-flat,input[type="submit"].paypal-flat{background-color:#1e9cd7}.btn.paypal-flat:hover,input[type="submit"].paypal-flat:hover{filter:brightness(50%)}.btn.paypal-flat-nodark,input[type="submit"].paypal-flat-nodark{background-color:#1e9cd7}.btn.pinterest,input[type="submit"].pinterest{background-color:#c5302f;filter:drop-shadow(5px 5px 0 rgba(197,48,47,0.6))}.btn.pinterest:hover,input[type="submit"].pinterest:hover{filter:drop-shadow(5px 5px 0 rgba(197,48,47,0.6)) brightness(50%)}.btn.pinterest-border,input[type="submit"].pinterest-border{border:3px solid #c5302f;height:auto}.btn.pinterest-border:hover,input[type="submit"].pinterest-border:hover{filter:brightness(50%)}.btn.pinterest-border-nodark,input[type="submit"].pinterest-border-nodark{border:3px solid #c5302f;height:auto}.btn.pinterest-nodark,input[type="submit"].pinterest-nodark{background-color:#c5302f;filter:drop-shadow(5px 5px 0 rgba(197,48,47,0.6))}.btn.pinterest-flat,input[type="submit"].pinterest-flat{background-color:#c5302f}.btn.pinterest-flat:hover,input[type="submit"].pinterest-flat:hover{filter:brightness(50%)}.btn.pinterest-flat-nodark,input[type="submit"].pinterest-flat-nodark{background-color:#c5302f}.btn.reddit,input[type="submit"].reddit{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.btn.reddit:hover,input[type="submit"].reddit:hover{filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6)) brightness(50%)}.btn.reddit-border,input[type="submit"].reddit-border{border:3px solid #f95119;height:auto}.btn.reddit-border:hover,input[type="submit"].reddit-border:hover{filter:brightness(50%)}.btn.reddit-border-nodark,input[type="submit"].reddit-border-nodark{border:3px solid #f95119;height:auto}.btn.reddit-nodark,input[type="submit"].reddit-nodark{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.btn.reddit-flat,input[type="submit"].reddit-flat{background-color:#f95119}.btn.reddit-flat:hover,input[type="submit"].reddit-flat:hover{filter:brightness(50%)}.btn.reddit-flat-nodark,input[type="submit"].reddit-flat-nodark{background-color:#f95119}.btn.skype,input[type="submit"].skype{background-color:#41a6ed;filter:drop-shadow(5px 5px 0 rgba(65,166,237,0.6))}.btn.skype:hover,input[type="submit"].skype:hover{filter:drop-shadow(5px 5px 0 rgba(65,166,237,0.6)) brightness(50%)}.btn.skype-border,input[type="submit"].skype-border{border:3px solid #41a6ed;height:auto}.btn.skype-border:hover,input[type="submit"].skype-border:hover{filter:brightness(50%)}.btn.skype-border-nodark,input[type="submit"].skype-border-nodark{border:3px solid #41a6ed;height:auto}.btn.skype-nodark,input[type="submit"].skype-nodark{background-color:#41a6ed;filter:drop-shadow(5px 5px 0 rgba(65,166,237,0.6))}.btn.skype-flat,input[type="submit"].skype-flat{background-color:#41a6ed}.btn.skype-flat:hover,input[type="submit"].skype-flat:hover{filter:brightness(50%)}.btn.skype-flat-nodark,input[type="submit"].skype-flat-nodark{background-color:#41a6ed}.btn.snapchat,input[type="submit"].snapchat{background-color:#f8ff2f;filter:drop-shadow(5px 5px 0 rgba(248,255,47,0.6))}.btn.snapchat:hover,input[type="submit"].snapchat:hover{filter:drop-shadow(5px 5px 0 rgba(248,255,47,0.6)) brightness(50%)}.btn.snapchat-border,input[type="submit"].snapchat-border{border:3px solid #f8ff2f;height:auto}.btn.snapchat-border:hover,input[type="submit"].snapchat-border:hover{filter:brightness(50%)}.btn.snapchat-border-nodark,input[type="submit"].snapchat-border-nodark{border:3px solid #f8ff2f;height:auto}.btn.snapchat-nodark,input[type="submit"].snapchat-nodark{background-color:#f8ff2f;filter:drop-shadow(5px 5px 0 rgba(248,255,47,0.6))}.btn.snapchat-flat,input[type="submit"].snapchat-flat{background-color:#f8ff2f}.btn.snapchat-flat:hover,input[type="submit"].snapchat-flat:hover{filter:brightness(50%)}.btn.snapchat-flat-nodark,input[type="submit"].snapchat-flat-nodark{background-color:#f8ff2f}.btn.soundcloud,input[type="submit"].soundcloud{background-color:#f95f1b;filter:drop-shadow(5px 5px 0 rgba(249,95,27,0.6))}.btn.soundcloud:hover,input[type="submit"].soundcloud:hover{filter:drop-shadow(5px 5px 0 rgba(249,95,27,0.6)) brightness(50%)}.btn.soundcloud-border,input[type="submit"].soundcloud-border{border:3px solid #f95f1b;height:auto}.btn.soundcloud-border:hover,input[type="submit"].soundcloud-border:hover{filter:brightness(50%)}.btn.soundcloud-border-nodark,input[type="submit"].soundcloud-border-nodark{border:3px solid #f95f1b;height:auto}.btn.soundcloud-nodark,input[type="submit"].soundcloud-nodark{background-color:#f95f1b;filter:drop-shadow(5px 5px 0 rgba(249,95,27,0.6))}.btn.soundcloud-flat,input[type="submit"].soundcloud-flat{background-color:#f95f1b}.btn.soundcloud-flat:hover,input[type="submit"].soundcloud-flat:hover{filter:brightness(50%)}.btn.soundcloud-flat-nodark,input[type="submit"].soundcloud-flat-nodark{background-color:#f95f1b}.btn.telegram,input[type="submit"].telegram{background-color:#4da8df;filter:drop-shadow(5px 5px 0 rgba(77,168,223,0.6))}.btn.telegram:hover,input[type="submit"].telegram:hover{filter:drop-shadow(5px 5px 0 rgba(77,168,223,0.6)) brightness(50%)}.btn.telegram-border,input[type="submit"].telegram-border{border:3px solid #4da8df;height:auto}.btn.telegram-border:hover,input[type="submit"].telegram-border:hover{filter:brightness(50%)}.btn.telegram-border-nodark,input[type="submit"].telegram-border-nodark{border:3px solid #4da8df;height:auto}.btn.telegram-nodark,input[type="submit"].telegram-nodark{background-color:#4da8df;filter:drop-shadow(5px 5px 0 rgba(77,168,223,0.6))}.btn.telegram-flat,input[type="submit"].telegram-flat{background-color:#4da8df}.btn.telegram-flat:hover,input[type="submit"].telegram-flat:hover{filter:brightness(50%)}.btn.telegram-flat-nodark,input[type="submit"].telegram-flat-nodark{background-color:#4da8df}.btn.tumblr,input[type="submit"].tumblr{background-color:#394e6b;filter:drop-shadow(5px 5px 0 rgba(57,78,107,0.6))}.btn.tumblr:hover,input[type="submit"].tumblr:hover{filter:drop-shadow(5px 5px 0 rgba(57,78,107,0.6)) brightness(50%)}.btn.tumblr-border,input[type="submit"].tumblr-border{border:3px solid #394e6b;height:auto}.btn.tumblr-border:hover,input[type="submit"].tumblr-border:hover{filter:brightness(50%)}.btn.tumblr-border-nodark,input[type="submit"].tumblr-border-nodark{border:3px solid #394e6b;height:auto}.btn.tumblr-nodark,input[type="submit"].tumblr-nodark{background-color:#394e6b;filter:drop-shadow(5px 5px 0 rgba(57,78,107,0.6))}.btn.tumblr-flat,input[type="submit"].tumblr-flat{background-color:#394e6b}.btn.tumblr-flat:hover,input[type="submit"].tumblr-flat:hover{filter:brightness(50%)}.btn.tumblr-flat-nodark,input[type="submit"].tumblr-flat-nodark{background-color:#394e6b}.btn.twitter,input[type="submit"].twitter{background-color:#4999f0;filter:drop-shadow(5px 5px 0 rgba(73,153,240,0.6))}.btn.twitter:hover,input[type="submit"].twitter:hover{filter:drop-shadow(5px 5px 0 rgba(73,153,240,0.6)) brightness(50%)}.btn.twitter-border,input[type="submit"].twitter-border{border:3px solid #4999f0;height:auto}.btn.twitter-border:hover,input[type="submit"].twitter-border:hover{filter:brightness(50%)}.btn.twitter-border-nodark,input[type="submit"].twitter-border-nodark{border:3px solid #4999f0;height:auto}.btn.twitter-nodark,input[type="submit"].twitter-nodark{background-color:#4999f0;filter:drop-shadow(5px 5px 0 rgba(73,153,240,0.6))}.btn.twitter-flat,input[type="submit"].twitter-flat{background-color:#4999f0}.btn.twitter-flat:hover,input[type="submit"].twitter-flat:hover{filter:brightness(50%)}.btn.twitter-flat-nodark,input[type="submit"].twitter-flat-nodark{background-color:#4999f0}.btn.viber,input[type="submit"].viber{background-color:#7f4d9b;filter:drop-shadow(5px 5px 0 rgba(127,77,155,0.6))}.btn.viber:hover,input[type="submit"].viber:hover{filter:drop-shadow(5px 5px 0 rgba(127,77,155,0.6)) brightness(50%)}.btn.viber-border,input[type="submit"].viber-border{border:3px solid #7f4d9b;height:auto}.btn.viber-border:hover,input[type="submit"].viber-border:hover{filter:brightness(50%)}.btn.viber-border-nodark,input[type="submit"].viber-border-nodark{border:3px solid #7f4d9b;height:auto}.btn.viber-nodark,input[type="submit"].viber-nodark{background-color:#7f4d9b;filter:drop-shadow(5px 5px 0 rgba(127,77,155,0.6))}.btn.viber-flat,input[type="submit"].viber-flat{background-color:#7f4d9b}.btn.viber-flat:hover,input[type="submit"].viber-flat:hover{filter:brightness(50%)}.btn.viber-flat-nodark,input[type="submit"].viber-flat-nodark{background-color:#7f4d9b}.btn.youtube,input[type="submit"].youtube{background-color:red;filter:drop-shadow(5px 5px 0 rgba(255,0,0,0.6))}.btn.youtube:hover,input[type="submit"].youtube:hover{filter:drop-shadow(5px 5px 0 rgba(255,0,0,0.6)) brightness(50%)}.btn.youtube-border,input[type="submit"].youtube-border{border:3px solid red;height:auto}.btn.youtube-border:hover,input[type="submit"].youtube-border:hover{filter:brightness(50%)}.btn.youtube-border-nodark,input[type="submit"].youtube-border-nodark{border:3px solid red;height:auto}.btn.youtube-nodark,input[type="submit"].youtube-nodark{background-color:red;filter:drop-shadow(5px 5px 0 rgba(255,0,0,0.6))}.btn.youtube-flat,input[type="submit"].youtube-flat{background-color:red}.btn.youtube-flat:hover,input[type="submit"].youtube-flat:hover{filter:brightness(50%)}.btn.youtube-flat-nodark,input[type="submit"].youtube-flat-nodark{background-color:red}.btn:hover,input[type="submit"]:hover{transition:all 350ms ease;-webkit-transition:all 350ms ease;-ms-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease;color:#fff}.btn.animation,input[type="submit"].animation{animation:button-enter 1s ease-in-out forwards;-webkit-animation:button-enter 1s ease-in-out forwards;-ms-animation:button-enter 1s ease-in-out forwards;-moz-animation:button-enter 1s ease-in-out forwards;-o-animation:button-enter 1s ease-in-out forwards;opacity:0;position:relative}.btn.animation:nth-child(1),input[type="submit"].animation:nth-child(1){animation-delay:.25s}.btn.animation:nth-child(2),input[type="submit"].animation:nth-child(2){animation-delay:.5s}.btn.animation:nth-child(3),input[type="submit"].animation:nth-child(3){animation-delay:.75s}.btn.animation:nth-child(4),input[type="submit"].animation:nth-child(4){animation-delay:1s}.btn.animation:nth-child(5),input[type="submit"].animation:nth-child(5){animation-delay:1.25s}.btn.animation:nth-child(6),input[type="submit"].animation:nth-child(6){animation-delay:1.5s}.btn.animation:nth-child(7),input[type="submit"].animation:nth-child(7){animation-delay:1.75s}.btn.animation:nth-child(8),input[type="submit"].animation:nth-child(8){animation-delay:2s}.btn.animation:nth-child(9),input[type="submit"].animation:nth-child(9){animation-delay:2.25s}.btn.animation:nth-child(10),input[type="submit"].animation:nth-child(10){animation-delay:2.5s}.btn.animation:nth-child(11),input[type="submit"].animation:nth-child(11){animation-delay:2.75s}.btn.animation:nth-child(12),input[type="submit"].animation:nth-child(12){animation-delay:3s}.btn.animation:nth-child(13),input[type="submit"].animation:nth-child(13){animation-delay:3.25s}.btn.animation:nth-child(14),input[type="submit"].animation:nth-child(14){animation-delay:3.5s}.btn.animation:nth-child(15),input[type="submit"].animation:nth-child(15){animation-delay:3.75s}.btn.animation-bounce,input[type="submit"].animation-bounce{animation:bounce-button 1s ease-in-out;-webkit-animation:bounce-button 1s ease-in-out;-ms-animation:bounce-button 1s ease-in-out;-moz-animation:bounce-button 1s ease-in-out;-o-animation:bounce-button 1s ease-in-out}.btn.animation-bounce:nth-child(1),input[type="submit"].animation-bounce:nth-child(1){animation-delay:.25s}.btn.animation-bounce:nth-child(2),input[type="submit"].animation-bounce:nth-child(2){animation-delay:.5s}.btn.animation-bounce:nth-child(3),input[type="submit"].animation-bounce:nth-child(3){animation-delay:.75s}.btn.animation-bounce:nth-child(4),input[type="submit"].animation-bounce:nth-child(4){animation-delay:1s}.btn.animation-bounce:nth-child(5),input[type="submit"].animation-bounce:nth-child(5){animation-delay:1.25s}.btn.animation-bounce:nth-child(6),input[type="submit"].animation-bounce:nth-child(6){animation-delay:1.5s}.btn.animation-bounce:nth-child(7),input[type="submit"].animation-bounce:nth-child(7){animation-delay:1.75s}.btn.animation-bounce:nth-child(8),input[type="submit"].animation-bounce:nth-child(8){animation-delay:2s}.btn.animation-bounce:nth-child(9),input[type="submit"].animation-bounce:nth-child(9){animation-delay:2.25s}.btn.animation-bounce:nth-child(10),input[type="submit"].animation-bounce:nth-child(10){animation-delay:2.5s}.btn.animation-bounce:nth-child(11),input[type="submit"].animation-bounce:nth-child(11){animation-delay:2.75s}.btn.animation-bounce:nth-child(12),input[type="submit"].animation-bounce:nth-child(12){animation-delay:3s}.btn.animation-bounce:nth-child(13),input[type="submit"].animation-bounce:nth-child(13){animation-delay:3.25s}.btn.animation-bounce:nth-child(14),input[type="submit"].animation-bounce:nth-child(14){animation-delay:3.5s}.btn.animation-bounce:nth-child(15),input[type="submit"].animation-bounce:nth-child(15){animation-delay:3.75s}.btn.hover-scale,input[type="submit"].hover-scale{transition:all 350ms ease;-webkit-transition:all 350ms ease;-ms-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease}.btn.hover-scale:hover,input[type="submit"].hover-scale:hover{transform:scale(1.05);-webkit-transform:scale(1.05);-ms-transform:scale(1.05);-moz-transform:scale(1.05);-o-transform:scale(1.05)}.btn.hover-scale-inset,input[type="submit"].hover-scale-inset{transition:all 350ms ease;-webkit-transition:all 350ms ease;-ms-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease}.btn.hover-scale-inset:hover,input[type="submit"].hover-scale-inset:hover{transform:scale(0.95);-webkit-transform:scale(0.95);-ms-transform:scale(0.95);-moz-transform:scale(0.95);-o-transform:scale(0.95)}.btn.hover-bounce:hover,input[type="submit"].hover-bounce:hover{animation:bounce-button 750ms ease-in-out;-webkit-animation:bounce-button 750ms ease-in-out;-ms-animation:bounce-button 750ms ease-in-out;-moz-animation:bounce-button 750ms ease-in-out;-o-animation:bounce-button 750ms ease-in-out}.btn.hover,input[type="submit"].hover{transition:all 350ms ease;-webkit-transition:all 350ms ease;-ms-transition:all 350ms ease;-moz-transition:all 350ms ease;-o-transition:all 350ms ease}.btn.hover:hover,input[type="submit"].hover:hover{transform:translateY(-0.5em);-webkit-transform:translateY(-0.5em);-ms-transform:translateY(-0.5em);-moz-transform:translateY(-0.5em);-o-transform:translateY(-0.5em)}.emoji{background-position:center center;background-repeat:no-repeat;background-size:1em 1em;display:inline-block;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em;width:1em}.emoji-lg{background-size:1.33em 1.33em;height:1.33em;margin:0 .0665em 0 .133em;vertical-align:-.133em;width:1.33em}.emoji-2x{background-size:2em 2em;height:2em;margin:0 .1em 0 .2em;vertical-align:-.2em;width:2em}.emoji-3x{background-size:3em 3em;height:3em;margin:0 .15em 0 .3em;vertical-align:-.3em;width:3em}.emoji-4x{background-size:4em 4em;height:4em;margin:0 .2em 0 .4em;vertical-align:-.4em;width:4em}.emoji-5x{background-size:5em 5em;height:5em;margin:0 .25em 0 .5em;vertical-align:-.5em;width:5em}.center-object{transform:translate(-50%,-50%);-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);left:50%;position:absolute;top:50%;width:100%}.fullscreen{background-color:#181818}.fullscreen .background{background-attachment:fixed;background-position:center;background-repeat:no-repeat;background-size:cover;filter:blur(5px) brightness(50%);height:100%;width:100%}.fullscreen .background.slideshow{transition:opacity 1s ease-in;-webkit-transition:opacity 1s ease-in;-ms-transition:opacity 1s ease-in;-moz-transition:opacity 1s ease-in;-o-transition:opacity 1s ease-in;position:absolute}.fullscreen .me{justify-content:center;width:100%}.fullscreen.half .background{height:50%}.fullscreen.half .center-object{top:25%}.fullscreen.half .center-object .btn{padding:0 20px}.fullscreen.half .center-object .me .image-title{width:6em}.fullscreen.half .center-object .me .title{font-size:4rem}.fullscreen.half .center-object .me .undertitle{font-size:1.75rem}.me{align-items:center;display:flex;flex-direction:column;height:auto;padding:1rem 0;z-index:10}.me.unset{flex-direction:unset}.me.unset .image-title{margin-right:.25em}.me.unset .image-title,.me.unset .title{flex-direction:column}.me .image-title{border-radius:100%;height:9em;width:9em}.me .image-title.large-border{border-radius:25px}.me .image-title.medium-border{border-radius:15px}.me .image-title.small-border{border-radius:5px}.me .image-title.no-border{border-radius:0}.me .title{filter:drop-shadow(6px 6px 0 rgba(0,0,0,0.4));font-size:5rem;letter-spacing:1px;margin:.25em}.me .undertitle{filter:drop-shadow(3px 3px 0 rgba(0,0,0,0.4));font-size:2.5rem;letter-spacing:1px;margin:0}.container{box-sizing:border-box;margin:0 auto;margin-bottom:2.5em;margin-top:1.5em;max-width:1100px;padding:0 2em;position:relative;width:100%}.column,.columns{box-sizing:border-box;float:left;width:100%}.row .column{margin-left:4%}.row .column:first-child{margin-left:0}.row .one-full.column{width:95%}.row .one-half.column{width:48%}.row .one-third.column{width:30.6666666667%}.row .one-fourth.column{width:21.9999999999%}.row::after{clear:both;content:'';display:table}.flex-grid{box-sizing:border-box;display:flex;flex:0 1 auto;flex-direction:row;flex-wrap:wrap}.flex-grid .col-xs,.flex-grid .col-xs-1,.flex-grid .col-xs-2,.flex-grid .col-xs-3,.flex-grid .col-xs-4,.flex-grid .col-xs-5,.flex-grid .col-xs-6,.flex-grid .col-xs-7,.flex-grid .col-xs-8,.flex-grid .col-xs-9,.flex-grid .col-xs-10,.flex-grid .col-xs-11,.flex-grid .col-xs-12{box-sizing:border-box;flex:0 0 auto;padding-left:.5rem;padding-right:.5rem}.flex-grid [class^="col-xs"]:first-child{padding-left:0}.flex-grid [class^="col-xs"]:last-child{padding-right:0}.flex-grid .col-xs{flex-basis:0;flex-grow:1;max-width:100%}.flex-grid .col-xs-1{flex-basis:8.33%;max-width:8.33%}.flex-grid .col-xs-2{flex-basis:16.66%;max-width:16.66%}.flex-grid .col-xs-3{flex-basis:25%;max-width:25%}.flex-grid .col-xs-4{flex-basis:33.33%;max-width:33.33%}.flex-grid .col-xs-5{flex-basis:41.66%;max-width:41.66%}.flex-grid .col-xs-6{flex-basis:50%;max-width:50%}.flex-grid .col-xs-7{flex-basis:58.33%;max-width:58.33%}.flex-grid .col-xs-8{flex-basis:66.66%;max-width:66.66%}.flex-grid .col-xs-9{flex-basis:75%;max-width:75%}.flex-grid .col-xs-10{flex-basis:83.33%;max-width:83.33%}.flex-grid .col-xs-11{flex-basis:91.66%;max-width:91.66%}.flex-grid .col-xs-12{flex-basis:100%;max-width:100%}.modal{animation:fading-modal-in 600ms ease-in-out;-webkit-animation:fading-modal-in 600ms ease-in-out;-ms-animation:fading-modal-in 600ms ease-in-out;-moz-animation:fading-modal-in 600ms ease-in-out;-o-animation:fading-modal-in 600ms ease-in-out;background-color:rgba(0,0,0,0.4);display:none;height:100%;left:0;overflow:auto;padding-top:100px;position:fixed;top:0;width:100%;z-index:1}.modal--close{animation:fading-modal-out 600ms ease-in-out;-webkit-animation:fading-modal-out 600ms ease-in-out;-ms-animation:fading-modal-out 600ms ease-in-out;-moz-animation:fading-modal-out 600ms ease-in-out;-o-animation:fading-modal-out 600ms ease-in-out}.modal-content{background-color:#272727;font-family:Arial;margin:auto;padding:20px;width:80%}.modal-content *{color:#fff}.modal-content p{margin:5px}.modal-content ul,.modal-content li{list-style-type:disc;margin:0;padding:0}.modal-content .close{color:#bbb;float:right;font-size:2.5em;font-weight:bold;margin-right:-.1em;margin-top:-.7em}.modal-content .close:hover,.modal-content .close:focus{color:#fff;cursor:pointer;text-decoration:none}.nav-container{align-items:center;background:#282828;border-bottom:4px solid #303030;display:flex;flex-direction:column;height:4em;justify-content:center;left:0;top:0;width:100%;z-index:100}.nav-container.default{background:transparent;border-bottom:0;display:block;height:auto;margin:0 auto;max-width:1100px}.nav-container.default #menu-icon{display:none}.nav-container.default .nav-title{float:left;font-size:20px;margin-left:.4em;padding:.5em 0;text-align:left}.nav-container.default .sidenav{transform:none;-webkit-transform:none;-ms-transform:none;-moz-transform:none;-o-transform:none;background-color:transparent;height:auto;margin-right:.4em;position:relative;text-align:right;width:auto}.nav-container.default .sidenav .current{color:#2ecc71}.nav-container.default .sidenav a{transition:.3s;-webkit-transition:.3s;-ms-transition:.3s;-moz-transition:.3s;-o-transition:.3s;color:#818181;display:inline-block;font-size:20px;margin:0 .3em;padding:.4em 0;text-decoration:none}.nav-container.default .sidenav a:hover{transform:none;-webkit-transform:none;-ms-transform:none;-moz-transform:none;-o-transform:none;color:#f1f1f1}.nav-container #menu-icon{background:#4c8fec url("images/menu-icon.png") center;cursor:pointer;display:inline-block;height:40px;left:0;margin:11px;position:absolute;top:0;width:40px}.nav-container .nav-title{margin:0;text-align:center}.nav-container .nav-content{align-items:center;display:flex;height:4em;padding-left:1em}.nav-container .nav-content.nav-button{padding-left:65px}.nav-container .nav-content *{margin:0}.nav-container .sidenav{transform:translateX(-250px);-webkit-transform:translateX(-250px);-ms-transform:translateX(-250px);-moz-transform:translateX(-250px);-o-transform:translateX(-250px);transition:.5s;-webkit-transition:.5s;-ms-transition:.5s;-moz-transition:.5s;-o-transition:.5s;background-color:#181818;height:100%;left:0;overflow-x:hidden;position:fixed;top:0;width:250px;z-index:1}.nav-container .sidenav .current{color:#2ecc71}.nav-container .sidenav a{transition:.3s;-webkit-transition:.3s;-ms-transition:.3s;-moz-transition:.3s;-o-transition:.3s;color:#818181;display:block;font-size:25px;padding:8px 8px 8px 32px;text-decoration:none}.nav-container .sidenav a:hover{transform:translateX(0.5em);-webkit-transform:translateX(0.5em);-ms-transform:translateX(0.5em);-moz-transform:translateX(0.5em);-o-transform:translateX(0.5em);color:#f1f1f1}.loader,.loader::before,.loader::after{animation:load 1s infinite ease-in-out;-webkit-animation:load 1s infinite ease-in-out;-ms-animation:load 1s infinite ease-in-out;-moz-animation:load 1s infinite ease-in-out;-o-animation:load 1s infinite ease-in-out;background:#fff;height:3em;width:1em}.loader{transform:translateZ(0);-webkit-transform:translateZ(0);-ms-transform:translateZ(0);-moz-transform:translateZ(0);-o-transform:translateZ(0);animation-delay:-.16s;color:#fff;font-size:11px;margin:3em auto;position:relative;text-indent:-9999em}.loader::before{animation-delay:-.32s;left:-1.5em}.loader::after{left:1.5em}.loader::before,.loader::after{content:'';position:absolute;top:0}.box-container{border-radius:10px;margin-bottom:2.5em;padding:1em}.box-container.alizarin{background-color:#e74c3c;filter:drop-shadow(5px 5px 0 rgba(231,76,60,0.6))}.box-container.alizarin-flat{background-color:#e74c3c}.box-container.amethyst{background-color:#9b59b6;filter:drop-shadow(5px 5px 0 rgba(155,89,182,0.6))}.box-container.amethyst-flat{background-color:#9b59b6}.box-container.asbestos{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.box-container.asbestos-flat{background-color:#7f8c8d}.box-container.belize-hole{background-color:#2980b9;filter:drop-shadow(5px 5px 0 rgba(41,128,185,0.6))}.box-container.belize-hole-flat{background-color:#2980b9}.box-container.black{background-color:#000;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.box-container.black-flat{background-color:#000}.box-container.carrot{background-color:#e67e22;filter:drop-shadow(5px 5px 0 rgba(230,126,34,0.6))}.box-container.carrot-flat{background-color:#e67e22}.box-container.clouds{background-color:#ecf0f1;filter:drop-shadow(5px 5px 0 rgba(236,240,241,0.6))}.box-container.clouds-flat{background-color:#ecf0f1}.box-container.concrete{background-color:#95a5a6;filter:drop-shadow(5px 5px 0 rgba(149,165,166,0.6))}.box-container.concrete-flat{background-color:#95a5a6}.box-container.emerald{background-color:#2ecc71;filter:drop-shadow(5px 5px 0 rgba(46,204,113,0.6))}.box-container.emerald-flat{background-color:#2ecc71}.box-container.green-sea{background-color:#16a085;filter:drop-shadow(5px 5px 0 rgba(22,160,133,0.6))}.box-container.green-sea-flat{background-color:#16a085}.box-container.highlight{background-color:#181818;filter:drop-shadow(5px 5px 0 rgba(24,24,24,0.6))}.box-container.highlight-flat{background-color:#181818}.box-container.midnight-blue{background-color:#2c3e50;filter:drop-shadow(5px 5px 0 rgba(44,62,80,0.6))}.box-container.midnight-blue-flat{background-color:#2c3e50}.box-container.nephritis{background-color:#27ae60;filter:drop-shadow(5px 5px 0 rgba(39,174,96,0.6))}.box-container.nephritis-flat{background-color:#27ae60}.box-container.orange{background-color:#f39c12;filter:drop-shadow(5px 5px 0 rgba(243,156,18,0.6))}.box-container.orange-flat{background-color:#f39c12}.box-container.peter-river{background-color:#3498db;filter:drop-shadow(5px 5px 0 rgba(52,152,219,0.6))}.box-container.peter-river-flat{background-color:#3498db}.box-container.pomegranate{background-color:#c0392b;filter:drop-shadow(5px 5px 0 rgba(192,57,43,0.6))}.box-container.pomegranate-flat{background-color:#c0392b}.box-container.primary{background-color:#282828;filter:drop-shadow(5px 5px 0 rgba(40,40,40,0.6))}.box-container.primary-flat{background-color:#282828}.box-container.pumpkin{background-color:#d35400;filter:drop-shadow(5px 5px 0 rgba(211,84,0,0.6))}.box-container.pumpkin-flat{background-color:#d35400}.box-container.secondary{background-color:#303030;filter:drop-shadow(5px 5px 0 rgba(48,48,48,0.6))}.box-container.secondary-flat{background-color:#303030}.box-container.silver{background-color:#7f8c8d;filter:drop-shadow(5px 5px 0 rgba(127,140,141,0.6))}.box-container.silver-flat{background-color:#7f8c8d}.box-container.sun-flower{background-color:#f1c40f;filter:drop-shadow(5px 5px 0 rgba(241,196,15,0.6))}.box-container.sun-flower-flat{background-color:#f1c40f}.box-container.transparent{background-color:transparent;filter:drop-shadow(5px 5px 0 rgba(0,0,0,0.6))}.box-container.transparent-flat{background-color:transparent}.box-container.turquoise{background-color:#1abc9c;filter:drop-shadow(5px 5px 0 rgba(26,188,156,0.6))}.box-container.turquoise-flat{background-color:#1abc9c}.box-container.wet-asphalt{background-color:#34495e;filter:drop-shadow(5px 5px 0 rgba(52,73,94,0.6))}.box-container.wet-asphalt-flat{background-color:#34495e}.box-container.white{background-color:#fff;filter:drop-shadow(5px 5px 0 rgba(255,255,255,0.6))}.box-container.white-flat{background-color:#fff}.box-container.wisteria{background-color:#8e44ad;filter:drop-shadow(5px 5px 0 rgba(142,68,173,0.6))}.box-container.wisteria-flat{background-color:#8e44ad}.box-container.devianart{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.box-container.devianart-flat{background-color:#f95119}.box-container.discord{background-color:#7289da;filter:drop-shadow(5px 5px 0 rgba(114,137,218,0.6))}.box-container.discord-flat{background-color:#7289da}.box-container.facebook{background-color:#475595;filter:drop-shadow(5px 5px 0 rgba(71,85,149,0.6))}.box-container.facebook-flat{background-color:#475595}.box-container.github{background-color:#292d32;filter:drop-shadow(5px 5px 0 rgba(41,45,50,0.6))}.box-container.github-flat{background-color:#292d32}.box-container.googleplus{background-color:#d75545;filter:drop-shadow(5px 5px 0 rgba(215,85,69,0.6))}.box-container.googleplus-flat{background-color:#d75545}.box-container.linkedin{background-color:#3275b3;filter:drop-shadow(5px 5px 0 rgba(50,117,179,0.6))}.box-container.linkedin-flat{background-color:#3275b3}.box-container.patreon{background-color:#f96854;filter:drop-shadow(5px 5px 0 rgba(249,104,84,0.6))}.box-container.patreon-flat{background-color:#f96854}.box-container.paypal{background-color:#1e9cd7;filter:drop-shadow(5px 5px 0 rgba(30,156,215,0.6))}.box-container.paypal-flat{background-color:#1e9cd7}.box-container.pinterest{background-color:#c5302f;filter:drop-shadow(5px 5px 0 rgba(197,48,47,0.6))}.box-container.pinterest-flat{background-color:#c5302f}.box-container.reddit{background-color:#f95119;filter:drop-shadow(5px 5px 0 rgba(249,81,25,0.6))}.box-container.reddit-flat{background-color:#f95119}.box-container.skype{background-color:#41a6ed;filter:drop-shadow(5px 5px 0 rgba(65,166,237,0.6))}.box-container.skype-flat{background-color:#41a6ed}.box-container.snapchat{background-color:#f8ff2f;filter:drop-shadow(5px 5px 0 rgba(248,255,47,0.6))}.box-container.snapchat-flat{background-color:#f8ff2f}.box-container.soundcloud{background-color:#f95f1b;filter:drop-shadow(5px 5px 0 rgba(249,95,27,0.6))}.box-container.soundcloud-flat{background-color:#f95f1b}.box-container.telegram{background-color:#4da8df;filter:drop-shadow(5px 5px 0 rgba(77,168,223,0.6))}.box-container.telegram-flat{background-color:#4da8df}.box-container.tumblr{background-color:#394e6b;filter:drop-shadow(5px 5px 0 rgba(57,78,107,0.6))}.box-container.tumblr-flat{background-color:#394e6b}.box-container.twitter{background-color:#4999f0;filter:drop-shadow(5px 5px 0 rgba(73,153,240,0.6))}.box-container.twitter-flat{background-color:#4999f0}.box-container.viber{background-color:#7f4d9b;filter:drop-shadow(5px 5px 0 rgba(127,77,155,0.6))}.box-container.viber-flat{background-color:#7f4d9b}.box-container.youtube{background-color:red;filter:drop-shadow(5px 5px 0 rgba(255,0,0,0.6))}.box-container.youtube-flat{background-color:red}.card-container{align-items:center;display:flex;flex-direction:row;flex-flow:row wrap;justify-content:space-around}.card-container .card{background-color:#303030;border-radius:6px;box-shadow:0 2px 10px 0 rgba(0,0,0,0.2),0 0 0 1px rgba(32,34,37,0.6);display:block;font-family:arial;margin:0 .05em;margin-top:2em;padding-bottom:.75em;text-align:center;width:320px}.card-container .card .avatar{background-color:#282828;border-radius:6px 6px 0 0;padding:.75em 0}.card-container .card .avatar img{border-radius:100%;margin:.5em 0;width:7em}.card-container .card .title{display:block;font-size:26px;margin-bottom:0;margin-top:.3em}.card-container .card .description{color:gray;font-size:18px;margin:0}.card-container .card .description.link{color:#2980b9}.card-container .card .footer{background-color:#303030;border-radius:0 0 6px 6px;display:block;padding-bottom:-.15em;padding-top:.6em}.card-container .card .footer *{margin:0 .75em;margin-bottom:0;text-align:left}.card-container .card .footer * *{margin:0}.card-container .card .footer a{color:#2980b9;font-size:14px;text-decoration:none}.workcard-container{align-items:center;display:flex;flex-direction:row;flex-flow:row wrap;justify-content:space-around}.workcard-container .card{cursor:default;height:200px;margin:14px 10px;position:relative;width:330px}.workcard-container .card img{height:200px;width:330px}.workcard-container .card a.overlay{cursor:pointer}.workcard-container .card .overlay{transition:opacity .25s ease-in-out;-webkit-transition:opacity .25s ease-in-out;-ms-transition:opacity .25s ease-in-out;-moz-transition:opacity .25s ease-in-out;-o-transition:opacity .25s ease-in-out;background:rgba(0,0,0,0.8);height:200px;left:0;opacity:0;position:absolute;top:0;width:330px}.workcard-container .card .overlay:hover{opacity:1}.workcard-container .card .overlay .text{transform:translate(-50%,-50%);-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);color:#fff;left:50%;line-height:1;margin:0;opacity:.75;position:absolute;text-align:center;top:50%;width:330px}.table-container{display:inline-block;margin-bottom:.75em;text-align:left}.table-container table{border:1px solid rgba(0,0,0,0.12);border-collapse:collapse;font-size:13px;margin:0;margin-bottom:2.5rem;max-width:800px;padding:0;position:relative;white-space:nowrap;width:auto}.table-container table tr{border:1px solid #ddd;padding:5px}.table-container table th,.table-container table td{padding:12px 20px;padding-bottom:8px}.table-container table th{box-sizing:border-box;font-size:12px;font-weight:700;height:48px;letter-spacing:0;line-height:24px;padding:12px 20px;padding-bottom:8px;position:relative;text-align:left;text-overflow:ellipsis;vertical-align:bottom}.table-container.table-center{display:block;text-align:center}.table-container.table-center table{margin:0 auto}.form-container form{margin-bottom:0}.parallax-container{position:relative}.parallax-container .parallax{background-attachment:fixed;background-position:center;background-repeat:no-repeat;background-size:cover;filter:blur(4px);height:20em;width:100%}.parallax-container .parallax.small{height:15em}.parallax-container .parallax.medium{height:26em}.parallax-container .parallax.large{height:33em}.parallax-container .content{left:50%;position:absolute;text-align:center;top:50%;transform:translate(-50%,-50%);width:100%}@media(max-width:750px){.markdown-container{padding:2em 3em}.fullscreen .center-object,.center-object{top:45%;width:90%}.me{padding:0}.me .title{font-size:10vmin;margin:0}.me .image-title{height:7em;width:7em}.me.unset{flex-direction:column}.buttons{align-items:center;display:flex;flex-flow:row wrap;justify-content:center}.buttons .btn{align-items:center;display:flex;font-size:18px;justify-content:center;line-height:20px;padding:25px 0;text-align:center;white-space:pre-line;width:calc(45% - 30px)}.buttons .btn i{margin-right:10px}.buttons.one-line .btn{padding:14px 8px;width:75%}}@media screen and (max-width:600px){.table-container table{border:0;filter:none;width:100%}.table-container table thead{display:none}.table-container table tr{border-bottom:3px solid #aaa;display:block;margin-bottom:10px}.table-container table td{border-bottom:1px dotted #ccc;display:block;font-size:13px}.table-container table td:first-child{font-weight:bold}.table-container table td:last-child{border-bottom:0}.table-container table td::before{content:attr(data-label);float:left;font-weight:bold;text-transform:uppercase}}@media screen and (max-width:650px){.nav-container.default{display:flex}.nav-container.default .nav-title{margin-left:0;padding:0;text-align:center}.nav-container.default .sidenav{margin-right:0;padding:0;text-align:center}}@media(max-width:550px){.workcard-container .card,.workcard-container .card img,.workcard-container .card .overlay,.workcard-container .card .overlay .text{height:auto;width:100%}.container{margin:0;margin-bottom:2.5em;margin-top:1.5em;padding:0;width:100%}.nav-container.no-nav{display:block;height:3em;position:static}.row .column{margin-left:0}.row .one-half.column,.row .one-third.column,.row .one-fourth.column{width:100%}.flex-grid .col-xs,.flex-grid .col-xs-1,.flex-grid .col-xs-2,.flex-grid .col-xs-3,.flex-grid .col-xs-4,.flex-grid .col-xs-5,.flex-grid .col-xs-6,.flex-grid .col-xs-7,.flex-grid .col-xs-8,.flex-grid .col-xs-9,.flex-grid .col-xs-10,.flex-grid .col-xs-11,.flex-grid .col-xs-12{flex-basis:100%;max-width:100%;padding:0;width:100%}} \ No newline at end of file diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 0000000..afd9c59 --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,15 @@ +#navbar img { + display: none; + float: left; + width: 50px; + height: 50px; + margin-right: 10px; +} + +#navbar.default img { + display: block; +} + +#navbar h3, #navbar div { + margin-top: 2px; +} diff --git a/docs/css/twemoji.min.css b/docs/css/twemoji.min.css new file mode 100644 index 0000000..65dd539 --- /dev/null +++ b/docs/css/twemoji.min.css @@ -0,0 +1 @@ +@charset "UTF-8";.twa-mahjong-red-dragon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f004.svg")}.twa-mahjong{background-image:url("https://twemoji.maxcdn.com/2/svg/1f004.svg")}.twa-joker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f0cf.svg")}.twa-black-joker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f0cf.svg")}.twa-a-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f170.svg")}.twa-a{background-image:url("https://twemoji.maxcdn.com/2/svg/1f170.svg")}.twa-b-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f171.svg")}.twa-b{background-image:url("https://twemoji.maxcdn.com/2/svg/1f171.svg")}.twa-o-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f17e.svg")}.twa-o2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f17e.svg")}.twa-p-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f17f.svg")}.twa-parking{background-image:url("https://twemoji.maxcdn.com/2/svg/1f17f.svg")}.twa-ab-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f18e.svg")}.twa-ab{background-image:url("https://twemoji.maxcdn.com/2/svg/1f18e.svg")}.twa-squared-cl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f191.svg")}.twa-cl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f191.svg")}.twa-squared-cool{background-image:url("https://twemoji.maxcdn.com/2/svg/1f192.svg")}.twa-cool{background-image:url("https://twemoji.maxcdn.com/2/svg/1f192.svg")}.twa-squared-free{background-image:url("https://twemoji.maxcdn.com/2/svg/1f193.svg")}.twa-free{background-image:url("https://twemoji.maxcdn.com/2/svg/1f193.svg")}.twa-squared-id{background-image:url("https://twemoji.maxcdn.com/2/svg/1f194.svg")}.twa-id{background-image:url("https://twemoji.maxcdn.com/2/svg/1f194.svg")}.twa-squared-new{background-image:url("https://twemoji.maxcdn.com/2/svg/1f195.svg")}.twa-new{background-image:url("https://twemoji.maxcdn.com/2/svg/1f195.svg")}.twa-squared-ng{background-image:url("https://twemoji.maxcdn.com/2/svg/1f196.svg")}.twa-ng{background-image:url("https://twemoji.maxcdn.com/2/svg/1f196.svg")}.twa-squared-ok{background-image:url("https://twemoji.maxcdn.com/2/svg/1f197.svg")}.twa-ok{background-image:url("https://twemoji.maxcdn.com/2/svg/1f197.svg")}.twa-squared-sos{background-image:url("https://twemoji.maxcdn.com/2/svg/1f198.svg")}.twa-sos{background-image:url("https://twemoji.maxcdn.com/2/svg/1f198.svg")}.twa-upexc-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f199.svg")}.twa-up{background-image:url("https://twemoji.maxcdn.com/2/svg/1f199.svg")}.twa-squared-vs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f19a.svg")}.twa-vs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f19a.svg")}.twa-ascension-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1e8.svg")}.twa-andorra-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1e9.svg")}.twa-emirates-uae-united-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ea.svg")}.twa-afghanistan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1eb.svg")}.twa-antigua-barbuda-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ec.svg")}.twa-anguilla-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ee.svg")}.twa-albania-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f1.svg")}.twa-armenia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f2.svg")}.twa-angola-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f4.svg")}.twa-antarctica-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f6.svg")}.twa-argentina-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f7.svg")}.twa-american-samoa-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f8.svg")}.twa-austria-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f9.svg")}.twa-australia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fa.svg")}.twa-aruba-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fc.svg")}.twa-aland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fd.svg")}.twa-azerbaijan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ff.svg")}.twa-aa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6.svg")}.twa-squared-a{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6.svg")}.twa-bosnia-herzegovina-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e6.svg")}.twa-barbados-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e7.svg")}.twa-bangladesh-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e9.svg")}.twa-belgium-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ea.svg")}.twa-burkina-faso-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1eb.svg")}.twa-bulgaria-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ec.svg")}.twa-bahrain-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ed.svg")}.twa-burundi-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ee.svg")}.twa-benin-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ef.svg")}.twa-barthelemy-barthelemy-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f1.svg")}.twa-bermuda-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f2.svg")}.twa-brunei-darussalam-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f3.svg")}.twa-bolivia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f4.svg")}.twa-bonaire-caribbean-eustatius-netherlands-saba-sint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f6.svg")}.twa-brazil-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f7.svg")}.twa-bahamas-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f8.svg")}.twa-bhutan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f9.svg")}.twa-bouvet-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fb.svg")}.twa-botswana-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fc.svg")}.twa-belarus-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fe.svg")}.twa-belize-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ff.svg")}.twa-bb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7.svg")}.twa-squared-b{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7.svg")}.twa-canada-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e6.svg")}.twa-cocos-island-keeling-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e8.svg")}.twa-congo-congo-kinshasa-democratic-republic-of-congo-drc-kinshasa-republic-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e9.svg")}.twa-central-african-republic-republic-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1eb.svg")}.twa-brazzaville-congo-congo-republic-congo-brazzaville-republic-republic-of-the-congo-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ec.svg")}.twa-switzerland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ed.svg")}.twa-cote-ivoire-cote-ivoire-ivory-coast-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ee.svg")}.twa-cook-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f0.svg")}.twa-chile-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f1.svg")}.twa-cameroon-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f2.svg")}.twa-china-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f3.svg")}.twa-cn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f3.svg")}.twa-colombia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f4.svg")}.twa-clipperton-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f5.svg")}.twa-costa-rica-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f7.svg")}.twa-cuba-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fa.svg")}.twa-cabo-cape-verde-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fb.svg")}.twa-antilles-curacao-curaçao-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fc.svg")}.twa-christmas-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fd.svg")}.twa-cyprus-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fe.svg")}.twa-czech-republic-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ff.svg")}.twa-cc{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8.svg")}.twa-squared-c{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8.svg")}.twa-germany-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ea.svg")}.twa-de{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ea.svg")}.twa-diego-garcia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ec.svg")}.twa-djibouti-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ef.svg")}.twa-denmark-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f0.svg")}.twa-dominica-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f2.svg")}.twa-dominican-republic-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f4.svg")}.twa-algeria-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ff.svg")}.twa-dd{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9.svg")}.twa-squared-d{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9.svg")}.twa-ceuta-melilla-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1e6.svg")}.twa-ecuador-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1e8.svg")}.twa-estonia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ea.svg")}.twa-egypt-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ec.svg")}.twa-sahara-west-western-sahara-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ed.svg")}.twa-eritrea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f7.svg")}.twa-spain-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f8.svg")}.twa-es{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f8.svg")}.twa-ethiopia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f9.svg")}.twa-european-union-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1fa.svg")}.twa-ee{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea.svg")}.twa-squared-e{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea.svg")}.twa-finland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1ee.svg")}.twa-fiji-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1ef.svg")}.twa-falkland-falklands-island-islas-malvinas-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f0.svg")}.twa-micronesia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f2.svg")}.twa-faroe-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f4.svg")}.twa-france-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f7.svg")}.twa-fr{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f7.svg")}.twa-ff{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb.svg")}.twa-squared-f{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb.svg")}.twa-gabon-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e6.svg")}.twa-britain-british-cornwall-england-great-britain-ireland-northern-ireland-scotland-uk-union-jack-united-united-kingdom-wales-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e7.svg")}.twa-gb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e7.svg")}.twa-grenada-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e9.svg")}.twa-georgia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ea.svg")}.twa-french-guiana-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1eb.svg")}.twa-guernsey-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ec.svg")}.twa-ghana-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ed.svg")}.twa-gibraltar-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ee.svg")}.twa-greenland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f1.svg")}.twa-gambia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f2.svg")}.twa-guinea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f3.svg")}.twa-guadeloupe-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f5.svg")}.twa-equatorial-guinea-guinea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f6.svg")}.twa-greece-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f7.svg")}.twa-georgia-island-south-south-georgia-south-sandwich-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f8.svg")}.twa-guatemala-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f9.svg")}.twa-guam-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fa.svg")}.twa-bissau-guinea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fc.svg")}.twa-guyana-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fe.svg")}.twa-gg{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec.svg")}.twa-squared-g{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec.svg")}.twa-china-hong-kong-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f0.svg")}.twa-heard-island-mcdonald-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f2.svg")}.twa-honduras-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f3.svg")}.twa-croatia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f7.svg")}.twa-haiti-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f9.svg")}.twa-hungary-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1fa.svg")}.twa-hh{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed.svg")}.twa-squared-h{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed.svg")}.twa-canary-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1e8.svg")}.twa-indonesia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1e9.svg")}.twa-ireland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1ea.svg")}.twa-israel-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f1.svg")}.twa-isle-of-man-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f2.svg")}.twa-india-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f3.svg")}.twa-british-chagos-indian-ocean-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f4.svg")}.twa-iraq-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f6.svg")}.twa-iran-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f7.svg")}.twa-iceland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f8.svg")}.twa-italy-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f9.svg")}.twa-it{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f9.svg")}.twa-ii{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee.svg")}.twa-squared-i{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee.svg")}.twa-jersey-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1ea.svg")}.twa-jamaica-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f2.svg")}.twa-jordan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f4.svg")}.twa-japan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f5.svg")}.twa-jp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f5.svg")}.twa-jj{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef.svg")}.twa-squared-j{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef.svg")}.twa-kenya-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ea.svg")}.twa-kyrgyzstan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ec.svg")}.twa-cambodia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ed.svg")}.twa-kiribati-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ee.svg")}.twa-comoros-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f2.svg")}.twa-kitts-nevis-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f3.svg")}.twa-korea-north-north-korea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f5.svg")}.twa-korea-south-south-korea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f7.svg")}.twa-kr{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f7.svg")}.twa-kuwait-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1fc.svg")}.twa-cayman-island-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1fe.svg")}.twa-kazakhstan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ff.svg")}.twa-kk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0.svg")}.twa-squared-k{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0.svg")}.twa-laos-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e6.svg")}.twa-lebanon-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e7.svg")}.twa-lucia-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e8.svg")}.twa-liechtenstein-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1ee.svg")}.twa-sri-lanka-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f0.svg")}.twa-liberia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f7.svg")}.twa-lesotho-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f8.svg")}.twa-lithuania-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f9.svg")}.twa-luxembourg-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fa.svg")}.twa-latvia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fb.svg")}.twa-libya-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fe.svg")}.twa-ll{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1.svg")}.twa-squared-l{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1.svg")}.twa-morocco-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e6.svg")}.twa-monaco-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e8.svg")}.twa-moldova-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e9.svg")}.twa-montenegro-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ea.svg")}.twa-french-martin-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1eb.svg")}.twa-madagascar-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ec.svg")}.twa-island-marshall-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ed.svg")}.twa-macedonia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f0.svg")}.twa-mali-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f1.svg")}.twa-burma-myanmar-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f2.svg")}.twa-mongolia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f3.svg")}.twa-china-macao-macau-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f4.svg")}.twa-island-mariana-north-northern-mariana-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f5.svg")}.twa-martinique-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f6.svg")}.twa-mauritania-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f7.svg")}.twa-montserrat-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f8.svg")}.twa-malta-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f9.svg")}.twa-mauritius-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fa.svg")}.twa-maldives-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fb.svg")}.twa-malawi-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fc.svg")}.twa-mexico-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fd.svg")}.twa-malaysia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fe.svg")}.twa-mozambique-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ff.svg")}.twa-mm{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2.svg")}.twa-squared-m{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2.svg")}.twa-namibia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1e6.svg")}.twa-new-new-caledonia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1e8.svg")}.twa-niger-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ea.svg")}.twa-island-norfolk-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1eb.svg")}.twa-nigeria-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ec.svg")}.twa-nicaragua-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ee.svg")}.twa-netherlands-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f1.svg")}.twa-norway-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f4.svg")}.twa-nepal-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f5.svg")}.twa-nauru-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f7.svg")}.twa-niue-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1fa.svg")}.twa-new-new-zealand-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ff.svg")}.twa-nn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3.svg")}.twa-squared-n{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3.svg")}.twa-oman-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f4-1f1f2.svg")}.twa-oo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f4.svg")}.twa-squared-o{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f4.svg")}.twa-panama-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1e6.svg")}.twa-peru-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ea.svg")}.twa-french-polynesia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1eb.svg")}.twa-guinea-new-papua-new-guinea-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ec.svg")}.twa-philippines-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ed.svg")}.twa-pakistan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f0.svg")}.twa-poland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f1.svg")}.twa-miquelon-pierre-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f2.svg")}.twa-island-pitcairn-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f3.svg")}.twa-puerto-rico-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f7.svg")}.twa-palestine-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f8.svg")}.twa-portugal-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f9.svg")}.twa-palau-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1fc.svg")}.twa-paraguay-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1fe.svg")}.twa-pp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5.svg")}.twa-squared-p{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5.svg")}.twa-qatar-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f6-1f1e6.svg")}.twa-qq{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f6.svg")}.twa-squared-q{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f6.svg")}.twa-reunion-reunion-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1ea.svg")}.twa-romania-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1f4.svg")}.twa-serbia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1f8.svg")}.twa-russia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1fa.svg")}.twa-ru{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1fa.svg")}.twa-rwanda-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1fc.svg")}.twa-rr{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7.svg")}.twa-squared-r{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7.svg")}.twa-saudi-arabia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e6.svg")}.twa-island-solomon-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e7.svg")}.twa-seychelles-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e8.svg")}.twa-sudan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e9.svg")}.twa-sweden-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ea.svg")}.twa-singapore-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ec.svg")}.twa-helena-saint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ed.svg")}.twa-slovenia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ee.svg")}.twa-jan-mayen-svalbard-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ef.svg")}.twa-slovakia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f0.svg")}.twa-sierra-leone-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f1.svg")}.twa-san-marino-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f2.svg")}.twa-senegal-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f3.svg")}.twa-somalia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f4.svg")}.twa-suriname-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f7.svg")}.twa-south-south-sudan-sudan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f8.svg")}.twa-principe-principe-sao-tome-sao-tome-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f9.svg")}.twa-el-salvador-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fb.svg")}.twa-maarten-sint-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fd.svg")}.twa-syria-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fe.svg")}.twa-swaziland-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ff.svg")}.twa-ss{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8.svg")}.twa-squared-s{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8.svg")}.twa-tristan-da-cunha-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e6.svg")}.twa-caicos-island-turks-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e8.svg")}.twa-chad-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e9.svg")}.twa-antarctic-french-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1eb.svg")}.twa-togo-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ec.svg")}.twa-thailand-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ed.svg")}.twa-tajikistan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ef.svg")}.twa-tokelau-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f0.svg")}.twa-east-east-timor-timor-leste-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f1.svg")}.twa-turkmenistan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f2.svg")}.twa-tunisia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f3.svg")}.twa-tonga-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f4.svg")}.twa-turkey-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f7.svg")}.twa-tobago-trinidad-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f9.svg")}.twa-tuvalu-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1fb.svg")}.twa-china-taiwan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1fc.svg")}.twa-tanzania-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ff.svg")}.twa-tt{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9.svg")}.twa-squared-t{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9.svg")}.twa-ukraine-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1e6.svg")}.twa-uganda-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1ec.svg")}.twa-america-island-minor-outlying-united-united-states-us-usa-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f2.svg")}.twa-america-stars-and-stripes-united-united-states-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f8.svg")}.twa-us{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f8.svg")}.twa-uruguay-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1fe.svg")}.twa-uzbekistan-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1ff.svg")}.twa-uu{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa.svg")}.twa-squared-u{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa.svg")}.twa-vatican-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1e6.svg")}.twa-grenadines-saint-vincent-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1e8.svg")}.twa-venezuela-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ea.svg")}.twa-british-island-virgin-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ec.svg")}.twa-america-american-island-united-united-states-us-usa-virgin-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ee.svg")}.twa-viet-nam-vietnam-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1f3.svg")}.twa-vanuatu-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1fa.svg")}.twa-vv{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb.svg")}.twa-squared-v{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb.svg")}.twa-futuna-wallis-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc-1f1eb.svg")}.twa-samoa-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc-1f1f8.svg")}.twa-ww{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc.svg")}.twa-squared-w{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc.svg")}.twa-kosovo-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fd-1f1f0.svg")}.twa-xx{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fd.svg")}.twa-squared-x{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fd.svg")}.twa-yemen-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe-1f1ea.svg")}.twa-mayotte-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe-1f1f9.svg")}.twa-yy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe.svg")}.twa-squared-y{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe.svg")}.twa-south-south-africa-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1e6.svg")}.twa-zambia-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1f2.svg")}.twa-zimbabwe-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1fc.svg")}.twa-zz{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff.svg")}.twa-squared-z{background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff.svg")}.twa-squared-katakana-koko{background-image:url("https://twemoji.maxcdn.com/2/svg/1f201.svg")}.twa-koko{background-image:url("https://twemoji.maxcdn.com/2/svg/1f201.svg")}.twa-squared-katakana-sa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f202.svg")}.twa-sa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f202.svg")}.twa-squared-negation-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f21a.svg")}.twa-u7121{background-image:url("https://twemoji.maxcdn.com/2/svg/1f21a.svg")}.twa-squared-finger-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f22f.svg")}.twa-u6307{background-image:url("https://twemoji.maxcdn.com/2/svg/1f22f.svg")}.twa-squared-prohibit-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f232.svg")}.twa-u7981{background-image:url("https://twemoji.maxcdn.com/2/svg/1f232.svg")}.twa-squared-empty-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f233.svg")}.twa-u7a7a{background-image:url("https://twemoji.maxcdn.com/2/svg/1f233.svg")}.twa-squared-together-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f234.svg")}.twa-u5408{background-image:url("https://twemoji.maxcdn.com/2/svg/1f234.svg")}.twa-squared-fullness-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f235.svg")}.twa-u6e80{background-image:url("https://twemoji.maxcdn.com/2/svg/1f235.svg")}.twa-squared-exist-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f236.svg")}.twa-u6709{background-image:url("https://twemoji.maxcdn.com/2/svg/1f236.svg")}.twa-squared-moon-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f237.svg")}.twa-u6708{background-image:url("https://twemoji.maxcdn.com/2/svg/1f237.svg")}.twa-squared-apply-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f238.svg")}.twa-u7533{background-image:url("https://twemoji.maxcdn.com/2/svg/1f238.svg")}.twa-squared-divide-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f239.svg")}.twa-u5272{background-image:url("https://twemoji.maxcdn.com/2/svg/1f239.svg")}.twa-squared-operating-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f23a.svg")}.twa-u55b6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f23a.svg")}.twa-circled-advantage-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f250.svg")}.twa-ideograph-advantage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f250.svg")}.twa-circled-accept-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f251.svg")}.twa-accept{background-image:url("https://twemoji.maxcdn.com/2/svg/1f251.svg")}.twa-cyclone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f300.svg")}.twa-foggy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f301.svg")}.twa-closed-umbrella{background-image:url("https://twemoji.maxcdn.com/2/svg/1f302.svg")}.twa-night-with-stars{background-image:url("https://twemoji.maxcdn.com/2/svg/1f303.svg")}.twa-stars{background-image:url("https://twemoji.maxcdn.com/2/svg/1f303.svg")}.twa-sunrise-over-mountains{background-image:url("https://twemoji.maxcdn.com/2/svg/1f304.svg")}.twa-sunrise{background-image:url("https://twemoji.maxcdn.com/2/svg/1f305.svg")}.twa-cityscape-at-dusk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f306.svg")}.twa-city-sunset{background-image:url("https://twemoji.maxcdn.com/2/svg/1f306.svg")}.twa-sunset{background-image:url("https://twemoji.maxcdn.com/2/svg/1f307.svg")}.twa-city-sunrise{background-image:url("https://twemoji.maxcdn.com/2/svg/1f307.svg")}.twa-rainbow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f308.svg")}.twa-bridge-at-night{background-image:url("https://twemoji.maxcdn.com/2/svg/1f309.svg")}.twa-water-wave{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30a.svg")}.twa-ocean{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30a.svg")}.twa-volcano{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30b.svg")}.twa-milky-way{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30c.svg")}.twa-globe-showing-europe-africa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30d.svg")}.twa-earth-africa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30d.svg")}.twa-globe-showing-americas{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30e.svg")}.twa-earth-americas{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30e.svg")}.twa-globe-showing-asia-australia{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30f.svg")}.twa-earth-asia{background-image:url("https://twemoji.maxcdn.com/2/svg/1f30f.svg")}.twa-globe-with-meridians{background-image:url("https://twemoji.maxcdn.com/2/svg/1f310.svg")}.twa-new-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f311.svg")}.twa-waxing-crescent-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f312.svg")}.twa-first-quarter-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f313.svg")}.twa-waxing-gibbous-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f314.svg")}.twa-full-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f315.svg")}.twa-waning-gibbous-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f316.svg")}.twa-last-quarter-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f317.svg")}.twa-waning-crescent-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f318.svg")}.twa-crescent-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f319.svg")}.twa-moon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f319.svg")}.twa-new-moon-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31a.svg")}.twa-new-moon-with-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31a.svg")}.twa-first-quarter-moon-with-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31b.svg")}.twa-last-quarter-moon-with-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31c.svg")}.twa-full-moon-with-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31d.svg")}.twa-sun-with-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31e.svg")}.twa-glowing-star{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31f.svg")}.twa-star2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f31f.svg")}.twa-shooting-star{background-image:url("https://twemoji.maxcdn.com/2/svg/1f320.svg")}.twa-thermometer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f321.svg")}.twa-sun-behind-small-cloud{background-image:url("https://twemoji.maxcdn.com/2/svg/1f324.svg")}.twa-sun-behind-large-cloud{background-image:url("https://twemoji.maxcdn.com/2/svg/1f325.svg")}.twa-sun-behind-cloud-with-rain{background-image:url("https://twemoji.maxcdn.com/2/svg/1f326.svg")}.twa-cloud-with-rain{background-image:url("https://twemoji.maxcdn.com/2/svg/1f327.svg")}.twa-cloud-with-snow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f328.svg")}.twa-cloud-with-lightning{background-image:url("https://twemoji.maxcdn.com/2/svg/1f329.svg")}.twa-tornado{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32a.svg")}.twa-fog{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32b.svg")}.twa-wind-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32c.svg")}.twa-hot-dog{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32d.svg")}.twa-taco{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32e.svg")}.twa-burrito{background-image:url("https://twemoji.maxcdn.com/2/svg/1f32f.svg")}.twa-chestnut{background-image:url("https://twemoji.maxcdn.com/2/svg/1f330.svg")}.twa-seedling{background-image:url("https://twemoji.maxcdn.com/2/svg/1f331.svg")}.twa-evergreen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f332.svg")}.twa-evergreen-tree{background-image:url("https://twemoji.maxcdn.com/2/svg/1f332.svg")}.twa-deciduous-tree{background-image:url("https://twemoji.maxcdn.com/2/svg/1f333.svg")}.twa-palm-tree{background-image:url("https://twemoji.maxcdn.com/2/svg/1f334.svg")}.twa-cactus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f335.svg")}.twa-hot-pepper{background-image:url("https://twemoji.maxcdn.com/2/svg/1f336.svg")}.twa-tulip{background-image:url("https://twemoji.maxcdn.com/2/svg/1f337.svg")}.twa-cherry-blossom{background-image:url("https://twemoji.maxcdn.com/2/svg/1f338.svg")}.twa-rose{background-image:url("https://twemoji.maxcdn.com/2/svg/1f339.svg")}.twa-hibiscus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33a.svg")}.twa-sunflower{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33b.svg")}.twa-blossom{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33c.svg")}.twa-ear-of-corn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33d.svg")}.twa-corn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33d.svg")}.twa-sheaf-of-rice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33e.svg")}.twa-ear-of-rice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33e.svg")}.twa-herb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f33f.svg")}.twa-four-leaf-clover{background-image:url("https://twemoji.maxcdn.com/2/svg/1f340.svg")}.twa-maple-leaf{background-image:url("https://twemoji.maxcdn.com/2/svg/1f341.svg")}.twa-fallen-leaf{background-image:url("https://twemoji.maxcdn.com/2/svg/1f342.svg")}.twa-leaf-fluttering-in-wind{background-image:url("https://twemoji.maxcdn.com/2/svg/1f343.svg")}.twa-leaves{background-image:url("https://twemoji.maxcdn.com/2/svg/1f343.svg")}.twa-mushroom{background-image:url("https://twemoji.maxcdn.com/2/svg/1f344.svg")}.twa-tomato{background-image:url("https://twemoji.maxcdn.com/2/svg/1f345.svg")}.twa-eggplant{background-image:url("https://twemoji.maxcdn.com/2/svg/1f346.svg")}.twa-grapes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f347.svg")}.twa-melon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f348.svg")}.twa-watermelon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f349.svg")}.twa-tangerine{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34a.svg")}.twa-lemon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34b.svg")}.twa-banana{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34c.svg")}.twa-pineapple{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34d.svg")}.twa-red-apple{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34e.svg")}.twa-apple{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34e.svg")}.twa-green-apple{background-image:url("https://twemoji.maxcdn.com/2/svg/1f34f.svg")}.twa-pear{background-image:url("https://twemoji.maxcdn.com/2/svg/1f350.svg")}.twa-peach{background-image:url("https://twemoji.maxcdn.com/2/svg/1f351.svg")}.twa-cherries{background-image:url("https://twemoji.maxcdn.com/2/svg/1f352.svg")}.twa-strawberry{background-image:url("https://twemoji.maxcdn.com/2/svg/1f353.svg")}.twa-hamburger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f354.svg")}.twa-pizza{background-image:url("https://twemoji.maxcdn.com/2/svg/1f355.svg")}.twa-meat-on-bone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f356.svg")}.twa-poultry-leg{background-image:url("https://twemoji.maxcdn.com/2/svg/1f357.svg")}.twa-rice-cracker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f358.svg")}.twa-rice-ball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f359.svg")}.twa-cooked-rice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35a.svg")}.twa-rice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35a.svg")}.twa-curry-rice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35b.svg")}.twa-curry{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35b.svg")}.twa-steaming-bowl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35c.svg")}.twa-ramen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35c.svg")}.twa-spaghetti{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35d.svg")}.twa-bread{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35e.svg")}.twa-french-fries{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35f.svg")}.twa-fries{background-image:url("https://twemoji.maxcdn.com/2/svg/1f35f.svg")}.twa-roasted-sweet-potato{background-image:url("https://twemoji.maxcdn.com/2/svg/1f360.svg")}.twa-sweet-potato{background-image:url("https://twemoji.maxcdn.com/2/svg/1f360.svg")}.twa-dango{background-image:url("https://twemoji.maxcdn.com/2/svg/1f361.svg")}.twa-oden{background-image:url("https://twemoji.maxcdn.com/2/svg/1f362.svg")}.twa-sushi{background-image:url("https://twemoji.maxcdn.com/2/svg/1f363.svg")}.twa-fried-shrimp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f364.svg")}.twa-fish-cake-with-swirl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f365.svg")}.twa-fish-cake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f365.svg")}.twa-soft-ice-cream{background-image:url("https://twemoji.maxcdn.com/2/svg/1f366.svg")}.twa-icecream{background-image:url("https://twemoji.maxcdn.com/2/svg/1f366.svg")}.twa-shaved-ice{background-image:url("https://twemoji.maxcdn.com/2/svg/1f367.svg")}.twa-ice-cream{background-image:url("https://twemoji.maxcdn.com/2/svg/1f368.svg")}.twa-doughnut{background-image:url("https://twemoji.maxcdn.com/2/svg/1f369.svg")}.twa-cookie{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36a.svg")}.twa-chocolate-bar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36b.svg")}.twa-candy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36c.svg")}.twa-lollipop{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36d.svg")}.twa-custard{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36e.svg")}.twa-honey-pot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f36f.svg")}.twa-shortcake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f370.svg")}.twa-cake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f370.svg")}.twa-bento-box{background-image:url("https://twemoji.maxcdn.com/2/svg/1f371.svg")}.twa-bento{background-image:url("https://twemoji.maxcdn.com/2/svg/1f371.svg")}.twa-pot-of-food{background-image:url("https://twemoji.maxcdn.com/2/svg/1f372.svg")}.twa-stew{background-image:url("https://twemoji.maxcdn.com/2/svg/1f372.svg")}.twa-cooking{background-image:url("https://twemoji.maxcdn.com/2/svg/1f373.svg")}.twa-fork-and-knife{background-image:url("https://twemoji.maxcdn.com/2/svg/1f374.svg")}.twa-teacup-without-handle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f375.svg")}.twa-tea{background-image:url("https://twemoji.maxcdn.com/2/svg/1f375.svg")}.twa-sake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f376.svg")}.twa-wine-glass{background-image:url("https://twemoji.maxcdn.com/2/svg/1f377.svg")}.twa-cocktail-glass{background-image:url("https://twemoji.maxcdn.com/2/svg/1f378.svg")}.twa-cocktail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f378.svg")}.twa-tropical-drink{background-image:url("https://twemoji.maxcdn.com/2/svg/1f379.svg")}.twa-beer-mug{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37a.svg")}.twa-beer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37a.svg")}.twa-clinking-beer-mugs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37b.svg")}.twa-beers{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37b.svg")}.twa-baby-bottle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37c.svg")}.twa-fork-and-knife-with-plate{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37d.svg")}.twa-bottle-with-popping-cork{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37e.svg")}.twa-popcorn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f37f.svg")}.twa-ribbon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f380.svg")}.twa-wrapped-present{background-image:url("https://twemoji.maxcdn.com/2/svg/1f381.svg")}.twa-gift{background-image:url("https://twemoji.maxcdn.com/2/svg/1f381.svg")}.twa-birthday-cake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f382.svg")}.twa-birthday{background-image:url("https://twemoji.maxcdn.com/2/svg/1f382.svg")}.twa-jack-o-lantern{background-image:url("https://twemoji.maxcdn.com/2/svg/1f383.svg")}.twa-christmas-tree{background-image:url("https://twemoji.maxcdn.com/2/svg/1f384.svg")}.twa-father-christmas-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385-1f3fb.svg")}.twa-father-christmas-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385-1f3fc.svg")}.twa-father-christmas-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385-1f3fd.svg")}.twa-father-christmas-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385-1f3fe.svg")}.twa-father-christmas-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385-1f3ff.svg")}.twa-santa-claus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385.svg")}.twa-santa{background-image:url("https://twemoji.maxcdn.com/2/svg/1f385.svg")}.twa-fireworks{background-image:url("https://twemoji.maxcdn.com/2/svg/1f386.svg")}.twa-sparkler{background-image:url("https://twemoji.maxcdn.com/2/svg/1f387.svg")}.twa-balloon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f388.svg")}.twa-party-popper{background-image:url("https://twemoji.maxcdn.com/2/svg/1f389.svg")}.twa-tada{background-image:url("https://twemoji.maxcdn.com/2/svg/1f389.svg")}.twa-confetti-ball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38a.svg")}.twa-tanabata-tree{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38b.svg")}.twa-crossed-flags{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38c.svg")}.twa-pine-decoration{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38d.svg")}.twa-bamboo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38d.svg")}.twa-japanese-dolls{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38e.svg")}.twa-dolls{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38e.svg")}.twa-carp-streamer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38f.svg")}.twa-flags{background-image:url("https://twemoji.maxcdn.com/2/svg/1f38f.svg")}.twa-wind-chime{background-image:url("https://twemoji.maxcdn.com/2/svg/1f390.svg")}.twa-moon-ceremony{background-image:url("https://twemoji.maxcdn.com/2/svg/1f391.svg")}.twa-rice-scene{background-image:url("https://twemoji.maxcdn.com/2/svg/1f391.svg")}.twa-school-backpack{background-image:url("https://twemoji.maxcdn.com/2/svg/1f392.svg")}.twa-school-satchel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f392.svg")}.twa-graduation-cap{background-image:url("https://twemoji.maxcdn.com/2/svg/1f393.svg")}.twa-mortar-board{background-image:url("https://twemoji.maxcdn.com/2/svg/1f393.svg")}.twa-military-medal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f396.svg")}.twa-reminder-ribbon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f397.svg")}.twa-studio-microphone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f399.svg")}.twa-level-slider{background-image:url("https://twemoji.maxcdn.com/2/svg/1f39a.svg")}.twa-control-knobs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f39b.svg")}.twa-film-frames{background-image:url("https://twemoji.maxcdn.com/2/svg/1f39e.svg")}.twa-admission-tickets{background-image:url("https://twemoji.maxcdn.com/2/svg/1f39f.svg")}.twa-carousel-horse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a0.svg")}.twa-ferris-wheel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a1.svg")}.twa-roller-coaster{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a2.svg")}.twa-fishing-pole{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a3.svg")}.twa-fishing-pole-and-fish{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a3.svg")}.twa-microphone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a4.svg")}.twa-movie-camera{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a5.svg")}.twa-cinema{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a6.svg")}.twa-headphone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a7.svg")}.twa-headphones{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a7.svg")}.twa-artist-palette{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a8.svg")}.twa-art{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a8.svg")}.twa-top-hat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a9.svg")}.twa-tophat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a9.svg")}.twa-circus-tent{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3aa.svg")}.twa-ticket{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ab.svg")}.twa-clapper-board{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ac.svg")}.twa-clapper{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ac.svg")}.twa-performing-arts{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ad.svg")}.twa-video-game{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ae.svg")}.twa-direct-hit{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3af.svg")}.twa-dart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3af.svg")}.twa-slot-machine{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b0.svg")}.twa-billiards{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b1.svg")}.twa-8ball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b1.svg")}.twa-game-die{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b2.svg")}.twa-bowling{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b3.svg")}.twa-flower-playing-cards{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b4.svg")}.twa-musical-note{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b5.svg")}.twa-musical-notes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b6.svg")}.twa-notes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b6.svg")}.twa-saxophone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b7.svg")}.twa-guitar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b8.svg")}.twa-musical-keyboard{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b9.svg")}.twa-trumpet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ba.svg")}.twa-violin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bb.svg")}.twa-musical-score{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bc.svg")}.twa-running-shirt{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bd.svg")}.twa-running-shirt-with-sash{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bd.svg")}.twa-tennis{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3be.svg")}.twa-skis{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bf.svg")}.twa-ski{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bf.svg")}.twa-basketball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c0.svg")}.twa-chequered-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c1.svg")}.twa-checkered-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c1.svg")}.twa-snowboarder-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2-1f3fb.svg")}.twa-snowboarder-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2-1f3fc.svg")}.twa-snowboarder-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2-1f3fd.svg")}.twa-snowboarder-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2-1f3fe.svg")}.twa-snowboarder-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2-1f3ff.svg")}.twa-snowboarder{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2.svg")}.twa-runner-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-1f3fb.svg")}.twa-runner-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-1f3fc.svg")}.twa-runner-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-1f3fd.svg")}.twa-runner-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-1f3fe.svg")}.twa-runner-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-1f3ff.svg")}.twa-runner{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3.svg")}.twa-surfer-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-1f3fb.svg")}.twa-surfer-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-1f3fc.svg")}.twa-surfer-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-1f3fd.svg")}.twa-surfer-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-1f3fe.svg")}.twa-surfer-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-1f3ff.svg")}.twa-surfer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4.svg")}.twa-sports-medal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c5.svg")}.twa-trophy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c6.svg")}.twa-horse-racing-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7-1f3fb.svg")}.twa-horse-racing-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7-1f3fc.svg")}.twa-horse-racing-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7-1f3fd.svg")}.twa-horse-racing-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7-1f3fe.svg")}.twa-horse-racing-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7-1f3ff.svg")}.twa-horse-racing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7.svg")}.twa-american-football{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c8.svg")}.twa-football{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c8.svg")}.twa-rugby-football{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c9.svg")}.twa-swimmer-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-1f3fb.svg")}.twa-swimmer-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-1f3fc.svg")}.twa-swimmer-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-1f3fd.svg")}.twa-swimmer-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-1f3fe.svg")}.twa-swimmer-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-1f3ff.svg")}.twa-swimmer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca.svg")}.twa-weight-lifter-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-1f3fb.svg")}.twa-weight-lifter-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-1f3fc.svg")}.twa-weight-lifter-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-1f3fd.svg")}.twa-weight-lifter-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-1f3fe.svg")}.twa-weight-lifter-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-1f3ff.svg")}.twa-weight-lifter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb.svg")}.twa-golfer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cc.svg")}.twa-motorcycle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cd.svg")}.twa-racing-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ce.svg")}.twa-cricket{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cf.svg")}.twa-volleyball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d0.svg")}.twa-field-hockey{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d1.svg")}.twa-ice-hockey-stick-and-puck{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d2.svg")}.twa-ping-pong{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d3.svg")}.twa-snow-capped-mountain{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d4.svg")}.twa-camping{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d5.svg")}.twa-beach-with-umbrella{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d6.svg")}.twa-building-construction{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d7.svg")}.twa-house-buildings{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d8.svg")}.twa-cityscape{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d9.svg")}.twa-derelict-house-building{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3da.svg")}.twa-classical-building{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3db.svg")}.twa-desert{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3dc.svg")}.twa-desert-island{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3dd.svg")}.twa-national-park{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3de.svg")}.twa-stadium{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3df.svg")}.twa-house-building{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e0.svg")}.twa-house{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e0.svg")}.twa-house-with-garden{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e1.svg")}.twa-office-building{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e2.svg")}.twa-office{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e2.svg")}.twa-japanese-post-office{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e3.svg")}.twa-post-office{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e4.svg")}.twa-european-post-office{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e4.svg")}.twa-hospital{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e5.svg")}.twa-bank{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e6.svg")}.twa-atm-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e7.svg")}.twa-atm{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e7.svg")}.twa-hotel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e8.svg")}.twa-love-hotel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e9.svg")}.twa-convenience-store{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ea.svg")}.twa-school{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3eb.svg")}.twa-department-store{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ec.svg")}.twa-factory{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ed.svg")}.twa-red-paper-lantern{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ee.svg")}.twa-japanese-castle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ef.svg")}.twa-castle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f0.svg")}.twa-european-castle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f0.svg")}.twa-rainbow-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f3-fe0f-200d-1f308.svg")}.twa-pride{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f3-fe0f-200d-1f308.svg")}.twa-waving-white-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f3.svg")}.twa-pirate-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4-200d-2620-fe0f.svg")}.twa-jolly-roger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4-200d-2620-fe0f.svg")}.twa-waving-black-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4.svg")}.twa-rosette{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f5.svg")}.twa-label{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f7.svg")}.twa-badminton{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f8.svg")}.twa-bow-and-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f9.svg")}.twa-amphora{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fa.svg")}.twa-skin-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fb.svg")}.twa-skin-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fc.svg")}.twa-skin-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fd.svg")}.twa-skin-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fe.svg")}.twa-skin-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ff.svg")}.twa-rat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f400.svg")}.twa-mouse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f401.svg")}.twa-mouse2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f401.svg")}.twa-ox{background-image:url("https://twemoji.maxcdn.com/2/svg/1f402.svg")}.twa-water-buffalo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f403.svg")}.twa-cow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f404.svg")}.twa-cow2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f404.svg")}.twa-tiger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f405.svg")}.twa-tiger2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f405.svg")}.twa-leopard{background-image:url("https://twemoji.maxcdn.com/2/svg/1f406.svg")}.twa-rabbit{background-image:url("https://twemoji.maxcdn.com/2/svg/1f407.svg")}.twa-rabbit2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f407.svg")}.twa-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f408.svg")}.twa-cat2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f408.svg")}.twa-dragon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f409.svg")}.twa-crocodile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40a.svg")}.twa-whale{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40b.svg")}.twa-whale2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40b.svg")}.twa-snail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40c.svg")}.twa-snake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40d.svg")}.twa-horse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40e.svg")}.twa-racehorse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40e.svg")}.twa-ram{background-image:url("https://twemoji.maxcdn.com/2/svg/1f40f.svg")}.twa-goat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f410.svg")}.twa-sheep{background-image:url("https://twemoji.maxcdn.com/2/svg/1f411.svg")}.twa-monkey{background-image:url("https://twemoji.maxcdn.com/2/svg/1f412.svg")}.twa-rooster{background-image:url("https://twemoji.maxcdn.com/2/svg/1f413.svg")}.twa-chicken{background-image:url("https://twemoji.maxcdn.com/2/svg/1f414.svg")}.twa-dog{background-image:url("https://twemoji.maxcdn.com/2/svg/1f415.svg")}.twa-dog2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f415.svg")}.twa-pig{background-image:url("https://twemoji.maxcdn.com/2/svg/1f416.svg")}.twa-pig2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f416.svg")}.twa-boar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f417.svg")}.twa-elephant{background-image:url("https://twemoji.maxcdn.com/2/svg/1f418.svg")}.twa-octopus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f419.svg")}.twa-spiral-shell{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41a.svg")}.twa-shell{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41a.svg")}.twa-bug{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41b.svg")}.twa-ant{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41c.svg")}.twa-honeybee{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41d.svg")}.twa-lady-beetle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41e.svg")}.twa-beetle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41e.svg")}.twa-fish{background-image:url("https://twemoji.maxcdn.com/2/svg/1f41f.svg")}.twa-tropical-fish{background-image:url("https://twemoji.maxcdn.com/2/svg/1f420.svg")}.twa-blowfish{background-image:url("https://twemoji.maxcdn.com/2/svg/1f421.svg")}.twa-turtle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f422.svg")}.twa-hatching-chick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f423.svg")}.twa-baby-chick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f424.svg")}.twa-front-facing-baby-chick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f425.svg")}.twa-hatched-chick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f425.svg")}.twa-bird{background-image:url("https://twemoji.maxcdn.com/2/svg/1f426.svg")}.twa-penguin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f427.svg")}.twa-koala{background-image:url("https://twemoji.maxcdn.com/2/svg/1f428.svg")}.twa-poodle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f429.svg")}.twa-camel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42a.svg")}.twa-dromedary-camel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42a.svg")}.twa-two-hump-camel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42b.svg")}.twa-dolphin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42c.svg")}.twa-mouse-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42d.svg")}.twa-cow-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42e.svg")}.twa-tiger-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f42f.svg")}.twa-rabbit-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f430.svg")}.twa-cat-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f431.svg")}.twa-dragon-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f432.svg")}.twa-spouting-whale{background-image:url("https://twemoji.maxcdn.com/2/svg/1f433.svg")}.twa-horse-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f434.svg")}.twa-monkey-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f435.svg")}.twa-dog-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f436.svg")}.twa-pig-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f437.svg")}.twa-frog-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f438.svg")}.twa-frog{background-image:url("https://twemoji.maxcdn.com/2/svg/1f438.svg")}.twa-hamster-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f439.svg")}.twa-hamster{background-image:url("https://twemoji.maxcdn.com/2/svg/1f439.svg")}.twa-wolf-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43a.svg")}.twa-wolf{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43a.svg")}.twa-bear-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43b.svg")}.twa-bear{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43b.svg")}.twa-panda-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43c.svg")}.twa-pig-nose{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43d.svg")}.twa-paw-prints{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43e.svg")}.twa-chipmunk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f43f.svg")}.twa-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f440.svg")}.twa-eye-in-speech-bubble{background-image:url("https://twemoji.maxcdn.com/2/svg/1f441-200d-1f5e8.svg")}.twa-eye{background-image:url("https://twemoji.maxcdn.com/2/svg/1f441.svg")}.twa-ear-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442-1f3fb.svg")}.twa-ear-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442-1f3fc.svg")}.twa-ear-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442-1f3fd.svg")}.twa-ear-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442-1f3fe.svg")}.twa-ear-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442-1f3ff.svg")}.twa-ear{background-image:url("https://twemoji.maxcdn.com/2/svg/1f442.svg")}.twa-nose-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443-1f3fb.svg")}.twa-nose-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443-1f3fc.svg")}.twa-nose-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443-1f3fd.svg")}.twa-nose-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443-1f3fe.svg")}.twa-nose-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443-1f3ff.svg")}.twa-nose{background-image:url("https://twemoji.maxcdn.com/2/svg/1f443.svg")}.twa-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f444.svg")}.twa-lips{background-image:url("https://twemoji.maxcdn.com/2/svg/1f444.svg")}.twa-tongue{background-image:url("https://twemoji.maxcdn.com/2/svg/1f445.svg")}.twa-white-up-pointing-backhand-index-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446-1f3fb.svg")}.twa-white-up-pointing-backhand-index-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446-1f3fc.svg")}.twa-white-up-pointing-backhand-index-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446-1f3fd.svg")}.twa-white-up-pointing-backhand-index-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446-1f3fe.svg")}.twa-white-up-pointing-backhand-index-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446-1f3ff.svg")}.twa-backhand-index-pointing-up{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446.svg")}.twa-point-up-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f446.svg")}.twa-white-down-pointing-backhand-index-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447-1f3fb.svg")}.twa-white-down-pointing-backhand-index-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447-1f3fc.svg")}.twa-white-down-pointing-backhand-index-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447-1f3fd.svg")}.twa-white-down-pointing-backhand-index-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447-1f3fe.svg")}.twa-white-down-pointing-backhand-index-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447-1f3ff.svg")}.twa-backhand-index-pointing-down{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447.svg")}.twa-point-down{background-image:url("https://twemoji.maxcdn.com/2/svg/1f447.svg")}.twa-white-left-pointing-backhand-index-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448-1f3fb.svg")}.twa-white-left-pointing-backhand-index-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448-1f3fc.svg")}.twa-white-left-pointing-backhand-index-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448-1f3fd.svg")}.twa-white-left-pointing-backhand-index-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448-1f3fe.svg")}.twa-white-left-pointing-backhand-index-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448-1f3ff.svg")}.twa-backhand-index-pointing-left{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448.svg")}.twa-point-left{background-image:url("https://twemoji.maxcdn.com/2/svg/1f448.svg")}.twa-white-right-pointing-backhand-index-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449-1f3fb.svg")}.twa-white-right-pointing-backhand-index-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449-1f3fc.svg")}.twa-white-right-pointing-backhand-index-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449-1f3fd.svg")}.twa-white-right-pointing-backhand-index-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449-1f3fe.svg")}.twa-white-right-pointing-backhand-index-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449-1f3ff.svg")}.twa-backhand-index-pointing-right{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449.svg")}.twa-point-right{background-image:url("https://twemoji.maxcdn.com/2/svg/1f449.svg")}.twa-fisted-hand-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a-1f3fb.svg")}.twa-fisted-hand-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a-1f3fc.svg")}.twa-fisted-hand-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a-1f3fd.svg")}.twa-fisted-hand-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a-1f3fe.svg")}.twa-fisted-hand-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a-1f3ff.svg")}.twa-oncoming-fist{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a.svg")}.twa-punch{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a.svg")}.twa-waving-hand-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b-1f3fb.svg")}.twa-waving-hand-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b-1f3fc.svg")}.twa-waving-hand-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b-1f3fd.svg")}.twa-waving-hand-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b-1f3fe.svg")}.twa-waving-hand-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b-1f3ff.svg")}.twa-waving-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b.svg")}.twa-wave{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b.svg")}.twa-ok-hand-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c-1f3fb.svg")}.twa-ok-hand-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c-1f3fc.svg")}.twa-ok-hand-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c-1f3fd.svg")}.twa-ok-hand-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c-1f3fe.svg")}.twa-ok-hand-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c-1f3ff.svg")}.twa-ok-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c.svg")}.twa-thumbs-up-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d-1f3fb.svg")}.twa-thumbs-up-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d-1f3fc.svg")}.twa-thumbs-up-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d-1f3fd.svg")}.twa-thumbs-up-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d-1f3fe.svg")}.twa-thumbs-up-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d-1f3ff.svg")}.twa-thumbs-up{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d.svg")}.twa-thumbsup{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d.svg")}.twa-thumbs-down-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e-1f3fb.svg")}.twa-thumbs-down-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e-1f3fc.svg")}.twa-thumbs-down-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e-1f3fd.svg")}.twa-thumbs-down-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e-1f3fe.svg")}.twa-thumbs-down-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e-1f3ff.svg")}.twa-thumbs-down{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e.svg")}.twa-thumbsdown{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e.svg")}.twa-clapping-hands-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f-1f3fb.svg")}.twa-clapping-hands-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f-1f3fc.svg")}.twa-clapping-hands-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f-1f3fd.svg")}.twa-clapping-hands-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f-1f3fe.svg")}.twa-clapping-hands-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f-1f3ff.svg")}.twa-clapping-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f.svg")}.twa-clap{background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f.svg")}.twa-open-hands-sign-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450-1f3fb.svg")}.twa-open-hands-sign-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450-1f3fc.svg")}.twa-open-hands-sign-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450-1f3fd.svg")}.twa-open-hands-sign-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450-1f3fe.svg")}.twa-open-hands-sign-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450-1f3ff.svg")}.twa-open-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f450.svg")}.twa-crown{background-image:url("https://twemoji.maxcdn.com/2/svg/1f451.svg")}.twa-womans-hat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f452.svg")}.twa-glasses{background-image:url("https://twemoji.maxcdn.com/2/svg/1f453.svg")}.twa-eyeglasses{background-image:url("https://twemoji.maxcdn.com/2/svg/1f453.svg")}.twa-necktie{background-image:url("https://twemoji.maxcdn.com/2/svg/1f454.svg")}.twa-t-shirt{background-image:url("https://twemoji.maxcdn.com/2/svg/1f455.svg")}.twa-shirt{background-image:url("https://twemoji.maxcdn.com/2/svg/1f455.svg")}.twa-jeans{background-image:url("https://twemoji.maxcdn.com/2/svg/1f456.svg")}.twa-dress{background-image:url("https://twemoji.maxcdn.com/2/svg/1f457.svg")}.twa-kimono{background-image:url("https://twemoji.maxcdn.com/2/svg/1f458.svg")}.twa-bikini{background-image:url("https://twemoji.maxcdn.com/2/svg/1f459.svg")}.twa-womans-clothes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45a.svg")}.twa-purse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45b.svg")}.twa-handbag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45c.svg")}.twa-pouch{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45d.svg")}.twa-mans-shoe{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45e.svg")}.twa-running-shoe{background-image:url("https://twemoji.maxcdn.com/2/svg/1f45f.svg")}.twa-high-heeled-shoe{background-image:url("https://twemoji.maxcdn.com/2/svg/1f460.svg")}.twa-high-heel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f460.svg")}.twa-womans-sandal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f461.svg")}.twa-sandal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f461.svg")}.twa-womans-boot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f462.svg")}.twa-boot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f462.svg")}.twa-footprints{background-image:url("https://twemoji.maxcdn.com/2/svg/1f463.svg")}.twa-feet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f463.svg")}.twa-bust-in-silhouette{background-image:url("https://twemoji.maxcdn.com/2/svg/1f464.svg")}.twa-busts-in-silhouette{background-image:url("https://twemoji.maxcdn.com/2/svg/1f465.svg")}.twa-boy-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466-1f3fb.svg")}.twa-boy-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466-1f3fc.svg")}.twa-boy-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466-1f3fd.svg")}.twa-boy-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466-1f3fe.svg")}.twa-boy-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466-1f3ff.svg")}.twa-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f466.svg")}.twa-girl-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467-1f3fb.svg")}.twa-girl-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467-1f3fc.svg")}.twa-girl-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467-1f3fd.svg")}.twa-girl-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467-1f3fe.svg")}.twa-girl-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467-1f3ff.svg")}.twa-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f467.svg")}.twa-man-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-1f3fb.svg")}.twa-man-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-1f3fc.svg")}.twa-man-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-1f3fd.svg")}.twa-man-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-1f3fe.svg")}.twa-man-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-1f3ff.svg")}.twa-family-man-man-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f466-200d-1f466.svg")}.twa-family-man-man-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f466.svg")}.twa-family-man-man-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467-200d-1f466.svg")}.twa-family-man-man-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467-200d-1f467.svg")}.twa-family-man-man-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467.svg")}.twa-family-man-woman-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f466-200d-1f466.svg")}.twa-family-man-woman-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f466.svg")}.twa-family-man-woman-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467-200d-1f466.svg")}.twa-family-man-woman-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467-200d-1f467.svg")}.twa-family-man-woman-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467.svg")}.twa-couple-with-heart-man-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2764-fe0f-200d-1f468.svg")}.twa-kiss-man-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.svg")}.twa-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f468.svg")}.twa-woman-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-1f3fb.svg")}.twa-woman-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-1f3fc.svg")}.twa-woman-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-1f3fd.svg")}.twa-woman-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-1f3fe.svg")}.twa-woman-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-1f3ff.svg")}.twa-family-woman-woman-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f466-200d-1f466.svg")}.twa-family-woman-woman-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f466.svg")}.twa-family-woman-woman-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467-200d-1f466.svg")}.twa-family-woman-woman-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467-200d-1f467.svg")}.twa-family-woman-woman-girl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467.svg")}.twa-couple-with-heart-woman-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f468.svg")}.twa-couple-with-heart-woman-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f469.svg")}.twa-kiss-woman-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.svg")}.twa-kiss-woman-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.svg")}.twa-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f469.svg")}.twa-family{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46a.svg")}.twa-man-and-woman-holding-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46b.svg")}.twa-couple{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46b.svg")}.twa-two-men-holding-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46c.svg")}.twa-two-women-holding-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46d.svg")}.twa-police-officer-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-1f3fb.svg")}.twa-police-officer-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-1f3fc.svg")}.twa-police-officer-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-1f3fd.svg")}.twa-police-officer-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-1f3fe.svg")}.twa-police-officer-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-1f3ff.svg")}.twa-police-officer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e.svg")}.twa-cop{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e.svg")}.twa-women-partying{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46f.svg")}.twa-dancers{background-image:url("https://twemoji.maxcdn.com/2/svg/1f46f.svg")}.twa-bride-with-veil-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470-1f3fb.svg")}.twa-bride-with-veil-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470-1f3fc.svg")}.twa-bride-with-veil-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470-1f3fd.svg")}.twa-bride-with-veil-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470-1f3fe.svg")}.twa-bride-with-veil-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470-1f3ff.svg")}.twa-bride-with-veil{background-image:url("https://twemoji.maxcdn.com/2/svg/1f470.svg")}.twa-person-with-blond-hair-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-1f3fb.svg")}.twa-person-with-blond-hair-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-1f3fc.svg")}.twa-person-with-blond-hair-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-1f3fd.svg")}.twa-person-with-blond-hair-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-1f3fe.svg")}.twa-person-with-blond-hair-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-1f3ff.svg")}.twa-person-with-blond-hair{background-image:url("https://twemoji.maxcdn.com/2/svg/1f471.svg")}.twa-man-with-gua-pi-mao-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472-1f3fb.svg")}.twa-man-with-gua-pi-mao-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472-1f3fc.svg")}.twa-man-with-gua-pi-mao-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472-1f3fd.svg")}.twa-man-with-gua-pi-mao-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472-1f3fe.svg")}.twa-man-with-gua-pi-mao-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472-1f3ff.svg")}.twa-man-with-chinese-cap{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472.svg")}.twa-man-with-gua-pi-mao{background-image:url("https://twemoji.maxcdn.com/2/svg/1f472.svg")}.twa-man-with-turban-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-1f3fb.svg")}.twa-man-with-turban-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-1f3fc.svg")}.twa-man-with-turban-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-1f3fd.svg")}.twa-man-with-turban-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-1f3fe.svg")}.twa-man-with-turban-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-1f3ff.svg")}.twa-man-with-turban{background-image:url("https://twemoji.maxcdn.com/2/svg/1f473.svg")}.twa-older-man-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474-1f3fb.svg")}.twa-older-man-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474-1f3fc.svg")}.twa-older-man-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474-1f3fd.svg")}.twa-older-man-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474-1f3fe.svg")}.twa-older-man-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474-1f3ff.svg")}.twa-old-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474.svg")}.twa-older-man{background-image:url("https://twemoji.maxcdn.com/2/svg/1f474.svg")}.twa-older-woman-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475-1f3fb.svg")}.twa-older-woman-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475-1f3fc.svg")}.twa-older-woman-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475-1f3fd.svg")}.twa-older-woman-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475-1f3fe.svg")}.twa-older-woman-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475-1f3ff.svg")}.twa-old-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475.svg")}.twa-older-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f475.svg")}.twa-baby-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476-1f3fb.svg")}.twa-baby-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476-1f3fc.svg")}.twa-baby-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476-1f3fd.svg")}.twa-baby-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476-1f3fe.svg")}.twa-baby-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476-1f3ff.svg")}.twa-baby{background-image:url("https://twemoji.maxcdn.com/2/svg/1f476.svg")}.twa-construction-worker-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-1f3fb.svg")}.twa-construction-worker-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-1f3fc.svg")}.twa-construction-worker-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-1f3fd.svg")}.twa-construction-worker-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-1f3fe.svg")}.twa-construction-worker-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-1f3ff.svg")}.twa-construction-worker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f477.svg")}.twa-princess-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478-1f3fb.svg")}.twa-princess-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478-1f3fc.svg")}.twa-princess-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478-1f3fd.svg")}.twa-princess-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478-1f3fe.svg")}.twa-princess-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478-1f3ff.svg")}.twa-princess{background-image:url("https://twemoji.maxcdn.com/2/svg/1f478.svg")}.twa-ogre{background-image:url("https://twemoji.maxcdn.com/2/svg/1f479.svg")}.twa-japanese-ogre{background-image:url("https://twemoji.maxcdn.com/2/svg/1f479.svg")}.twa-goblin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47a.svg")}.twa-japanese-goblin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47a.svg")}.twa-ghost{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47b.svg")}.twa-baby-angel-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c-1f3fb.svg")}.twa-baby-angel-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c-1f3fc.svg")}.twa-baby-angel-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c-1f3fd.svg")}.twa-baby-angel-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c-1f3fe.svg")}.twa-baby-angel-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c-1f3ff.svg")}.twa-baby-angel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c.svg")}.twa-angel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c.svg")}.twa-alien{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47d.svg")}.twa-alien-monster{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47e.svg")}.twa-space-invader{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47e.svg")}.twa-imp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f47f.svg")}.twa-skull{background-image:url("https://twemoji.maxcdn.com/2/svg/1f480.svg")}.twa-information-desk-person-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-1f3fb.svg")}.twa-information-desk-person-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-1f3fc.svg")}.twa-information-desk-person-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-1f3fd.svg")}.twa-information-desk-person-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-1f3fe.svg")}.twa-information-desk-person-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-1f3ff.svg")}.twa-information-desk-person{background-image:url("https://twemoji.maxcdn.com/2/svg/1f481.svg")}.twa-guardsman-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-1f3fb.svg")}.twa-guardsman-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-1f3fc.svg")}.twa-guardsman-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-1f3fd.svg")}.twa-guardsman-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-1f3fe.svg")}.twa-guardsman-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-1f3ff.svg")}.twa-guardsman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f482.svg")}.twa-dancer-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483-1f3fb.svg")}.twa-dancer-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483-1f3fc.svg")}.twa-dancer-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483-1f3fd.svg")}.twa-dancer-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483-1f3fe.svg")}.twa-dancer-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483-1f3ff.svg")}.twa-dancer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f483.svg")}.twa-lipstick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f484.svg")}.twa-nail-polish-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485-1f3fb.svg")}.twa-nail-polish-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485-1f3fc.svg")}.twa-nail-polish-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485-1f3fd.svg")}.twa-nail-polish-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485-1f3fe.svg")}.twa-nail-polish-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485-1f3ff.svg")}.twa-nail-polish{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485.svg")}.twa-nail-care{background-image:url("https://twemoji.maxcdn.com/2/svg/1f485.svg")}.twa-face-massage-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-1f3fb.svg")}.twa-face-massage-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-1f3fc.svg")}.twa-face-massage-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-1f3fd.svg")}.twa-face-massage-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-1f3fe.svg")}.twa-face-massage-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-1f3ff.svg")}.twa-face-massage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486.svg")}.twa-massage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f486.svg")}.twa-haircut-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-1f3fb.svg")}.twa-haircut-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-1f3fc.svg")}.twa-haircut-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-1f3fd.svg")}.twa-haircut-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-1f3fe.svg")}.twa-haircut-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-1f3ff.svg")}.twa-haircut{background-image:url("https://twemoji.maxcdn.com/2/svg/1f487.svg")}.twa-barber-pole{background-image:url("https://twemoji.maxcdn.com/2/svg/1f488.svg")}.twa-barber{background-image:url("https://twemoji.maxcdn.com/2/svg/1f488.svg")}.twa-syringe{background-image:url("https://twemoji.maxcdn.com/2/svg/1f489.svg")}.twa-pill{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48a.svg")}.twa-kiss-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48b.svg")}.twa-love-letter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48c.svg")}.twa-ring{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48d.svg")}.twa-gem-stone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48e.svg")}.twa-gem{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48e.svg")}.twa-kiss{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48f.svg")}.twa-couplekiss{background-image:url("https://twemoji.maxcdn.com/2/svg/1f48f.svg")}.twa-bouquet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f490.svg")}.twa-couple-with-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f491.svg")}.twa-wedding{background-image:url("https://twemoji.maxcdn.com/2/svg/1f492.svg")}.twa-beating-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f493.svg")}.twa-heartbeat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f493.svg")}.twa-broken-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f494.svg")}.twa-two-hearts{background-image:url("https://twemoji.maxcdn.com/2/svg/1f495.svg")}.twa-sparkling-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f496.svg")}.twa-growing-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f497.svg")}.twa-heartpulse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f497.svg")}.twa-heart-with-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f498.svg")}.twa-cupid{background-image:url("https://twemoji.maxcdn.com/2/svg/1f498.svg")}.twa-blue-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f499.svg")}.twa-green-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49a.svg")}.twa-yellow-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49b.svg")}.twa-purple-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49c.svg")}.twa-heart-with-ribbon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49d.svg")}.twa-gift-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49d.svg")}.twa-revolving-hearts{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49e.svg")}.twa-heart-decoration{background-image:url("https://twemoji.maxcdn.com/2/svg/1f49f.svg")}.twa-diamond-with-a-dot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a0.svg")}.twa-diamond-shape-with-a-dot-inside{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a0.svg")}.twa-light-bulb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a1.svg")}.twa-bulb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a1.svg")}.twa-anger-symbol{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a2.svg")}.twa-anger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a2.svg")}.twa-bomb{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a3.svg")}.twa-zzz{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a4.svg")}.twa-collision{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a5.svg")}.twa-boom{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a5.svg")}.twa-sweat-droplets{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a6.svg")}.twa-sweat-drops{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a6.svg")}.twa-droplet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a7.svg")}.twa-dashing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a8.svg")}.twa-dash{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a8.svg")}.twa-pile-of-poo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a9.svg")}.twa-poop{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a9.svg")}.twa-flexed-biceps-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa-1f3fb.svg")}.twa-flexed-biceps-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa-1f3fc.svg")}.twa-flexed-biceps-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa-1f3fd.svg")}.twa-flexed-biceps-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa-1f3fe.svg")}.twa-flexed-biceps-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa-1f3ff.svg")}.twa-flexed-biceps{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa.svg")}.twa-muscle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa.svg")}.twa-dizzy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ab.svg")}.twa-speech-balloon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ac.svg")}.twa-thought-balloon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ad.svg")}.twa-white-flower{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ae.svg")}.twa-hundred-points{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4af.svg")}.twa-100{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4af.svg")}.twa-money-bag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b0.svg")}.twa-moneybag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b0.svg")}.twa-currency-exchange{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b1.svg")}.twa-heavy-dollar-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b2.svg")}.twa-credit-card{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b3.svg")}.twa-yen-banknote{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b4.svg")}.twa-yen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b4.svg")}.twa-dollar-banknote{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b5.svg")}.twa-dollar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b5.svg")}.twa-euro-banknote{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b6.svg")}.twa-euro{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b6.svg")}.twa-pound-banknote{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b7.svg")}.twa-pound{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b7.svg")}.twa-money-with-wings{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b8.svg")}.twa-chart-increasing-with-yen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b9.svg")}.twa-chart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b9.svg")}.twa-seat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ba.svg")}.twa-laptop-computer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bb.svg")}.twa-computer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bb.svg")}.twa-briefcase{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bc.svg")}.twa-minidisc{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bd.svg")}.twa-floppy-disk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4be.svg")}.twa-optical-disc{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bf.svg")}.twa-cd{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bf.svg")}.twa-dvd{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c0.svg")}.twa-file-folder{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c1.svg")}.twa-open-file-folder{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c2.svg")}.twa-page-with-curl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c3.svg")}.twa-page-facing-up{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c4.svg")}.twa-calendar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c5.svg")}.twa-date{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c5.svg")}.twa-tear-off-calendar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c6.svg")}.twa-card-index{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c7.svg")}.twa-chart-increasing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c8.svg")}.twa-chart-with-upwards-trend{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c8.svg")}.twa-chart-decreasing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c9.svg")}.twa-chart-with-downwards-trend{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c9.svg")}.twa-bar-chart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ca.svg")}.twa-clipboard{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cb.svg")}.twa-pushpin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cc.svg")}.twa-round-pushpin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cd.svg")}.twa-paperclip{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ce.svg")}.twa-straight-ruler{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cf.svg")}.twa-triangular-ruler{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d0.svg")}.twa-bookmark-tabs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d1.svg")}.twa-ledger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d2.svg")}.twa-notebook{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d3.svg")}.twa-notebook-with-decorative-cover{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d4.svg")}.twa-closed-book{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d5.svg")}.twa-open-book{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d6.svg")}.twa-green-book{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d7.svg")}.twa-blue-book{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d8.svg")}.twa-orange-book{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d9.svg")}.twa-books{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4da.svg")}.twa-name-badge{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4db.svg")}.twa-scroll{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4dc.svg")}.twa-memo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4dd.svg")}.twa-telephone-receiver{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4de.svg")}.twa-pager{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4df.svg")}.twa-fax-machine{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e0.svg")}.twa-fax{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e0.svg")}.twa-satellite-antenna{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e1.svg")}.twa-loudspeaker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e2.svg")}.twa-megaphone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e3.svg")}.twa-mega{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e3.svg")}.twa-outbox-tray{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e4.svg")}.twa-inbox-tray{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e5.svg")}.twa-package{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e6.svg")}.twa-e-mail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e7.svg")}.twa-incoming-envelope{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e8.svg")}.twa-envelope-with-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e9.svg")}.twa-closed-mailbox-with-lowered-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ea.svg")}.twa-mailbox-closed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ea.svg")}.twa-closed-mailbox-with-raised-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4eb.svg")}.twa-mailbox{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4eb.svg")}.twa-open-mailbox-with-raised-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ec.svg")}.twa-mailbox-with-mail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ec.svg")}.twa-open-mailbox-with-lowered-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ed.svg")}.twa-mailbox-with-no-mail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ed.svg")}.twa-postbox{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ee.svg")}.twa-postal-horn{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ef.svg")}.twa-newspaper{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f0.svg")}.twa-mobile-phone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f1.svg")}.twa-iphone{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f1.svg")}.twa-mobile-phone-with-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f2.svg")}.twa-calling{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f2.svg")}.twa-vibration-mode{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f3.svg")}.twa-mobile-phone-off{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f4.svg")}.twa-no-mobile-phones{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f5.svg")}.twa-antenna-bars{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f6.svg")}.twa-signal-strength{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f6.svg")}.twa-camera{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f7.svg")}.twa-camera-with-flash{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f8.svg")}.twa-video-camera{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f9.svg")}.twa-television{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fa.svg")}.twa-tv{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fa.svg")}.twa-radio{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fb.svg")}.twa-videocassette{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fc.svg")}.twa-vhs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fc.svg")}.twa-film-projector{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fd.svg")}.twa-prayer-beads{background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ff.svg")}.twa-shuffle-tracks-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f500.svg")}.twa-twisted-rightwards-arrows{background-image:url("https://twemoji.maxcdn.com/2/svg/1f500.svg")}.twa-repeat-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f501.svg")}.twa-repeat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f501.svg")}.twa-repeat-single-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f502.svg")}.twa-repeat-one{background-image:url("https://twemoji.maxcdn.com/2/svg/1f502.svg")}.twa-clockwise-vertical-arrows{background-image:url("https://twemoji.maxcdn.com/2/svg/1f503.svg")}.twa-arrows-clockwise{background-image:url("https://twemoji.maxcdn.com/2/svg/1f503.svg")}.twa-anticlockwise-arrows-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f504.svg")}.twa-arrows-counterclockwise{background-image:url("https://twemoji.maxcdn.com/2/svg/1f504.svg")}.twa-dim-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f505.svg")}.twa-low-brightness{background-image:url("https://twemoji.maxcdn.com/2/svg/1f505.svg")}.twa-bright-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f506.svg")}.twa-high-brightness{background-image:url("https://twemoji.maxcdn.com/2/svg/1f506.svg")}.twa-speaker-off{background-image:url("https://twemoji.maxcdn.com/2/svg/1f507.svg")}.twa-mute{background-image:url("https://twemoji.maxcdn.com/2/svg/1f507.svg")}.twa-speaker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f508.svg")}.twa-speaker-on{background-image:url("https://twemoji.maxcdn.com/2/svg/1f509.svg")}.twa-sound{background-image:url("https://twemoji.maxcdn.com/2/svg/1f509.svg")}.twa-speaker-loud{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50a.svg")}.twa-battery{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50b.svg")}.twa-electric-plug{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50c.svg")}.twa-left-pointing-magnifying-glass{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50d.svg")}.twa-mag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50d.svg")}.twa-right-pointing-magnifying-glass{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50e.svg")}.twa-mag-right{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50e.svg")}.twa-lock-with-pen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50f.svg")}.twa-lock-with-ink-pen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f50f.svg")}.twa-closed-lock-with-key{background-image:url("https://twemoji.maxcdn.com/2/svg/1f510.svg")}.twa-key{background-image:url("https://twemoji.maxcdn.com/2/svg/1f511.svg")}.twa-lock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f512.svg")}.twa-open-lock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f513.svg")}.twa-unlock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f513.svg")}.twa-bell{background-image:url("https://twemoji.maxcdn.com/2/svg/1f514.svg")}.twa-bell-with-slash{background-image:url("https://twemoji.maxcdn.com/2/svg/1f515.svg")}.twa-no-bell{background-image:url("https://twemoji.maxcdn.com/2/svg/1f515.svg")}.twa-bookmark{background-image:url("https://twemoji.maxcdn.com/2/svg/1f516.svg")}.twa-link{background-image:url("https://twemoji.maxcdn.com/2/svg/1f517.svg")}.twa-radio-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f518.svg")}.twa-back-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f519.svg")}.twa-end-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51a.svg")}.twa-end{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51a.svg")}.twa-onexc-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51b.svg")}.twa-on{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51b.svg")}.twa-soon-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51c.svg")}.twa-soon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51c.svg")}.twa-top-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51d.svg")}.twa-top{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51d.svg")}.twa-no-one-under-eighteen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51e.svg")}.twa-underage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51e.svg")}.twa-keycap-ten{background-image:url("https://twemoji.maxcdn.com/2/svg/1f51f.svg")}.twa-input-latin-uppercase{background-image:url("https://twemoji.maxcdn.com/2/svg/1f520.svg")}.twa-capital-abcd{background-image:url("https://twemoji.maxcdn.com/2/svg/1f520.svg")}.twa-input-latin-lowercase{background-image:url("https://twemoji.maxcdn.com/2/svg/1f521.svg")}.twa-abcd{background-image:url("https://twemoji.maxcdn.com/2/svg/1f521.svg")}.twa-input-numbers{background-image:url("https://twemoji.maxcdn.com/2/svg/1f522.svg")}.twa-1234{background-image:url("https://twemoji.maxcdn.com/2/svg/1f522.svg")}.twa-input-symbols{background-image:url("https://twemoji.maxcdn.com/2/svg/1f523.svg")}.twa-symbols{background-image:url("https://twemoji.maxcdn.com/2/svg/1f523.svg")}.twa-input-latin-letters{background-image:url("https://twemoji.maxcdn.com/2/svg/1f524.svg")}.twa-abc{background-image:url("https://twemoji.maxcdn.com/2/svg/1f524.svg")}.twa-fire{background-image:url("https://twemoji.maxcdn.com/2/svg/1f525.svg")}.twa-flashlight{background-image:url("https://twemoji.maxcdn.com/2/svg/1f526.svg")}.twa-wrench{background-image:url("https://twemoji.maxcdn.com/2/svg/1f527.svg")}.twa-hammer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f528.svg")}.twa-nut-and-bolt{background-image:url("https://twemoji.maxcdn.com/2/svg/1f529.svg")}.twa-kitchen-knife{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52a.svg")}.twa-hocho{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52a.svg")}.twa-pistol{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52b.svg")}.twa-gun{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52b.svg")}.twa-microscope{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52c.svg")}.twa-telescope{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52d.svg")}.twa-crystal-ball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52e.svg")}.twa-dotted-six-pointed-star{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52f.svg")}.twa-six-pointed-star{background-image:url("https://twemoji.maxcdn.com/2/svg/1f52f.svg")}.twa-japanese-symbol-for-beginner{background-image:url("https://twemoji.maxcdn.com/2/svg/1f530.svg")}.twa-beginner{background-image:url("https://twemoji.maxcdn.com/2/svg/1f530.svg")}.twa-trident-emblem{background-image:url("https://twemoji.maxcdn.com/2/svg/1f531.svg")}.twa-trident{background-image:url("https://twemoji.maxcdn.com/2/svg/1f531.svg")}.twa-black-square-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f532.svg")}.twa-white-square-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f533.svg")}.twa-red-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f534.svg")}.twa-blue-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f535.svg")}.twa-large-blue-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f535.svg")}.twa-large-orange-diamond{background-image:url("https://twemoji.maxcdn.com/2/svg/1f536.svg")}.twa-large-blue-diamond{background-image:url("https://twemoji.maxcdn.com/2/svg/1f537.svg")}.twa-small-orange-diamond{background-image:url("https://twemoji.maxcdn.com/2/svg/1f538.svg")}.twa-small-blue-diamond{background-image:url("https://twemoji.maxcdn.com/2/svg/1f539.svg")}.twa-red-triangle-pointed-up{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53a.svg")}.twa-small-red-triangle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53a.svg")}.twa-red-triangle-pointed-down{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53b.svg")}.twa-small-red-triangle-down{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53b.svg")}.twa-up-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53c.svg")}.twa-arrow-up-small{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53c.svg")}.twa-down-button{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53d.svg")}.twa-arrow-down-small{background-image:url("https://twemoji.maxcdn.com/2/svg/1f53d.svg")}.twa-om{background-image:url("https://twemoji.maxcdn.com/2/svg/1f549.svg")}.twa-dove{background-image:url("https://twemoji.maxcdn.com/2/svg/1f54a.svg")}.twa-kaaba{background-image:url("https://twemoji.maxcdn.com/2/svg/1f54b.svg")}.twa-mosque{background-image:url("https://twemoji.maxcdn.com/2/svg/1f54c.svg")}.twa-synagogue{background-image:url("https://twemoji.maxcdn.com/2/svg/1f54d.svg")}.twa-menorah{background-image:url("https://twemoji.maxcdn.com/2/svg/1f54e.svg")}.twa-one-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f550.svg")}.twa-clock1{background-image:url("https://twemoji.maxcdn.com/2/svg/1f550.svg")}.twa-two-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f551.svg")}.twa-clock2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f551.svg")}.twa-three-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f552.svg")}.twa-clock3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f552.svg")}.twa-four-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f553.svg")}.twa-clock4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f553.svg")}.twa-five-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f554.svg")}.twa-clock5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f554.svg")}.twa-six-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f555.svg")}.twa-clock6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f555.svg")}.twa-seven-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f556.svg")}.twa-clock7{background-image:url("https://twemoji.maxcdn.com/2/svg/1f556.svg")}.twa-eight-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f557.svg")}.twa-clock8{background-image:url("https://twemoji.maxcdn.com/2/svg/1f557.svg")}.twa-nine-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f558.svg")}.twa-clock9{background-image:url("https://twemoji.maxcdn.com/2/svg/1f558.svg")}.twa-ten-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f559.svg")}.twa-clock10{background-image:url("https://twemoji.maxcdn.com/2/svg/1f559.svg")}.twa-eleven-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55a.svg")}.twa-clock11{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55a.svg")}.twa-twelve-oclock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55b.svg")}.twa-clock12{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55b.svg")}.twa-one-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55c.svg")}.twa-clock130{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55c.svg")}.twa-two-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55d.svg")}.twa-clock230{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55d.svg")}.twa-three-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55e.svg")}.twa-clock330{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55e.svg")}.twa-four-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55f.svg")}.twa-clock430{background-image:url("https://twemoji.maxcdn.com/2/svg/1f55f.svg")}.twa-five-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f560.svg")}.twa-clock530{background-image:url("https://twemoji.maxcdn.com/2/svg/1f560.svg")}.twa-six-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f561.svg")}.twa-clock630{background-image:url("https://twemoji.maxcdn.com/2/svg/1f561.svg")}.twa-seven-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f562.svg")}.twa-clock730{background-image:url("https://twemoji.maxcdn.com/2/svg/1f562.svg")}.twa-eight-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f563.svg")}.twa-clock830{background-image:url("https://twemoji.maxcdn.com/2/svg/1f563.svg")}.twa-nine-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f564.svg")}.twa-clock930{background-image:url("https://twemoji.maxcdn.com/2/svg/1f564.svg")}.twa-ten-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f565.svg")}.twa-clock1030{background-image:url("https://twemoji.maxcdn.com/2/svg/1f565.svg")}.twa-eleven-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f566.svg")}.twa-clock1130{background-image:url("https://twemoji.maxcdn.com/2/svg/1f566.svg")}.twa-twelve-thirty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f567.svg")}.twa-clock1230{background-image:url("https://twemoji.maxcdn.com/2/svg/1f567.svg")}.twa-candle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f56f.svg")}.twa-mantelpiece-clock{background-image:url("https://twemoji.maxcdn.com/2/svg/1f570.svg")}.twa-hole{background-image:url("https://twemoji.maxcdn.com/2/svg/1f573.svg")}.twa-man-in-business-suit-levitating{background-image:url("https://twemoji.maxcdn.com/2/svg/1f574.svg")}.twa-sleuth-or-spy-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-1f3fb.svg")}.twa-sleuth-or-spy-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-1f3fc.svg")}.twa-sleuth-or-spy-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-1f3fd.svg")}.twa-sleuth-or-spy-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-1f3fe.svg")}.twa-sleuth-or-spy-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-1f3ff.svg")}.twa-detective{background-image:url("https://twemoji.maxcdn.com/2/svg/1f575.svg")}.twa-sunglasses{background-image:url("https://twemoji.maxcdn.com/2/svg/1f576.svg")}.twa-spider{background-image:url("https://twemoji.maxcdn.com/2/svg/1f577.svg")}.twa-spider-web{background-image:url("https://twemoji.maxcdn.com/2/svg/1f578.svg")}.twa-joystick{background-image:url("https://twemoji.maxcdn.com/2/svg/1f579.svg")}.twa-man-dancing-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a-1f3fb.svg")}.twa-man-dancing-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a-1f3fc.svg")}.twa-man-dancing-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a-1f3fd.svg")}.twa-man-dancing-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a-1f3fe.svg")}.twa-man-dancing-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a-1f3ff.svg")}.twa-man-dancing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a.svg")}.twa-linked-paperclips{background-image:url("https://twemoji.maxcdn.com/2/svg/1f587.svg")}.twa-pen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f58a.svg")}.twa-fountain-pen{background-image:url("https://twemoji.maxcdn.com/2/svg/1f58b.svg")}.twa-paintbrush{background-image:url("https://twemoji.maxcdn.com/2/svg/1f58c.svg")}.twa-crayon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f58d.svg")}.twa-raised-hand-with-fingers-splayed-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590-1f3fb.svg")}.twa-raised-hand-with-fingers-splayed-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590-1f3fc.svg")}.twa-raised-hand-with-fingers-splayed-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590-1f3fd.svg")}.twa-raised-hand-with-fingers-splayed-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590-1f3fe.svg")}.twa-raised-hand-with-fingers-splayed-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590-1f3ff.svg")}.twa-raised-hand-with-fingers-splayed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f590.svg")}.twa-reversed-hand-with-middle-finger-extended-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595-1f3fb.svg")}.twa-reversed-hand-with-middle-finger-extended-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595-1f3fc.svg")}.twa-reversed-hand-with-middle-finger-extended-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595-1f3fd.svg")}.twa-reversed-hand-with-middle-finger-extended-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595-1f3fe.svg")}.twa-reversed-hand-with-middle-finger-extended-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595-1f3ff.svg")}.twa-middle-finger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f595.svg")}.twa-raised-hand-with-part-between-middle-and-ring-fingers-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596-1f3fb.svg")}.twa-raised-hand-with-part-between-middle-and-ring-fingers-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596-1f3fc.svg")}.twa-raised-hand-with-part-between-middle-and-ring-fingers-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596-1f3fd.svg")}.twa-raised-hand-with-part-between-middle-and-ring-fingers-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596-1f3fe.svg")}.twa-raised-hand-with-part-between-middle-and-ring-fingers-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596-1f3ff.svg")}.twa-vulcan-salute{background-image:url("https://twemoji.maxcdn.com/2/svg/1f596.svg")}.twa-black-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a4.svg")}.twa-desktop-computer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a5.svg")}.twa-printer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a8.svg")}.twa-computer-mouse{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5b1.svg")}.twa-trackball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5b2.svg")}.twa-frame-with-picture{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5bc.svg")}.twa-card-index-dividers{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c2.svg")}.twa-card-file-box{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c3.svg")}.twa-file-cabinet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c4.svg")}.twa-wastebasket{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d1.svg")}.twa-spiral-notepad{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d2.svg")}.twa-spiral-calendar{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d3.svg")}.twa-compression{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5dc.svg")}.twa-old-key{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5dd.svg")}.twa-rolled-up-newspaper{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5de.svg")}.twa-dagger{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e1.svg")}.twa-speaking-head{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e3.svg")}.twa-left-speech-bubble{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e8.svg")}.twa-right-anger-bubble{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5ef.svg")}.twa-ballot-box-with-ballot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5f3.svg")}.twa-world-map{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fa.svg")}.twa-mount-fuji{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fb.svg")}.twa-tokyo-tower{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fc.svg")}.twa-statue-of-liberty{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fd.svg")}.twa-map-of-japan{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fe.svg")}.twa-japan{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fe.svg")}.twa-moai{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5ff.svg")}.twa-moyai{background-image:url("https://twemoji.maxcdn.com/2/svg/1f5ff.svg")}.twa-grinning-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f600.svg")}.twa-grinning{background-image:url("https://twemoji.maxcdn.com/2/svg/1f600.svg")}.twa-grinning-face-with-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f601.svg")}.twa-grin{background-image:url("https://twemoji.maxcdn.com/2/svg/1f601.svg")}.twa-face-with-tears-of-joy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f602.svg")}.twa-joy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f602.svg")}.twa-smiling-face-with-open-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f603.svg")}.twa-smiley{background-image:url("https://twemoji.maxcdn.com/2/svg/1f603.svg")}.twa-smiling-face-with-open-mouth-and-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f604.svg")}.twa-smile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f604.svg")}.twa-smiling-face-with-open-mouth-and-cold-sweat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f605.svg")}.twa-sweat-smile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f605.svg")}.twa-smiling-face-with-open-mouth-and-tightly-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f606.svg")}.twa-laughing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f606.svg")}.twa-smiling-face-with-halo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f607.svg")}.twa-innocent{background-image:url("https://twemoji.maxcdn.com/2/svg/1f607.svg")}.twa-smiling-face-with-horns{background-image:url("https://twemoji.maxcdn.com/2/svg/1f608.svg")}.twa-smiling-imp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f608.svg")}.twa-winking-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f609.svg")}.twa-wink{background-image:url("https://twemoji.maxcdn.com/2/svg/1f609.svg")}.twa-smiling-face-with-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60a.svg")}.twa-blush{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60a.svg")}.twa-face-savouring-delicious-food{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60b.svg")}.twa-yum{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60b.svg")}.twa-relieved-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60c.svg")}.twa-satisfied{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60c.svg")}.twa-smiling-face-with-heart-shaped-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60d.svg")}.twa-heart-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60d.svg")}.twa-smiling-face-with-sunglasses{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60e.svg")}.twa-smirking-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60f.svg")}.twa-smirk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f60f.svg")}.twa-neutral-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f610.svg")}.twa-expressionless-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f611.svg")}.twa-expressionless{background-image:url("https://twemoji.maxcdn.com/2/svg/1f611.svg")}.twa-unamused-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f612.svg")}.twa-unamused{background-image:url("https://twemoji.maxcdn.com/2/svg/1f612.svg")}.twa-face-with-cold-sweat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f613.svg")}.twa-sweat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f613.svg")}.twa-pensive-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f614.svg")}.twa-pensive{background-image:url("https://twemoji.maxcdn.com/2/svg/1f614.svg")}.twa-confused-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f615.svg")}.twa-confused{background-image:url("https://twemoji.maxcdn.com/2/svg/1f615.svg")}.twa-confounded-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f616.svg")}.twa-confounded{background-image:url("https://twemoji.maxcdn.com/2/svg/1f616.svg")}.twa-kissing-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f617.svg")}.twa-kissing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f617.svg")}.twa-face-throwing-a-kiss{background-image:url("https://twemoji.maxcdn.com/2/svg/1f618.svg")}.twa-kissing-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/1f618.svg")}.twa-kissing-face-with-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f619.svg")}.twa-kissing-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f619.svg")}.twa-kissing-face-with-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61a.svg")}.twa-kissing-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61a.svg")}.twa-face-with-stuck-out-tongue{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61b.svg")}.twa-stuck-out-tongue{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61b.svg")}.twa-face-with-stuck-out-tongue-and-winking-eye{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61c.svg")}.twa-stuck-out-tongue-winking-eye{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61c.svg")}.twa-face-with-stuck-out-tongue-and-tightly-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61d.svg")}.twa-stuck-out-tongue-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61d.svg")}.twa-disappointed-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61e.svg")}.twa-disappointed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61e.svg")}.twa-worried-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61f.svg")}.twa-worried{background-image:url("https://twemoji.maxcdn.com/2/svg/1f61f.svg")}.twa-angry-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f620.svg")}.twa-angry{background-image:url("https://twemoji.maxcdn.com/2/svg/1f620.svg")}.twa-pouting-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f621.svg")}.twa-rage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f621.svg")}.twa-crying-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f622.svg")}.twa-cry{background-image:url("https://twemoji.maxcdn.com/2/svg/1f622.svg")}.twa-persevering-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f623.svg")}.twa-persevere{background-image:url("https://twemoji.maxcdn.com/2/svg/1f623.svg")}.twa-face-with-steam-from-nose{background-image:url("https://twemoji.maxcdn.com/2/svg/1f624.svg")}.twa-triumph{background-image:url("https://twemoji.maxcdn.com/2/svg/1f624.svg")}.twa-disappointed-but-relieved-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f625.svg")}.twa-relieved{background-image:url("https://twemoji.maxcdn.com/2/svg/1f625.svg")}.twa-frowning-face-with-open-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f626.svg")}.twa-frowning{background-image:url("https://twemoji.maxcdn.com/2/svg/1f626.svg")}.twa-anguished-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f627.svg")}.twa-anguished{background-image:url("https://twemoji.maxcdn.com/2/svg/1f627.svg")}.twa-fearful-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f628.svg")}.twa-fearful{background-image:url("https://twemoji.maxcdn.com/2/svg/1f628.svg")}.twa-weary-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f629.svg")}.twa-weary{background-image:url("https://twemoji.maxcdn.com/2/svg/1f629.svg")}.twa-sleepy-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62a.svg")}.twa-sleepy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62a.svg")}.twa-tired-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62b.svg")}.twa-grimacing-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62c.svg")}.twa-grimacing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62c.svg")}.twa-loudly-crying-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62d.svg")}.twa-sob{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62d.svg")}.twa-face-with-open-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62e.svg")}.twa-open-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62e.svg")}.twa-hushed-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62f.svg")}.twa-hushed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f62f.svg")}.twa-face-with-open-mouth-and-cold-sweat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f630.svg")}.twa-cold-sweat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f630.svg")}.twa-face-screaming-in-fear{background-image:url("https://twemoji.maxcdn.com/2/svg/1f631.svg")}.twa-scream{background-image:url("https://twemoji.maxcdn.com/2/svg/1f631.svg")}.twa-astonished-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f632.svg")}.twa-astonished{background-image:url("https://twemoji.maxcdn.com/2/svg/1f632.svg")}.twa-flushed-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f633.svg")}.twa-flushed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f633.svg")}.twa-sleeping-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f634.svg")}.twa-sleeping{background-image:url("https://twemoji.maxcdn.com/2/svg/1f634.svg")}.twa-dizzy-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f635.svg")}.twa-face-without-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f636.svg")}.twa-no-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f636.svg")}.twa-face-with-medical-mask{background-image:url("https://twemoji.maxcdn.com/2/svg/1f637.svg")}.twa-mask{background-image:url("https://twemoji.maxcdn.com/2/svg/1f637.svg")}.twa-grinning-cat-face-with-smiling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f638.svg")}.twa-smile-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f638.svg")}.twa-cat-face-with-tears-of-joy{background-image:url("https://twemoji.maxcdn.com/2/svg/1f639.svg")}.twa-joy-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f639.svg")}.twa-smiling-cat-face-with-open-mouth{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63a.svg")}.twa-smiley-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63a.svg")}.twa-smiling-cat-face-with-heart-shaped-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63b.svg")}.twa-heart-eyes-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63b.svg")}.twa-cat-face-with-wry-smile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63c.svg")}.twa-smirk-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63c.svg")}.twa-kissing-cat-face-with-closed-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63d.svg")}.twa-kissing-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63d.svg")}.twa-pouting-cat-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63e.svg")}.twa-pouting-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63e.svg")}.twa-crying-cat-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f63f.svg")}.twa-weary-cat-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f640.svg")}.twa-scream-cat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f640.svg")}.twa-slightly-frowning-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f641.svg")}.twa-slightly-smiling-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f642.svg")}.twa-upside-down-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f643.svg")}.twa-face-with-rolling-eyes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f644.svg")}.twa-face-with-no-good-gesture-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-1f3fb.svg")}.twa-face-with-no-good-gesture-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-1f3fc.svg")}.twa-face-with-no-good-gesture-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-1f3fd.svg")}.twa-face-with-no-good-gesture-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-1f3fe.svg")}.twa-face-with-no-good-gesture-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-1f3ff.svg")}.twa-gesturing-no{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645.svg")}.twa-no-good{background-image:url("https://twemoji.maxcdn.com/2/svg/1f645.svg")}.twa-face-with-ok-gesture-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-1f3fb.svg")}.twa-face-with-ok-gesture-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-1f3fc.svg")}.twa-face-with-ok-gesture-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-1f3fd.svg")}.twa-face-with-ok-gesture-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-1f3fe.svg")}.twa-face-with-ok-gesture-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-1f3ff.svg")}.twa-gesturing-ok{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646.svg")}.twa-ok-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f646.svg")}.twa-person-bowing-deeply-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-1f3fb.svg")}.twa-person-bowing-deeply-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-1f3fc.svg")}.twa-person-bowing-deeply-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-1f3fd.svg")}.twa-person-bowing-deeply-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-1f3fe.svg")}.twa-person-bowing-deeply-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-1f3ff.svg")}.twa-person-bowing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647.svg")}.twa-bow{background-image:url("https://twemoji.maxcdn.com/2/svg/1f647.svg")}.twa-see-no-evil{background-image:url("https://twemoji.maxcdn.com/2/svg/1f648.svg")}.twa-hear-no-evil{background-image:url("https://twemoji.maxcdn.com/2/svg/1f649.svg")}.twa-speak-no-evil{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64a.svg")}.twa-happy-person-raising-one-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-1f3fb.svg")}.twa-happy-person-raising-one-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-1f3fc.svg")}.twa-happy-person-raising-one-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-1f3fd.svg")}.twa-happy-person-raising-one-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-1f3fe.svg")}.twa-happy-person-raising-one-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-1f3ff.svg")}.twa-happy-person-raising-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b.svg")}.twa-happy-person-raised-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b.svg")}.twa-person-raising-both-hands-in-celebration-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c-1f3fb.svg")}.twa-person-raising-both-hands-in-celebration-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c-1f3fc.svg")}.twa-person-raising-both-hands-in-celebration-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c-1f3fd.svg")}.twa-person-raising-both-hands-in-celebration-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c-1f3fe.svg")}.twa-person-raising-both-hands-in-celebration-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c-1f3ff.svg")}.twa-person-raising-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c.svg")}.twa-raised-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c.svg")}.twa-person-frowning-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-1f3fb.svg")}.twa-person-frowning-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-1f3fc.svg")}.twa-person-frowning-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-1f3fd.svg")}.twa-person-frowning-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-1f3fe.svg")}.twa-person-frowning-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-1f3ff.svg")}.twa-person-frowning{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d.svg")}.twa-person-with-pouting-face-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-1f3fb.svg")}.twa-person-with-pouting-face-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-1f3fc.svg")}.twa-person-with-pouting-face-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-1f3fd.svg")}.twa-person-with-pouting-face-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-1f3fe.svg")}.twa-person-with-pouting-face-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-1f3ff.svg")}.twa-person-pouting{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e.svg")}.twa-person-with-pouting-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e.svg")}.twa-person-with-folded-hands-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f-1f3fb.svg")}.twa-person-with-folded-hands-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f-1f3fc.svg")}.twa-person-with-folded-hands-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f-1f3fd.svg")}.twa-person-with-folded-hands-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f-1f3fe.svg")}.twa-person-with-folded-hands-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f-1f3ff.svg")}.twa-folded-hands{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f.svg")}.twa-pray{background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f.svg")}.twa-rocket{background-image:url("https://twemoji.maxcdn.com/2/svg/1f680.svg")}.twa-helicopter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f681.svg")}.twa-locomotive{background-image:url("https://twemoji.maxcdn.com/2/svg/1f682.svg")}.twa-steam-locomotive{background-image:url("https://twemoji.maxcdn.com/2/svg/1f682.svg")}.twa-railway-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f683.svg")}.twa-high-speed-train{background-image:url("https://twemoji.maxcdn.com/2/svg/1f684.svg")}.twa-bullettrain-side{background-image:url("https://twemoji.maxcdn.com/2/svg/1f684.svg")}.twa-high-speed-train-with-bullet-nose{background-image:url("https://twemoji.maxcdn.com/2/svg/1f685.svg")}.twa-bullettrain-front{background-image:url("https://twemoji.maxcdn.com/2/svg/1f685.svg")}.twa-train{background-image:url("https://twemoji.maxcdn.com/2/svg/1f686.svg")}.twa-train2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f686.svg")}.twa-metro{background-image:url("https://twemoji.maxcdn.com/2/svg/1f687.svg")}.twa-light-rail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f688.svg")}.twa-station{background-image:url("https://twemoji.maxcdn.com/2/svg/1f689.svg")}.twa-tram{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68a.svg")}.twa-tram-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68b.svg")}.twa-bus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68c.svg")}.twa-oncoming-bus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68d.svg")}.twa-trolleybus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68e.svg")}.twa-bus-stop{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68f.svg")}.twa-busstop{background-image:url("https://twemoji.maxcdn.com/2/svg/1f68f.svg")}.twa-minibus{background-image:url("https://twemoji.maxcdn.com/2/svg/1f690.svg")}.twa-ambulance{background-image:url("https://twemoji.maxcdn.com/2/svg/1f691.svg")}.twa-fire-engine{background-image:url("https://twemoji.maxcdn.com/2/svg/1f692.svg")}.twa-police-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f693.svg")}.twa-oncoming-police-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f694.svg")}.twa-taxi{background-image:url("https://twemoji.maxcdn.com/2/svg/1f695.svg")}.twa-oncoming-taxi{background-image:url("https://twemoji.maxcdn.com/2/svg/1f696.svg")}.twa-automobile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f697.svg")}.twa-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f697.svg")}.twa-oncoming-automobile{background-image:url("https://twemoji.maxcdn.com/2/svg/1f698.svg")}.twa-recreational-vehicle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f699.svg")}.twa-blue-car{background-image:url("https://twemoji.maxcdn.com/2/svg/1f699.svg")}.twa-delivery-truck{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69a.svg")}.twa-truck{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69a.svg")}.twa-articulated-lorry{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69b.svg")}.twa-tractor{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69c.svg")}.twa-monorail{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69d.svg")}.twa-mountain-railway{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69e.svg")}.twa-suspension-railway{background-image:url("https://twemoji.maxcdn.com/2/svg/1f69f.svg")}.twa-mountain-cableway{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a0.svg")}.twa-aerial-tramway{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a1.svg")}.twa-ship{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a2.svg")}.twa-rowboat-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-1f3fb.svg")}.twa-rowboat-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-1f3fc.svg")}.twa-rowboat-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-1f3fd.svg")}.twa-rowboat-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-1f3fe.svg")}.twa-rowboat-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-1f3ff.svg")}.twa-rowboat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3.svg")}.twa-speedboat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a4.svg")}.twa-horizontal-traffic-light{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a5.svg")}.twa-traffic-light{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a5.svg")}.twa-vertical-traffic-light{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a6.svg")}.twa-construction{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a7.svg")}.twa-police-cars-light{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a8.svg")}.twa-rotating-light{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a8.svg")}.twa-triangular-flag{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a9.svg")}.twa-triangular-flag-on-post{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a9.svg")}.twa-door{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6aa.svg")}.twa-prohibited{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ab.svg")}.twa-no-entry-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ab.svg")}.twa-smoking{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ac.svg")}.twa-no-smoking{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ad.svg")}.twa-litter-in-bin-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ae.svg")}.twa-put-litter-in-its-place{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ae.svg")}.twa-no-littering{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6af.svg")}.twa-do-not-litter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6af.svg")}.twa-potable-water{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b0.svg")}.twa-non-potable-water{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b1.svg")}.twa-bicycle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b2.svg")}.twa-bike{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b2.svg")}.twa-no-bicycles{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b3.svg")}.twa-bicyclist-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-1f3fb.svg")}.twa-bicyclist-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-1f3fc.svg")}.twa-bicyclist-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-1f3fd.svg")}.twa-bicyclist-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-1f3fe.svg")}.twa-bicyclist-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-1f3ff.svg")}.twa-bicyclist{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4.svg")}.twa-mountain-bicyclist-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-1f3fb.svg")}.twa-mountain-bicyclist-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-1f3fc.svg")}.twa-mountain-bicyclist-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-1f3fd.svg")}.twa-mountain-bicyclist-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-1f3fe.svg")}.twa-mountain-bicyclist-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-1f3ff.svg")}.twa-mountain-biker{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5.svg")}.twa-mountain-bicyclist{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5.svg")}.twa-pedestrian-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-1f3fb.svg")}.twa-pedestrian-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-1f3fc.svg")}.twa-pedestrian-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-1f3fd.svg")}.twa-pedestrian-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-1f3fe.svg")}.twa-pedestrian-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-1f3ff.svg")}.twa-pedestrian{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6.svg")}.twa-walking{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6.svg")}.twa-no-pedestrians{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b7.svg")}.twa-children-crossing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b8.svg")}.twa-mens-room{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b9.svg")}.twa-mens{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b9.svg")}.twa-womens-room{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ba.svg")}.twa-womens{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ba.svg")}.twa-restroom{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bb.svg")}.twa-baby-symbol{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bc.svg")}.twa-toilet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bd.svg")}.twa-water-closet{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6be.svg")}.twa-wc{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6be.svg")}.twa-shower{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bf.svg")}.twa-bath-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0-1f3fb.svg")}.twa-bath-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0-1f3fc.svg")}.twa-bath-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0-1f3fd.svg")}.twa-bath-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0-1f3fe.svg")}.twa-bath-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0-1f3ff.svg")}.twa-person-taking-bath{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0.svg")}.twa-bath{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0.svg")}.twa-bathtub{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c1.svg")}.twa-passport-control{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c2.svg")}.twa-customs{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c3.svg")}.twa-baggage-claim{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c4.svg")}.twa-left-luggage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c5.svg")}.twa-couch-and-lamp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cb.svg")}.twa-person-in-bed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cc.svg")}.twa-shopping-bags{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cd.svg")}.twa-bellhop-bell{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ce.svg")}.twa-bed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cf.svg")}.twa-place-of-worship{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d0.svg")}.twa-octagonal-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d1.svg")}.twa-shopping-trolley{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d2.svg")}.twa-hammer-and-wrench{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e0.svg")}.twa-shield{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e1.svg")}.twa-oil-drum{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e2.svg")}.twa-motorway{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e3.svg")}.twa-railway-track{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e4.svg")}.twa-motor-boat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e5.svg")}.twa-small-airplane{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e9.svg")}.twa-airplane-departure{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6eb.svg")}.twa-airplane-arrival{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ec.svg")}.twa-satellite{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f0.svg")}.twa-passenger-ship{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f3.svg")}.twa-scooter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f4.svg")}.twa-motor-scooter{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f5.svg")}.twa-canoe{background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f6.svg")}.twa-zipper-mouth-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f910.svg")}.twa-money-mouth-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f911.svg")}.twa-face-with-thermometer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f912.svg")}.twa-nerd-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f913.svg")}.twa-thinking-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f914.svg")}.twa-face-with-head-bandage{background-image:url("https://twemoji.maxcdn.com/2/svg/1f915.svg")}.twa-robot-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f916.svg")}.twa-hugging-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f917.svg")}.twa-sign-of-the-horns-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918-1f3fb.svg")}.twa-sign-of-the-horns-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918-1f3fc.svg")}.twa-sign-of-the-horns-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918-1f3fd.svg")}.twa-sign-of-the-horns-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918-1f3fe.svg")}.twa-sign-of-the-horns-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918-1f3ff.svg")}.twa-sign-of-the-horns{background-image:url("https://twemoji.maxcdn.com/2/svg/1f918.svg")}.twa-call-me-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919-1f3fb.svg")}.twa-call-me-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919-1f3fc.svg")}.twa-call-me-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919-1f3fd.svg")}.twa-call-me-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919-1f3fe.svg")}.twa-call-me-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919-1f3ff.svg")}.twa-call-me-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f919.svg")}.twa-raised-back-of-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a-1f3fb.svg")}.twa-raised-back-of-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a-1f3fc.svg")}.twa-raised-back-of-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a-1f3fd.svg")}.twa-raised-back-of-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a-1f3fe.svg")}.twa-raised-back-of-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a-1f3ff.svg")}.twa-raised-back-of-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a.svg")}.twa-left-facing-fist-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b-1f3fb.svg")}.twa-left-facing-fist-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b-1f3fc.svg")}.twa-left-facing-fist-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b-1f3fd.svg")}.twa-left-facing-fist-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b-1f3fe.svg")}.twa-left-facing-fist-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b-1f3ff.svg")}.twa-left-facing-fist{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b.svg")}.twa-right-facing-fist-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c-1f3fb.svg")}.twa-right-facing-fist-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c-1f3fc.svg")}.twa-right-facing-fist-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c-1f3fd.svg")}.twa-right-facing-fist-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c-1f3fe.svg")}.twa-right-facing-fist-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c-1f3ff.svg")}.twa-right-facing-fist{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c.svg")}.twa-handshake-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d-1f3fb.svg")}.twa-handshake-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d-1f3fc.svg")}.twa-handshake-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d-1f3fd.svg")}.twa-handshake-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d-1f3fe.svg")}.twa-handshake-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d-1f3ff.svg")}.twa-handshake{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d.svg")}.twa-hand-with-index-and-middle-fingers-crossed-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e-1f3fb.svg")}.twa-hand-with-index-and-middle-fingers-crossed-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e-1f3fc.svg")}.twa-hand-with-index-and-middle-fingers-crossed-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e-1f3fd.svg")}.twa-hand-with-index-and-middle-fingers-crossed-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e-1f3fe.svg")}.twa-hand-with-index-and-middle-fingers-crossed-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e-1f3ff.svg")}.twa-hand-with-index-and-middle-fingers-crossed{background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e.svg")}.twa-face-with-cowboy-hat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f920.svg")}.twa-clown-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f921.svg")}.twa-nauseated-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f922.svg")}.twa-rolling-on-the-floor-laughing{background-image:url("https://twemoji.maxcdn.com/2/svg/1f923.svg")}.twa-drooling-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f924.svg")}.twa-lying-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f925.svg")}.twa-face-palm-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-1f3fb.svg")}.twa-face-palm-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-1f3fc.svg")}.twa-face-palm-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-1f3fd.svg")}.twa-face-palm-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-1f3fe.svg")}.twa-face-palm-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-1f3ff.svg")}.twa-face-palm{background-image:url("https://twemoji.maxcdn.com/2/svg/1f926.svg")}.twa-sneezing-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f927.svg")}.twa-pregnant-woman-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930-1f3fb.svg")}.twa-pregnant-woman-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930-1f3fc.svg")}.twa-pregnant-woman-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930-1f3fd.svg")}.twa-pregnant-woman-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930-1f3fe.svg")}.twa-pregnant-woman-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930-1f3ff.svg")}.twa-pregnant-woman{background-image:url("https://twemoji.maxcdn.com/2/svg/1f930.svg")}.twa-selfie-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933-1f3fb.svg")}.twa-selfie-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933-1f3fc.svg")}.twa-selfie-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933-1f3fd.svg")}.twa-selfie-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933-1f3fe.svg")}.twa-selfie-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933-1f3ff.svg")}.twa-selfie{background-image:url("https://twemoji.maxcdn.com/2/svg/1f933.svg")}.twa-prince-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934-1f3fb.svg")}.twa-prince-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934-1f3fc.svg")}.twa-prince-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934-1f3fd.svg")}.twa-prince-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934-1f3fe.svg")}.twa-prince-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934-1f3ff.svg")}.twa-prince{background-image:url("https://twemoji.maxcdn.com/2/svg/1f934.svg")}.twa-man-in-tuxedo-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935-1f3fb.svg")}.twa-man-in-tuxedo-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935-1f3fc.svg")}.twa-man-in-tuxedo-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935-1f3fd.svg")}.twa-man-in-tuxedo-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935-1f3fe.svg")}.twa-man-in-tuxedo-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935-1f3ff.svg")}.twa-man-in-tuxedo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f935.svg")}.twa-mother-christmas-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936-1f3fb.svg")}.twa-mother-christmas-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936-1f3fc.svg")}.twa-mother-christmas-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936-1f3fd.svg")}.twa-mother-christmas-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936-1f3fe.svg")}.twa-mother-christmas-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936-1f3ff.svg")}.twa-mother-christmas{background-image:url("https://twemoji.maxcdn.com/2/svg/1f936.svg")}.twa-shrug-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-1f3fb.svg")}.twa-shrug-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-1f3fc.svg")}.twa-shrug-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-1f3fd.svg")}.twa-shrug-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-1f3fe.svg")}.twa-shrug-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-1f3ff.svg")}.twa-shrug{background-image:url("https://twemoji.maxcdn.com/2/svg/1f937.svg")}.twa-person-doing-cartwheel-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-1f3fb.svg")}.twa-person-doing-cartwheel-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-1f3fc.svg")}.twa-person-doing-cartwheel-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-1f3fd.svg")}.twa-person-doing-cartwheel-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-1f3fe.svg")}.twa-person-doing-cartwheel-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-1f3ff.svg")}.twa-person-doing-cartwheel{background-image:url("https://twemoji.maxcdn.com/2/svg/1f938.svg")}.twa-juggling-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-1f3fb.svg")}.twa-juggling-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-1f3fc.svg")}.twa-juggling-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-1f3fd.svg")}.twa-juggling-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-1f3fe.svg")}.twa-juggling-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-1f3ff.svg")}.twa-juggling{background-image:url("https://twemoji.maxcdn.com/2/svg/1f939.svg")}.twa-fencer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93a.svg")}.twa-wrestlers-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-1f3fb.svg")}.twa-wrestlers-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-1f3fc.svg")}.twa-wrestlers-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-1f3fd.svg")}.twa-wrestlers-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-1f3fe.svg")}.twa-wrestlers-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-1f3ff.svg")}.twa-wrestlers{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c.svg")}.twa-water-polo-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-1f3fb.svg")}.twa-water-polo-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-1f3fc.svg")}.twa-water-polo-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-1f3fd.svg")}.twa-water-polo-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-1f3fe.svg")}.twa-water-polo-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-1f3ff.svg")}.twa-water-polo{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d.svg")}.twa-handball-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-1f3fb.svg")}.twa-handball-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-1f3fc.svg")}.twa-handball-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-1f3fd.svg")}.twa-handball-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-1f3fe.svg")}.twa-handball-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-1f3ff.svg")}.twa-handball{background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e.svg")}.twa-wilted-flower{background-image:url("https://twemoji.maxcdn.com/2/svg/1f940.svg")}.twa-drum-with-drumsticks{background-image:url("https://twemoji.maxcdn.com/2/svg/1f941.svg")}.twa-clinking-glasses{background-image:url("https://twemoji.maxcdn.com/2/svg/1f942.svg")}.twa-tumbler-glass{background-image:url("https://twemoji.maxcdn.com/2/svg/1f943.svg")}.twa-spoon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f944.svg")}.twa-goal-net{background-image:url("https://twemoji.maxcdn.com/2/svg/1f945.svg")}.twa-first-place-medal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f947.svg")}.twa-second-place-medal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f948.svg")}.twa-third-place-medal{background-image:url("https://twemoji.maxcdn.com/2/svg/1f949.svg")}.twa-boxing-glove{background-image:url("https://twemoji.maxcdn.com/2/svg/1f94a.svg")}.twa-martial-arts-uniform{background-image:url("https://twemoji.maxcdn.com/2/svg/1f94b.svg")}.twa-croissant{background-image:url("https://twemoji.maxcdn.com/2/svg/1f950.svg")}.twa-avocado{background-image:url("https://twemoji.maxcdn.com/2/svg/1f951.svg")}.twa-cucumber{background-image:url("https://twemoji.maxcdn.com/2/svg/1f952.svg")}.twa-bacon{background-image:url("https://twemoji.maxcdn.com/2/svg/1f953.svg")}.twa-potato{background-image:url("https://twemoji.maxcdn.com/2/svg/1f954.svg")}.twa-carrot{background-image:url("https://twemoji.maxcdn.com/2/svg/1f955.svg")}.twa-baguette-bread{background-image:url("https://twemoji.maxcdn.com/2/svg/1f956.svg")}.twa-green-salad{background-image:url("https://twemoji.maxcdn.com/2/svg/1f957.svg")}.twa-shallow-pan-of-food{background-image:url("https://twemoji.maxcdn.com/2/svg/1f958.svg")}.twa-stuffed-flatbread{background-image:url("https://twemoji.maxcdn.com/2/svg/1f959.svg")}.twa-egg{background-image:url("https://twemoji.maxcdn.com/2/svg/1f95a.svg")}.twa-glass-of-milk{background-image:url("https://twemoji.maxcdn.com/2/svg/1f95b.svg")}.twa-peanuts{background-image:url("https://twemoji.maxcdn.com/2/svg/1f95c.svg")}.twa-kiwifruit{background-image:url("https://twemoji.maxcdn.com/2/svg/1f95d.svg")}.twa-pancakes{background-image:url("https://twemoji.maxcdn.com/2/svg/1f95e.svg")}.twa-crab{background-image:url("https://twemoji.maxcdn.com/2/svg/1f980.svg")}.twa-lion-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f981.svg")}.twa-scorpion{background-image:url("https://twemoji.maxcdn.com/2/svg/1f982.svg")}.twa-turkey{background-image:url("https://twemoji.maxcdn.com/2/svg/1f983.svg")}.twa-unicorn-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f984.svg")}.twa-eagle{background-image:url("https://twemoji.maxcdn.com/2/svg/1f985.svg")}.twa-duck{background-image:url("https://twemoji.maxcdn.com/2/svg/1f986.svg")}.twa-bat{background-image:url("https://twemoji.maxcdn.com/2/svg/1f987.svg")}.twa-shark{background-image:url("https://twemoji.maxcdn.com/2/svg/1f988.svg")}.twa-owl{background-image:url("https://twemoji.maxcdn.com/2/svg/1f989.svg")}.twa-fox-face{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98a.svg")}.twa-butterfly{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98b.svg")}.twa-deer{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98c.svg")}.twa-gorilla{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98d.svg")}.twa-lizard{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98e.svg")}.twa-rhinoceros{background-image:url("https://twemoji.maxcdn.com/2/svg/1f98f.svg")}.twa-shrimp{background-image:url("https://twemoji.maxcdn.com/2/svg/1f990.svg")}.twa-squid{background-image:url("https://twemoji.maxcdn.com/2/svg/1f991.svg")}.twa-cheese-wedge{background-image:url("https://twemoji.maxcdn.com/2/svg/1f9c0.svg")}.twa-double-exclamation-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/203c.svg")}.twa-bangbang{background-image:url("https://twemoji.maxcdn.com/2/svg/203c.svg")}.twa-exclamation-question-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2049.svg")}.twa-interrobang{background-image:url("https://twemoji.maxcdn.com/2/svg/2049.svg")}.twa-trade-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2122.svg")}.twa-tm{background-image:url("https://twemoji.maxcdn.com/2/svg/2122.svg")}.twa-information-source{background-image:url("https://twemoji.maxcdn.com/2/svg/2139.svg")}.twa-left-right-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2194.svg")}.twa-up-down-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2195.svg")}.twa-arrow-up-down{background-image:url("https://twemoji.maxcdn.com/2/svg/2195.svg")}.twa-up-left-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2196.svg")}.twa-arrow-upper-left{background-image:url("https://twemoji.maxcdn.com/2/svg/2196.svg")}.twa-up-right-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2197.svg")}.twa-arrow-upper-right{background-image:url("https://twemoji.maxcdn.com/2/svg/2197.svg")}.twa-down-right-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2198.svg")}.twa-arrow-lower-right{background-image:url("https://twemoji.maxcdn.com/2/svg/2198.svg")}.twa-down-left-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2199.svg")}.twa-arrow-lower-left{background-image:url("https://twemoji.maxcdn.com/2/svg/2199.svg")}.twa-right-arrow-curving-left{background-image:url("https://twemoji.maxcdn.com/2/svg/21a9.svg")}.twa-leftwards-arrow-with-hook{background-image:url("https://twemoji.maxcdn.com/2/svg/21a9.svg")}.twa-left-arrow-curving-right{background-image:url("https://twemoji.maxcdn.com/2/svg/21aa.svg")}.twa-arrow-right-hook{background-image:url("https://twemoji.maxcdn.com/2/svg/21aa.svg")}.twa-hash{background-image:url("https://twemoji.maxcdn.com/2/svg/23-20e3.svg")}.twa-watch{background-image:url("https://twemoji.maxcdn.com/2/svg/231a.svg")}.twa-hourglass{background-image:url("https://twemoji.maxcdn.com/2/svg/231b.svg")}.twa-keyboard{background-image:url("https://twemoji.maxcdn.com/2/svg/2328.svg")}.twa-eject-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23cf.svg")}.twa-fast-forword-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23e9.svg")}.twa-fast-forward{background-image:url("https://twemoji.maxcdn.com/2/svg/23e9.svg")}.twa-fast-reverse-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23ea.svg")}.twa-rewind{background-image:url("https://twemoji.maxcdn.com/2/svg/23ea.svg")}.twa-fast-up-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23eb.svg")}.twa-arrow-double-up{background-image:url("https://twemoji.maxcdn.com/2/svg/23eb.svg")}.twa-fast-down-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23ec.svg")}.twa-arrow-double-down{background-image:url("https://twemoji.maxcdn.com/2/svg/23ec.svg")}.twa-next-track-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23ed.svg")}.twa-last-track-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23ee.svg")}.twa-play-or-pause-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23ef.svg")}.twa-alarm-clock{background-image:url("https://twemoji.maxcdn.com/2/svg/23f0.svg")}.twa-stopwatch{background-image:url("https://twemoji.maxcdn.com/2/svg/23f1.svg")}.twa-timer-clock{background-image:url("https://twemoji.maxcdn.com/2/svg/23f2.svg")}.twa-hourglass-with-flowing-sand{background-image:url("https://twemoji.maxcdn.com/2/svg/23f3.svg")}.twa-hourglass-flowing-sand{background-image:url("https://twemoji.maxcdn.com/2/svg/23f3.svg")}.twa-pause-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23f8.svg")}.twa-stop-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23f9.svg")}.twa-record-button{background-image:url("https://twemoji.maxcdn.com/2/svg/23fa.svg")}.twa-circled-letter-m{background-image:url("https://twemoji.maxcdn.com/2/svg/24c2.svg")}.twa-m{background-image:url("https://twemoji.maxcdn.com/2/svg/24c2.svg")}.twa-black-small-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25aa.svg")}.twa-white-small-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25ab.svg")}.twa-play-button{background-image:url("https://twemoji.maxcdn.com/2/svg/25b6.svg")}.twa-arrow-forward{background-image:url("https://twemoji.maxcdn.com/2/svg/25b6.svg")}.twa-reverse-button{background-image:url("https://twemoji.maxcdn.com/2/svg/25c0.svg")}.twa-arrow-backward{background-image:url("https://twemoji.maxcdn.com/2/svg/25c0.svg")}.twa-white-medium-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25fb.svg")}.twa-black-medium-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25fc.svg")}.twa-white-medium-small-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25fd.svg")}.twa-black-medium-small-square{background-image:url("https://twemoji.maxcdn.com/2/svg/25fe.svg")}.twa-sun{background-image:url("https://twemoji.maxcdn.com/2/svg/2600.svg")}.twa-sunny{background-image:url("https://twemoji.maxcdn.com/2/svg/2600.svg")}.twa-cloud{background-image:url("https://twemoji.maxcdn.com/2/svg/2601.svg")}.twa-umbrella{background-image:url("https://twemoji.maxcdn.com/2/svg/2602.svg")}.twa-snowman{background-image:url("https://twemoji.maxcdn.com/2/svg/2603.svg")}.twa-comet{background-image:url("https://twemoji.maxcdn.com/2/svg/2604.svg")}.twa-telephone{background-image:url("https://twemoji.maxcdn.com/2/svg/260e.svg")}.twa-phone{background-image:url("https://twemoji.maxcdn.com/2/svg/260e.svg")}.twa-ballot-box-with-check{background-image:url("https://twemoji.maxcdn.com/2/svg/2611.svg")}.twa-umbrella-with-rain-drops{background-image:url("https://twemoji.maxcdn.com/2/svg/2614.svg")}.twa-hot-beverage{background-image:url("https://twemoji.maxcdn.com/2/svg/2615.svg")}.twa-coffee{background-image:url("https://twemoji.maxcdn.com/2/svg/2615.svg")}.twa-shamrock{background-image:url("https://twemoji.maxcdn.com/2/svg/2618.svg")}.twa-white-up-pointing-index-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/261d-1f3fb.svg")}.twa-white-up-pointing-index-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/261d-1f3fc.svg")}.twa-white-up-pointing-index-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/261d-1f3fd.svg")}.twa-white-up-pointing-index-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/261d-1f3fe.svg")}.twa-white-up-pointing-index-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/261d-1f3ff.svg")}.twa-index-pointing-up{background-image:url("https://twemoji.maxcdn.com/2/svg/261d.svg")}.twa-point-up{background-image:url("https://twemoji.maxcdn.com/2/svg/261d.svg")}.twa-skull-and-crossbones{background-image:url("https://twemoji.maxcdn.com/2/svg/2620.svg")}.twa-radioactive{background-image:url("https://twemoji.maxcdn.com/2/svg/2622.svg")}.twa-biohazard{background-image:url("https://twemoji.maxcdn.com/2/svg/2623.svg")}.twa-orthodox-cross{background-image:url("https://twemoji.maxcdn.com/2/svg/2626.svg")}.twa-star-and-crescent{background-image:url("https://twemoji.maxcdn.com/2/svg/262a.svg")}.twa-peace-symbol{background-image:url("https://twemoji.maxcdn.com/2/svg/262e.svg")}.twa-yin-yang{background-image:url("https://twemoji.maxcdn.com/2/svg/262f.svg")}.twa-wheel-of-dharma{background-image:url("https://twemoji.maxcdn.com/2/svg/2638.svg")}.twa-frowning-face{background-image:url("https://twemoji.maxcdn.com/2/svg/2639.svg")}.twa-smiling-face{background-image:url("https://twemoji.maxcdn.com/2/svg/263a.svg")}.twa-relaxed{background-image:url("https://twemoji.maxcdn.com/2/svg/263a.svg")}.twa-aries{background-image:url("https://twemoji.maxcdn.com/2/svg/2648.svg")}.twa-taurus{background-image:url("https://twemoji.maxcdn.com/2/svg/2649.svg")}.twa-gemini{background-image:url("https://twemoji.maxcdn.com/2/svg/264a.svg")}.twa-cancer{background-image:url("https://twemoji.maxcdn.com/2/svg/264b.svg")}.twa-leo{background-image:url("https://twemoji.maxcdn.com/2/svg/264c.svg")}.twa-virgo{background-image:url("https://twemoji.maxcdn.com/2/svg/264d.svg")}.twa-libra{background-image:url("https://twemoji.maxcdn.com/2/svg/264e.svg")}.twa-scorpius{background-image:url("https://twemoji.maxcdn.com/2/svg/264f.svg")}.twa-sagittarius{background-image:url("https://twemoji.maxcdn.com/2/svg/2650.svg")}.twa-capricorn{background-image:url("https://twemoji.maxcdn.com/2/svg/2651.svg")}.twa-aquarius{background-image:url("https://twemoji.maxcdn.com/2/svg/2652.svg")}.twa-pisces{background-image:url("https://twemoji.maxcdn.com/2/svg/2653.svg")}.twa-spade-suit{background-image:url("https://twemoji.maxcdn.com/2/svg/2660.svg")}.twa-spades{background-image:url("https://twemoji.maxcdn.com/2/svg/2660.svg")}.twa-club-suit{background-image:url("https://twemoji.maxcdn.com/2/svg/2663.svg")}.twa-clubs{background-image:url("https://twemoji.maxcdn.com/2/svg/2663.svg")}.twa-heart-suit{background-image:url("https://twemoji.maxcdn.com/2/svg/2665.svg")}.twa-hearts{background-image:url("https://twemoji.maxcdn.com/2/svg/2665.svg")}.twa-diamond-suit{background-image:url("https://twemoji.maxcdn.com/2/svg/2666.svg")}.twa-diamonds{background-image:url("https://twemoji.maxcdn.com/2/svg/2666.svg")}.twa-hot-springs{background-image:url("https://twemoji.maxcdn.com/2/svg/2668.svg")}.twa-hotsprings{background-image:url("https://twemoji.maxcdn.com/2/svg/2668.svg")}.twa-recycling-symbol{background-image:url("https://twemoji.maxcdn.com/2/svg/267b.svg")}.twa-recycle{background-image:url("https://twemoji.maxcdn.com/2/svg/267b.svg")}.twa-wheelchair{background-image:url("https://twemoji.maxcdn.com/2/svg/267f.svg")}.twa-hammer-and-pick{background-image:url("https://twemoji.maxcdn.com/2/svg/2692.svg")}.twa-anchor{background-image:url("https://twemoji.maxcdn.com/2/svg/2693.svg")}.twa-crossed-swords{background-image:url("https://twemoji.maxcdn.com/2/svg/2694.svg")}.twa-balance-scale{background-image:url("https://twemoji.maxcdn.com/2/svg/2696.svg")}.twa-alembic{background-image:url("https://twemoji.maxcdn.com/2/svg/2697.svg")}.twa-gear{background-image:url("https://twemoji.maxcdn.com/2/svg/2699.svg")}.twa-atom-symbol{background-image:url("https://twemoji.maxcdn.com/2/svg/269b.svg")}.twa-fleur-de-lis{background-image:url("https://twemoji.maxcdn.com/2/svg/269c.svg")}.twa-warning{background-image:url("https://twemoji.maxcdn.com/2/svg/26a0.svg")}.twa-high-voltage{background-image:url("https://twemoji.maxcdn.com/2/svg/26a1.svg")}.twa-zap{background-image:url("https://twemoji.maxcdn.com/2/svg/26a1.svg")}.twa-white-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/26aa.svg")}.twa-black-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/26ab.svg")}.twa-coffin{background-image:url("https://twemoji.maxcdn.com/2/svg/26b0.svg")}.twa-funeral-urn{background-image:url("https://twemoji.maxcdn.com/2/svg/26b1.svg")}.twa-soccer-ball{background-image:url("https://twemoji.maxcdn.com/2/svg/26bd.svg")}.twa-soccer{background-image:url("https://twemoji.maxcdn.com/2/svg/26bd.svg")}.twa-baseball{background-image:url("https://twemoji.maxcdn.com/2/svg/26be.svg")}.twa-snowman-without-snow{background-image:url("https://twemoji.maxcdn.com/2/svg/26c4.svg")}.twa-sun-behind-cloud{background-image:url("https://twemoji.maxcdn.com/2/svg/26c5.svg")}.twa-partly-sunny{background-image:url("https://twemoji.maxcdn.com/2/svg/26c5.svg")}.twa-cloud-with-lightning-and-rain{background-image:url("https://twemoji.maxcdn.com/2/svg/26c8.svg")}.twa-ophiuchus{background-image:url("https://twemoji.maxcdn.com/2/svg/26ce.svg")}.twa-pick{background-image:url("https://twemoji.maxcdn.com/2/svg/26cf.svg")}.twa-helmet-with-white-cross{background-image:url("https://twemoji.maxcdn.com/2/svg/26d1.svg")}.twa-chains{background-image:url("https://twemoji.maxcdn.com/2/svg/26d3.svg")}.twa-no-entry{background-image:url("https://twemoji.maxcdn.com/2/svg/26d4.svg")}.twa-shinto-shrine{background-image:url("https://twemoji.maxcdn.com/2/svg/26e9.svg")}.twa-church{background-image:url("https://twemoji.maxcdn.com/2/svg/26ea.svg")}.twa-mountain{background-image:url("https://twemoji.maxcdn.com/2/svg/26f0.svg")}.twa-umbrella-on-ground{background-image:url("https://twemoji.maxcdn.com/2/svg/26f1.svg")}.twa-fountain{background-image:url("https://twemoji.maxcdn.com/2/svg/26f2.svg")}.twa-flag-in-hole{background-image:url("https://twemoji.maxcdn.com/2/svg/26f3.svg")}.twa-golf{background-image:url("https://twemoji.maxcdn.com/2/svg/26f3.svg")}.twa-ferry{background-image:url("https://twemoji.maxcdn.com/2/svg/26f4.svg")}.twa-sailboat{background-image:url("https://twemoji.maxcdn.com/2/svg/26f5.svg")}.twa-boat{background-image:url("https://twemoji.maxcdn.com/2/svg/26f5.svg")}.twa-skier{background-image:url("https://twemoji.maxcdn.com/2/svg/26f7.svg")}.twa-ice-skate{background-image:url("https://twemoji.maxcdn.com/2/svg/26f8.svg")}.twa-person-with-ball-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-1f3fb.svg")}.twa-person-with-ball-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-1f3fc.svg")}.twa-person-with-ball-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-1f3fd.svg")}.twa-person-with-ball-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-1f3fe.svg")}.twa-person-with-ball-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-1f3ff.svg")}.twa-person-with-ball{background-image:url("https://twemoji.maxcdn.com/2/svg/26f9.svg")}.twa-tent{background-image:url("https://twemoji.maxcdn.com/2/svg/26fa.svg")}.twa-fuel-pump{background-image:url("https://twemoji.maxcdn.com/2/svg/26fd.svg")}.twa-fuelpump{background-image:url("https://twemoji.maxcdn.com/2/svg/26fd.svg")}.twa-scissors{background-image:url("https://twemoji.maxcdn.com/2/svg/2702.svg")}.twa-white-heavy-check-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2705.svg")}.twa-white-check-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2705.svg")}.twa-airplane{background-image:url("https://twemoji.maxcdn.com/2/svg/2708.svg")}.twa-envelope{background-image:url("https://twemoji.maxcdn.com/2/svg/2709.svg")}.twa-email{background-image:url("https://twemoji.maxcdn.com/2/svg/2709.svg")}.twa-raised-fist-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/270a-1f3fb.svg")}.twa-raised-fist-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/270a-1f3fc.svg")}.twa-raised-fist-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/270a-1f3fd.svg")}.twa-raised-fist-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/270a-1f3fe.svg")}.twa-raised-fist-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/270a-1f3ff.svg")}.twa-raised-fist{background-image:url("https://twemoji.maxcdn.com/2/svg/270a.svg")}.twa-fist{background-image:url("https://twemoji.maxcdn.com/2/svg/270a.svg")}.twa-raised-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/270b-1f3fb.svg")}.twa-raised-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/270b-1f3fc.svg")}.twa-raised-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/270b-1f3fd.svg")}.twa-raised-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/270b-1f3fe.svg")}.twa-raised-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/270b-1f3ff.svg")}.twa-raised-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/270b.svg")}.twa-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/270b.svg")}.twa-victory-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/270c-1f3fb.svg")}.twa-victory-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/270c-1f3fc.svg")}.twa-victory-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/270c-1f3fd.svg")}.twa-victory-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/270c-1f3fe.svg")}.twa-victory-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/270c-1f3ff.svg")}.twa-victory-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/270c.svg")}.twa-v{background-image:url("https://twemoji.maxcdn.com/2/svg/270c.svg")}.twa-writing-hand-type-1-2{background-image:url("https://twemoji.maxcdn.com/2/svg/270d-1f3fb.svg")}.twa-writing-hand-type-3{background-image:url("https://twemoji.maxcdn.com/2/svg/270d-1f3fc.svg")}.twa-writing-hand-type-4{background-image:url("https://twemoji.maxcdn.com/2/svg/270d-1f3fd.svg")}.twa-writing-hand-type-5{background-image:url("https://twemoji.maxcdn.com/2/svg/270d-1f3fe.svg")}.twa-writing-hand-type-6{background-image:url("https://twemoji.maxcdn.com/2/svg/270d-1f3ff.svg")}.twa-writing-hand{background-image:url("https://twemoji.maxcdn.com/2/svg/270d.svg")}.twa-pencil{background-image:url("https://twemoji.maxcdn.com/2/svg/270f.svg")}.twa-pencil2{background-image:url("https://twemoji.maxcdn.com/2/svg/270f.svg")}.twa-black-nib{background-image:url("https://twemoji.maxcdn.com/2/svg/2712.svg")}.twa-heavy-check-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2714.svg")}.twa-heavy-multiplication-x{background-image:url("https://twemoji.maxcdn.com/2/svg/2716.svg")}.twa-latin-cross{background-image:url("https://twemoji.maxcdn.com/2/svg/271d.svg")}.twa-star-of-david{background-image:url("https://twemoji.maxcdn.com/2/svg/2721.svg")}.twa-sparkles{background-image:url("https://twemoji.maxcdn.com/2/svg/2728.svg")}.twa-eight-spoked-asterisk{background-image:url("https://twemoji.maxcdn.com/2/svg/2733.svg")}.twa-eight-pointed-star{background-image:url("https://twemoji.maxcdn.com/2/svg/2734.svg")}.twa-eight-pointed-black-star{background-image:url("https://twemoji.maxcdn.com/2/svg/2734.svg")}.twa-snowflake{background-image:url("https://twemoji.maxcdn.com/2/svg/2744.svg")}.twa-sparkle{background-image:url("https://twemoji.maxcdn.com/2/svg/2747.svg")}.twa-cross-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/274c.svg")}.twa-x{background-image:url("https://twemoji.maxcdn.com/2/svg/274c.svg")}.twa-cross-mark-button{background-image:url("https://twemoji.maxcdn.com/2/svg/274e.svg")}.twa-negative-squared-cross-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/274e.svg")}.twa-question-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2753.svg")}.twa-question{background-image:url("https://twemoji.maxcdn.com/2/svg/2753.svg")}.twa-white-question-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2754.svg")}.twa-grey-question{background-image:url("https://twemoji.maxcdn.com/2/svg/2754.svg")}.twa-white-exclamation-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2755.svg")}.twa-grey-exclamation{background-image:url("https://twemoji.maxcdn.com/2/svg/2755.svg")}.twa-exclamation-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/2757.svg")}.twa-exclamation{background-image:url("https://twemoji.maxcdn.com/2/svg/2757.svg")}.twa-heavy-heart-exclamation-mark-ornament{background-image:url("https://twemoji.maxcdn.com/2/svg/2763.svg")}.twa-red-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/2764.svg")}.twa-heart{background-image:url("https://twemoji.maxcdn.com/2/svg/2764.svg")}.twa-heavy-plus-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/2795.svg")}.twa-heavy-minus-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/2796.svg")}.twa-heavy-division-sign{background-image:url("https://twemoji.maxcdn.com/2/svg/2797.svg")}.twa-right-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/27a1.svg")}.twa-arrow-right{background-image:url("https://twemoji.maxcdn.com/2/svg/27a1.svg")}.twa-curly-loop{background-image:url("https://twemoji.maxcdn.com/2/svg/27b0.svg")}.twa-double-curly-loop{background-image:url("https://twemoji.maxcdn.com/2/svg/27bf.svg")}.twa-loop{background-image:url("https://twemoji.maxcdn.com/2/svg/27bf.svg")}.twa-right-arrow-curving-up{background-image:url("https://twemoji.maxcdn.com/2/svg/2934.svg")}.twa-arrow-heading-up{background-image:url("https://twemoji.maxcdn.com/2/svg/2934.svg")}.twa-right-arrow-curving-down{background-image:url("https://twemoji.maxcdn.com/2/svg/2935.svg")}.twa-arrow-heading-down{background-image:url("https://twemoji.maxcdn.com/2/svg/2935.svg")}.twa-asterisk{background-image:url("https://twemoji.maxcdn.com/2/svg/2a-20e3.svg")}.twa-left-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2b05.svg")}.twa-arrow-left{background-image:url("https://twemoji.maxcdn.com/2/svg/2b05.svg")}.twa-up-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2b06.svg")}.twa-arrow-up{background-image:url("https://twemoji.maxcdn.com/2/svg/2b06.svg")}.twa-down-arrow{background-image:url("https://twemoji.maxcdn.com/2/svg/2b07.svg")}.twa-arrow-down{background-image:url("https://twemoji.maxcdn.com/2/svg/2b07.svg")}.twa-black-large-square{background-image:url("https://twemoji.maxcdn.com/2/svg/2b1b.svg")}.twa-white-large-square{background-image:url("https://twemoji.maxcdn.com/2/svg/2b1c.svg")}.twa-white-medium-star{background-image:url("https://twemoji.maxcdn.com/2/svg/2b50.svg")}.twa-star{background-image:url("https://twemoji.maxcdn.com/2/svg/2b50.svg")}.twa-heavy-large-circle{background-image:url("https://twemoji.maxcdn.com/2/svg/2b55.svg")}.twa-o{background-image:url("https://twemoji.maxcdn.com/2/svg/2b55.svg")}.twa-zero{background-image:url("https://twemoji.maxcdn.com/2/svg/30-20e3.svg")}.twa-wavy-dash{background-image:url("https://twemoji.maxcdn.com/2/svg/3030.svg")}.twa-part-alternation-mark{background-image:url("https://twemoji.maxcdn.com/2/svg/303d.svg")}.twa-one{background-image:url("https://twemoji.maxcdn.com/2/svg/31-20e3.svg")}.twa-two{background-image:url("https://twemoji.maxcdn.com/2/svg/32-20e3.svg")}.twa-circled-congratulate-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/3297.svg")}.twa-congratulations{background-image:url("https://twemoji.maxcdn.com/2/svg/3297.svg")}.twa-circled-secret-ideograph{background-image:url("https://twemoji.maxcdn.com/2/svg/3299.svg")}.twa-secret{background-image:url("https://twemoji.maxcdn.com/2/svg/3299.svg")}.twa-three{background-image:url("https://twemoji.maxcdn.com/2/svg/33-20e3.svg")}.twa-four{background-image:url("https://twemoji.maxcdn.com/2/svg/34-20e3.svg")}.twa-five{background-image:url("https://twemoji.maxcdn.com/2/svg/35-20e3.svg")}.twa-six{background-image:url("https://twemoji.maxcdn.com/2/svg/36-20e3.svg")}.twa-seven{background-image:url("https://twemoji.maxcdn.com/2/svg/37-20e3.svg")}.twa-eight{background-image:url("https://twemoji.maxcdn.com/2/svg/38-20e3.svg")}.twa-nine{background-image:url("https://twemoji.maxcdn.com/2/svg/39-20e3.svg")}.twa-copyright{background-image:url("https://twemoji.maxcdn.com/2/svg/a9.svg")}.twa-registered{background-image:url("https://twemoji.maxcdn.com/2/svg/ae.svg")}.twa-shibuya{background-image:url("https://twemoji.maxcdn.com/2/svg/e50a.svg")}.twa-shibuya-109{background-image:url("https://twemoji.maxcdn.com/2/svg/e50a.svg")} \ No newline at end of file diff --git a/docs/img/union.svg b/docs/img/union.svg new file mode 100644 index 0000000..ac9ca29 --- /dev/null +++ b/docs/img/union.svg @@ -0,0 +1,18 @@ + + + + + From aeecc6b94c4f2501ac4676c0967ce12fe5543a96 Mon Sep 17 00:00:00 2001 From: Bowser65 Date: Sat, 20 Oct 2018 12:00:21 +0200 Subject: [PATCH 19/42] I guess this is how to use Jekyll --- .gitmodules | 3 ++ docs/_config.yml | 7 ++-- docs/_layouts/{docs.html => docs.twig} | 28 +++++++-------- docs/_pages/api.md | 7 ++++ docs/_pages/apps.md | 9 +++++ docs/_pages/index.md | 6 +--- docs/_pages/socket.md | 7 ++++ docs/_pages/themes.md | 9 +++++ docs/_pages/users.md | 9 ----- docs/css/blobcat.min.css | 1 - docs/css/images/arrow.png | Bin 3391 -> 0 bytes docs/css/images/avatar.png | Bin 16645 -> 0 bytes docs/css/images/menu-icon.png | Bin 265 -> 0 bytes docs/css/modesta.min.css | 1 - docs/css/style.css | 47 +++++++++++++++++++++++++ docs/css/twemoji.min.css | 1 - docs/modesta | 1 + 17 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 .gitmodules rename docs/_layouts/{docs.html => docs.twig} (63%) create mode 100644 docs/_pages/api.md create mode 100644 docs/_pages/apps.md create mode 100644 docs/_pages/socket.md create mode 100644 docs/_pages/themes.md delete mode 100644 docs/_pages/users.md delete mode 100644 docs/css/blobcat.min.css delete mode 100644 docs/css/images/arrow.png delete mode 100644 docs/css/images/avatar.png delete mode 100644 docs/css/images/menu-icon.png delete mode 100644 docs/css/modesta.min.css delete mode 100644 docs/css/twemoji.min.css create mode 160000 docs/modesta diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d7e0482 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/modesta"] + path = docs/modesta + url = https://github.com/AlexFlipnote/ModestaCSS diff --git a/docs/_config.yml b/docs/_config.yml index a2cbdd1..25317c0 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,8 @@ -name: Union API Docs -title: Union API Docs +name: Union API Documentation +title: Union API Documentation +description: Welcome to the official documentation for Union API! include: ['_pages'] -exclude: ['modesta'] +exclude: ['modesta/_pages'] baseurl: "" url: "" markdown: kramdown diff --git a/docs/_layouts/docs.html b/docs/_layouts/docs.twig similarity index 63% rename from docs/_layouts/docs.html rename to docs/_layouts/docs.twig index 4a213bf..2da3793 100644 --- a/docs/_layouts/docs.html +++ b/docs/_layouts/docs.twig @@ -1,36 +1,35 @@ + - {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} + {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} {% if page.description %} - - + + {% endif %} - - + + @@ -71,9 +70,6 @@ -
{{ content }}
- +Design by AlexFlipnote ♥