├── .gitignore ├── Dockerfile ├── README.md ├── config.example.js ├── counter.js ├── docker_start.sh ├── index.js ├── item_parser.js ├── package-lock.json ├── package.json ├── profile_fetcher.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | items_game.txt 4 | csgo_english.txt 5 | config.js 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/csgofloat-db 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | 13 | # Bundle app source 14 | COPY . . 15 | 16 | VOLUME /config 17 | 18 | CMD [ "/bin/bash", "docker_start.sh" ] 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FloatDB API Server 2 | 3 | Source Code that Powers the FloatDB API Server 4 | 5 | **Note**: This repository is deprecated and not currently used to power our production servers 6 | 7 | ### Repo Links 8 | 9 | [CSGOFloat-Extension](https://github.com/Step7750/CSGOFloat-Extension) 10 | 11 | [CSGOFloat-DB-Website](https://github.com/Step7750/CSGOFloat-DB-Website) 12 | 13 | [CSGOFloat-Website](https://github.com/Step7750/CSGOFloat-Website) 14 | 15 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 80, 3 | // amount of seconds between updating game files 4 | file_update_interval: 30 * 60 * 1000, 5 | // postgres connection string 6 | connectionString: '', 7 | allowed_regex_origins: [], 8 | allowed_origins: [], 9 | search_rate_window: 2 * 60 * 60 * 1000, // 2 hours 10 | search_rate_limit: 120, 11 | trust_proxy: false, 12 | steam_api_keys: [], 13 | steam_cache_expiring_ms: 15 * 60 * 1000, // 15 min, web api cache expiry time 14 | max_query_items: 200, // max items returned for query 15 | recaptcha: { 16 | enable: false, 17 | secret: 'XXXXXXXXXXXXX', // Google Recaptcha V3 Secret 18 | minScore: 0.2, // Minimum Score to Allow a User 19 | action: 'db_query' // Name of the action to verify 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /counter.js: -------------------------------------------------------------------------------- 1 | class Counter { 2 | constructor(pool, counterUpdateInterval) { 3 | this.pool = pool; 4 | this.counterUpdateInterval = this.counterUpdateInterval || 30000; 5 | 6 | this.updateState(); 7 | 8 | setInterval(async () => { 9 | await this.updateState(); 10 | }, counterUpdateInterval || 30000); 11 | } 12 | 13 | async updateState() { 14 | const countRows = await this.pool.query(`SELECT n_live_tup as count FROM pg_stat_all_tables WHERE relname = 'items'`); 15 | const count = countRows.rows[0].count; 16 | 17 | const lastUpdate = Date.now()/1000; 18 | if (this.lastUpdate && this.count) { 19 | this.rateOfChange = (count-this.count)/(lastUpdate-this.lastUpdate); 20 | } 21 | 22 | this.count = count; 23 | this.lastUpdate = lastUpdate; 24 | } 25 | 26 | get() { 27 | return { 28 | count: parseInt(this.count), 29 | rate: this.rateOfChange, 30 | lastUpdate: this.lastUpdate, 31 | updateInterval: this.counterUpdateInterval/1000 32 | } 33 | } 34 | } 35 | 36 | module.exports = Counter; 37 | -------------------------------------------------------------------------------- /docker_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f /config/config.js ]; then 4 | cp /usr/src/csgofloat-db/config.example.js /config/config.js 5 | echo "Copied example config file to /config/config.js, please edit this file and restart" 6 | exit 1 7 | fi 8 | 9 | node index.js -c /config/config.js 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const optionDefinitions = [ 2 | { name: 'config', alias: 'c', type: String, defaultValue: './config' } // base file path directory 3 | ]; 4 | 5 | const args = require('command-line-args')(optionDefinitions); 6 | const config = require(args.config); 7 | const vdf = require('simple-vdf'); 8 | const rateLimit = require("express-rate-limit"); 9 | const fs = require('fs'); 10 | const express = require('express'); 11 | const fetch = require('node-fetch'); 12 | const { Pool } = require('pg'); 13 | const ItemParser = require('./item_parser'); 14 | const Counter = require('./counter'); 15 | const ProfileFetcher = require('./profile_fetcher'); 16 | const rp = require('request-promise'); 17 | 18 | const app = express(); 19 | 20 | const pool = new Pool({ 21 | connectionString: config.connectionString, 22 | }); 23 | 24 | const profileFetcher = new ProfileFetcher(pool, config.steam_api_keys, config.steam_cache_expiring_ms); 25 | let itemParser; 26 | 27 | const itemUrl = `https://raw.githubusercontent.com/SteamDatabase/GameTracking-CSGO/master/csgo/scripts/items/items_game.txt`; 28 | const englishUrl = `https://raw.githubusercontent.com/SteamDatabase/GameTracking-CSGO/master/csgo/resource/csgo_english.txt`; 29 | 30 | async function updateItems() { 31 | try { 32 | const resp = await fetch(itemUrl); 33 | const data = await resp.text(); 34 | fs.writeFile('items_game.txt', data, () => { 35 | console.log('Saved items_game.txt'); 36 | }); 37 | 38 | const langResp = await fetch(englishUrl); 39 | const langData = await langResp.text(); 40 | fs.writeFile('csgo_english.txt', langData, () => { 41 | console.log('Saved csgo_english.txt'); 42 | }); 43 | 44 | itemParser = new ItemParser(vdf.parse(data).items_game, vdf.parse(langData).lang.Tokens); 45 | } catch (e) { 46 | console.error(e); 47 | } 48 | } 49 | 50 | if (fs.existsSync('items_game.txt') && fs.existsSync('items_game.txt')) { 51 | const itemsGame = fs.readFileSync('items_game.txt', 'utf8'); 52 | itemParser = new ItemParser(vdf.parse(itemsGame)['items_game']); 53 | 54 | const english = fs.readFileSync('csgo_english.txt', 'utf8'); 55 | itemParser = new ItemParser(vdf.parse(itemsGame).items_game, vdf.parse(english).lang.Tokens); 56 | } else { 57 | updateItems(); 58 | } 59 | 60 | setInterval(() => updateItems(), config.file_update_interval); 61 | 62 | if (config.trust_proxy) { 63 | app.enable('trust proxy'); 64 | } 65 | 66 | config.allowed_regex_origins = config.allowed_regex_origins || []; 67 | config.allowed_origins = config.allowed_origins || []; 68 | const allowedRegexOrigins = config.allowed_regex_origins.map((origin) => new RegExp(origin)); 69 | 70 | function EnsureOrigin(req, res, next) { 71 | // Origin whitelist 72 | if ((config.allowed_origins.length > 0 || config.allowed_regex_origins.length > 0)) { 73 | // check to see if its a valid domain 74 | const allowed = config.allowed_origins.indexOf(req.get('origin')) > -1 || 75 | allowedRegexOrigins.findIndex((reg) => reg.test(req.get('origin'))) > -1; 76 | 77 | if (allowed) { 78 | res.header('Access-Control-Allow-Origin', req.get('origin')); 79 | res.header('Access-Control-Allow-Methods', 'GET'); 80 | next(); 81 | } else { 82 | res.status(400).json({error: 'Invalid request'}); 83 | } 84 | } else { 85 | res.header('Access-Control-Allow-Origin', req.get('origin')); 86 | res.header('Access-Control-Allow-Methods', 'GET'); 87 | next(); 88 | } 89 | } 90 | 91 | app.use(EnsureOrigin); 92 | 93 | app.get('/items', (req, res) => { 94 | if (itemParser) { 95 | res.json(itemParser.getFullResponse()); 96 | } else { 97 | res.status(500).json({error: 'Item response is not initialized, new csgo update?'}); 98 | } 99 | }); 100 | 101 | 102 | const counter = new Counter(pool); 103 | app.get('/count', (req, res) => { 104 | const countData = counter.get(); 105 | res.setHeader("Cache-Control", `public, s-maxage=${Math.ceil(counter.counterUpdateInterval/1000- 106 | (Date.now()/1000-countData.lastUpdate))}`); 107 | res.json(countData); 108 | }); 109 | 110 | function isInt(i) { 111 | return !isNaN(parseInt(i)); 112 | } 113 | 114 | /* 115 | Possible URL Query Params 116 | 117 | defIndex: Weapon index 118 | paintIndex: Paint index 119 | order: 1 for asc, -1 for desc 120 | stattrak: true/false 121 | souvenir: true/false 122 | paintseed: 0-999 123 | rarity: 1-7 124 | min: 0-1 125 | max: 0-1 126 | stickers: [{i: stickerId, s: slot number}] 127 | start: 0-5000 128 | */ 129 | function buildQuery(params) { 130 | const conditions = [], values = []; 131 | let conditionIndex = 0; 132 | 133 | if (params.defIndex && isInt(params.defIndex)) { 134 | conditions.push(`defindex = $${++conditionIndex}`); 135 | values.push(params.defIndex); 136 | } 137 | 138 | if (params.paintIndex && isInt(params.paintIndex)) { 139 | conditions.push(`paintindex = $${++conditionIndex}`); 140 | values.push(params.paintIndex); 141 | } 142 | 143 | if (params.stattrak) { 144 | conditions.push(`stattrak = $${++conditionIndex}`); 145 | values.push(params.stattrak === 'true'); 146 | } 147 | 148 | if (params.souvenir) { 149 | conditions.push(`souvenir = $${++conditionIndex}`); 150 | values.push(params.souvenir === 'true'); 151 | } 152 | 153 | if (params.paintSeed && isInt(params.paintSeed)) { 154 | conditions.push(`paintseed = $${++conditionIndex}`); 155 | values.push(params.paintSeed); 156 | } 157 | 158 | if (params.min) { 159 | const min = parseFloat(params.min); 160 | 161 | if (min >= 0.0 && min <= 1.0) { 162 | const buf = Buffer.alloc(4); 163 | buf.writeFloatBE(min, 0); 164 | const intMin = buf.readInt32BE(0); 165 | 166 | conditions.push(`paintwear >= $${++conditionIndex}`); 167 | values.push(intMin); 168 | } 169 | } 170 | 171 | if (params.max) { 172 | const max = parseFloat(params.max); 173 | 174 | if (max >= 0.0 && max <= 1.0) { 175 | const buf = Buffer.alloc(4); 176 | buf.writeFloatBE(max, 0); 177 | const intMax = buf.readInt32BE(0); 178 | 179 | conditions.push(`paintwear <= $${++conditionIndex}`); 180 | values.push(intMax); 181 | } 182 | } 183 | 184 | if (params.rarity && isInt(params.rarity)) { 185 | conditions.push(`rarity = $${++conditionIndex}`); 186 | values.push(params.rarity); 187 | } 188 | 189 | if (params.stickers) { 190 | try { 191 | const stickers = []; 192 | const uniqueIds = new Set(); 193 | 194 | const inputStickers = JSON.parse(params.stickers); 195 | 196 | for (const s of inputStickers) { 197 | if (!s.i) continue; 198 | 199 | const sticker = { 200 | i: parseInt(s.i) 201 | }; 202 | 203 | uniqueIds.add(s.i); 204 | 205 | if (s.s !== undefined) { 206 | sticker.s = parseInt(s.s); 207 | } 208 | 209 | stickers.push(sticker); 210 | } 211 | 212 | // This seems to force postgres to use the i_stickers index if > 1 stickers, otherwise it sometimes uses 213 | // the i_paintwear index and filters rows which is substantially slower if we put it all in one array 214 | for (const s of stickers) { 215 | conditions.push(`stickers @> $${++conditionIndex}`); 216 | values.push(JSON.stringify([s])); 217 | } 218 | 219 | // Yes, I know this seems strange, why add the same value twice if there's only one sticker? 220 | // This seems to get postgres' query planner to use the i_stickers index instead of i_paintwear 221 | // when there's only one sticker, which is way faster 222 | if (stickers.length === 1) { 223 | conditions.push(`stickers @> $${++conditionIndex}`); 224 | values.push(JSON.stringify([stickers[0]])); 225 | } 226 | 227 | let totalDuplicates = 0; 228 | 229 | // Add duplicate property (allows us to use the index to search sticker dupes) 230 | for (const sticker of stickers) { 231 | const matching = stickers.filter((s) => s.i === sticker.i); 232 | if (matching.length > 1 && !matching.find((s) => s.d > 1)) { 233 | sticker.d = matching.length; 234 | totalDuplicates += sticker.d; 235 | } 236 | } 237 | 238 | // Patch to ensure that if a user wants to search 2 of a same sticker, we'd also include guns with 2 or more 239 | // of the same one 240 | // Unfortunately the DB is designed to only store the highest amount of one sticker 241 | for (const sticker of stickers) { 242 | if (inputStickers.length === 1) continue; 243 | if (!sticker.d) continue; 244 | 245 | const possibleExtra = 5 - uniqueIds.size + 1 - sticker.d; 246 | 247 | const conds = []; 248 | // Amount of possible stickers 249 | for (let i = 0; i < possibleExtra+1; i++) { 250 | conds.push(`stickers @> $${++conditionIndex}`); 251 | values.push(JSON.stringify([{i: sticker.i, d: sticker.d+i}])); 252 | } 253 | 254 | conditions.push(`(${conds.join(' OR ')})`); 255 | } 256 | } catch (e) { 257 | console.error(e); 258 | } 259 | } 260 | 261 | const start = (params.start && parseInt(params.start) < 5000 && parseInt(params.start) >= 0) ? 262 | parseInt(params.start) : 0; 263 | 264 | const statement = `SELECT * FROM items ${conditions.length > 0 ? 'WHERE' : ''} ${conditions.join(' AND ')} 265 | ORDER BY paintwear ${params.order === '-1' ? 'DESC' : ''} LIMIT ${config.max_query_items || 200} 266 | OFFSET ${start}`; 267 | 268 | return { 269 | text: statement, 270 | values 271 | } 272 | } 273 | 274 | /* 275 | Converts the given unsigned 64 bit integer into a signed 64 bit integer 276 | */ 277 | function unsigned64ToSigned(num) { 278 | const mask = 1n << 63n; 279 | return (BigInt(num)^mask) - mask; 280 | } 281 | 282 | /* 283 | Converts the given signed 64 bit integer into an unsigned 64 bit integer 284 | */ 285 | function signed64ToUnsigned(num) { 286 | const mask = 1n << 63n; 287 | return (BigInt(num)+mask) ^ mask; 288 | } 289 | 290 | function isSteamId64(id) { 291 | id = BigInt(id); 292 | const universe = id >> 56n; 293 | if (universe > 5n) return false; 294 | 295 | const instance = (id >> 32n) & (1n << 20n)-1n; 296 | 297 | // There are currently no documented instances above 4, but this is for good measure 298 | return instance <= 32n; 299 | } 300 | 301 | const searchLimiter = rateLimit({ 302 | windowMs: config.search_rate_window || 2 * 60 * 60 * 1000, // 2 hours 303 | max: config.search_rate_limit || 120, 304 | headers: false, 305 | handler: function (req, res) { 306 | res.status(429).json({error: `Rate limit exceeded, please try again later`}); 307 | } 308 | }); 309 | 310 | async function captchaVerifier(req, res, next) { 311 | if (!config.recaptcha || !config.recaptcha.enable) { 312 | next(); 313 | return; 314 | } 315 | 316 | if (!req.query.token) { 317 | res.status(400).json({error: 'Failed to include token'}); 318 | return; 319 | } 320 | 321 | try { 322 | const response = await rp({ 323 | url: `https://www.google.com/recaptcha/api/siteverify?secret=${config.recaptcha.secret}&response=${req.query.token}&remoteip=${req.ip}`, 324 | json: true 325 | }); 326 | 327 | if (response.success === false || response.action !== config.recaptcha.action || 328 | response.score < config.recaptcha.minScore) { 329 | res.status(400).json({error: `Failed to verify recaptcha, please try again or remove ad blockers`}); 330 | } else { 331 | next(); 332 | } 333 | } catch (e) { 334 | console.error(`Failed to verify recaptcha: ${e.toString()}`); 335 | res.status(400).json({error: `Failed to verify recaptcha, please try again or remove ad blockers`}); 336 | } 337 | } 338 | 339 | app.get('/search', [searchLimiter, captchaVerifier], async (req, res) => { 340 | const query = buildQuery(req.query); 341 | 342 | try { 343 | const results = await pool.query(query); 344 | let rows = results.rows.map((row) => { 345 | const buf = Buffer.alloc(4); 346 | buf.writeInt32BE(row.paintwear, 0); 347 | const floatvalue = buf.readFloatBE(0); 348 | 349 | const a = signed64ToUnsigned(row.a).toString(); 350 | const d = signed64ToUnsigned(row.d).toString(); 351 | const ms = signed64ToUnsigned(row.ms).toString(); 352 | let m = '0', s = '0'; 353 | 354 | if (isSteamId64(ms)){ 355 | s = ms; 356 | } else { 357 | m = ms; 358 | } 359 | 360 | return { 361 | s, 362 | a, 363 | d, 364 | m, 365 | floatvalue, 366 | props: row.props, 367 | souvenir: row.souvenir, 368 | stattrak: row.stattrak, 369 | stickers: row.stickers, 370 | updated: row.updated, 371 | paintseed: row.paintseed, 372 | defIndex: row.defindex, 373 | paintIndex: row.paintindex, 374 | } 375 | }); 376 | 377 | if (profileFetcher.canFetch()) { 378 | const steamIds = [...new Set(rows.filter(e => e.s != '0').map(e => e.s))]; 379 | const profiles = await profileFetcher.getProfilesForSteamIds(steamIds); 380 | 381 | rows = rows.map(row => { 382 | const profile = profiles[row.s]; 383 | 384 | if (profile) { 385 | row.avatar = profile.avatar; 386 | row.personaState = profile.personastate; 387 | 388 | // Patch to pass state if they are in-game 389 | if (profile.gameextrainfo) { 390 | row.personaState = 100; 391 | } 392 | } 393 | 394 | return row; 395 | }); 396 | } 397 | 398 | res.json(rows); 399 | } catch (e) { 400 | console.error(e); 401 | res.status(400).json({error: 'Something went wrong'}); 402 | } 403 | }); 404 | 405 | app.listen(config.port, () => console.log(`Listening on Port ${config.port}`)); 406 | -------------------------------------------------------------------------------- /item_parser.js: -------------------------------------------------------------------------------- 1 | const LanguageHandler = { 2 | get: function(obj, prop) { 3 | return obj[prop.toLowerCase()]; 4 | }, 5 | has: function (obj, prop) { 6 | return prop.toLowerCase() in obj; 7 | } 8 | }; 9 | 10 | const dopplerPhases = { 11 | 418: 'Phase 1', 12 | 419: 'Phase 2', 13 | 420: 'Phase 3', 14 | 421: 'Phase 4', 15 | 415: 'Ruby', 16 | 416: 'Sapphire', 17 | 417: 'Black Pearl', 18 | 569: 'Phase 1', 19 | 570: 'Phase 2', 20 | 571: 'Phase 3', 21 | 572: 'Phase 4', 22 | 568: 'Emerald', 23 | 618: 'Phase 2', 24 | 619: 'Sapphire', 25 | 617: 'Black Pearl', 26 | 852: 'Phase 1', 27 | 853: 'Phase 2', 28 | 854: 'Phase 3', 29 | 855: 'Phase 4' 30 | }; 31 | 32 | 33 | class ItemParser { 34 | constructor(itemsGame, language) { 35 | this.itemsGame = itemsGame; 36 | this.language = new Proxy(this._objectKeysToLowerCase(language || {}), LanguageHandler); 37 | } 38 | 39 | /* 40 | Calls toLowerCase on all object shallow keys, modifies in-place, not pure 41 | */ 42 | _objectKeysToLowerCase(obj) { 43 | const keys = Object.keys(obj); 44 | let n = keys.length; 45 | while (n--) { 46 | const key = keys[n]; 47 | const lower = key.toLowerCase(); 48 | if (key !== lower) { 49 | obj[lower] = obj[key]; 50 | delete obj[key]; 51 | } 52 | } 53 | 54 | return obj 55 | } 56 | 57 | _getPrefabStickerAmount(prefabName) { 58 | const prefab = this.itemsGame.prefabs[prefabName]; 59 | return Object.keys(prefab.stickers || {}).length; 60 | } 61 | 62 | _isWeapon(prefabName) { 63 | if (prefabName === 'melee_unusual' || prefabName === 'hands_paintable') return true; 64 | 65 | const prefab = this.itemsGame.prefabs[prefabName]; 66 | const usedClasses = prefab && prefab.used_by_classes; 67 | 68 | return usedClasses && (usedClasses['terrorists'] || usedClasses['counter-terrorists']); 69 | } 70 | 71 | _getWeapons() { 72 | const weapons = {}; 73 | for (const defIndex in this.itemsGame.items) { 74 | const item = this.itemsGame.items[defIndex]; 75 | if (item.prefab && this._isWeapon(item.prefab)) { 76 | weapons[defIndex] = item; 77 | } 78 | } 79 | 80 | return weapons; 81 | } 82 | 83 | _getWeaponLanguageName(defIndex) { 84 | const item = this.itemsGame.items[defIndex]; 85 | 86 | if (item.item_name) { 87 | return this._getLanguageValue(item.item_name); 88 | } else { 89 | const prefab = this.itemsGame.prefabs[item.prefab]; 90 | return this._getLanguageValue(prefab.item_name); 91 | } 92 | } 93 | 94 | _getPaintKitIndex(name) { 95 | return Object.keys(this.itemsGame.paint_kits).find((paintIndex) => { 96 | const kit = this.itemsGame.paint_kits[paintIndex]; 97 | 98 | if (kit.name === name) { 99 | return true; 100 | } 101 | }) 102 | } 103 | 104 | _getLanguageValue(token) { 105 | return this.language[token.replace('#', '')]; 106 | } 107 | 108 | _getWeaponPaints(weaponName) { 109 | const paints = {}; 110 | 111 | for (const iconId of Object.keys(this.itemsGame.alternate_icons2.weapon_icons)) { 112 | const iconPath = this.itemsGame.alternate_icons2.weapon_icons[iconId].icon_path; 113 | if (iconPath.indexOf(weaponName) === -1) continue; 114 | 115 | const parsed = iconPath.match(/econ\/default_generated\/(.*)_/)[1]; 116 | const paintName = parsed.replace(`${weaponName}_`, ''); 117 | 118 | const index = this._getPaintKitIndex(paintName); 119 | 120 | if (index) { 121 | const kit = this.itemsGame.paint_kits[index]; 122 | 123 | let name = this._getLanguageValue(kit.description_tag); 124 | 125 | if (index in dopplerPhases) { 126 | name += ` (${dopplerPhases[index]})`; 127 | } 128 | 129 | paints[index] = { 130 | name, 131 | min: parseFloat(kit.wear_remap_min || 0.06), 132 | max: parseFloat(kit.wear_remap_max || 0.80), 133 | }; 134 | } 135 | } 136 | 137 | return paints; 138 | } 139 | 140 | getStickers() { 141 | const stickers = {}; 142 | 143 | for (const stickerId of Object.keys(this.itemsGame.sticker_kits)) { 144 | if (stickerId == '0') continue; 145 | 146 | const sticker = this.itemsGame.sticker_kits[stickerId]; 147 | 148 | // Ignore graffiti 149 | if (!sticker.name.startsWith("spray_")) { 150 | stickers[stickerId] = this._getLanguageValue(sticker.item_name); 151 | } 152 | } 153 | 154 | return stickers; 155 | } 156 | 157 | getFullResponse() { 158 | if (this.resp) return this.resp; 159 | 160 | const resp = {}; 161 | 162 | const weapons = this._getWeapons(); 163 | 164 | const weaponsResp = {}; 165 | 166 | for (const defIndex of Object.keys(weapons)) { 167 | const weapon = weapons[defIndex]; 168 | const paints = this._getWeaponPaints(weapon.name); 169 | 170 | if (Object.keys(paints).length === 0) continue; 171 | 172 | let type; 173 | if (weapon.prefab === 'hands_paintable') { 174 | type = 'Gloves' 175 | } else if (weapon.prefab === 'melee_unusual') { 176 | type = 'Knives' 177 | } else { 178 | type = 'Weapons' 179 | } 180 | 181 | weaponsResp[defIndex] = { 182 | name: this._getWeaponLanguageName(defIndex), 183 | type, 184 | stickerAmount: this._getPrefabStickerAmount(weapon.prefab), 185 | paints 186 | }; 187 | } 188 | 189 | resp.weapons = weaponsResp; 190 | resp.stickers = this.getStickers(); 191 | 192 | this.resp = resp; 193 | 194 | return resp; 195 | } 196 | } 197 | 198 | module.exports = ItemParser; 199 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CSGOFloat-DB", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "ajv": { 17 | "version": "6.10.2", 18 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", 19 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", 20 | "requires": { 21 | "fast-deep-equal": "^2.0.1", 22 | "fast-json-stable-stringify": "^2.0.0", 23 | "json-schema-traverse": "^0.4.1", 24 | "uri-js": "^4.2.2" 25 | } 26 | }, 27 | "array-back": { 28 | "version": "3.1.0", 29 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 30 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" 31 | }, 32 | "array-flatten": { 33 | "version": "1.1.1", 34 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 35 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 36 | }, 37 | "asn1": { 38 | "version": "0.2.4", 39 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 40 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 41 | "requires": { 42 | "safer-buffer": "~2.1.0" 43 | } 44 | }, 45 | "assert-plus": { 46 | "version": "1.0.0", 47 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 48 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 49 | }, 50 | "asynckit": { 51 | "version": "0.4.0", 52 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 53 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 54 | }, 55 | "aws-sign2": { 56 | "version": "0.7.0", 57 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 58 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 59 | }, 60 | "aws4": { 61 | "version": "1.8.0", 62 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 63 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 64 | }, 65 | "bcrypt-pbkdf": { 66 | "version": "1.0.2", 67 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 68 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 69 | "requires": { 70 | "tweetnacl": "^0.14.3" 71 | } 72 | }, 73 | "bluebird": { 74 | "version": "3.5.5", 75 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", 76 | "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" 77 | }, 78 | "body-parser": { 79 | "version": "1.18.3", 80 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 81 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 82 | "requires": { 83 | "bytes": "3.0.0", 84 | "content-type": "~1.0.4", 85 | "debug": "2.6.9", 86 | "depd": "~1.1.2", 87 | "http-errors": "~1.6.3", 88 | "iconv-lite": "0.4.23", 89 | "on-finished": "~2.3.0", 90 | "qs": "6.5.2", 91 | "raw-body": "2.3.3", 92 | "type-is": "~1.6.16" 93 | } 94 | }, 95 | "buffer-writer": { 96 | "version": "2.0.0", 97 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", 98 | "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" 99 | }, 100 | "bytes": { 101 | "version": "3.0.0", 102 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 103 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 104 | }, 105 | "caseless": { 106 | "version": "0.12.0", 107 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 108 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 109 | }, 110 | "clone": { 111 | "version": "1.0.4", 112 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", 113 | "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" 114 | }, 115 | "combined-stream": { 116 | "version": "1.0.8", 117 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 118 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 119 | "requires": { 120 | "delayed-stream": "~1.0.0" 121 | } 122 | }, 123 | "command-line-args": { 124 | "version": "5.1.1", 125 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", 126 | "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", 127 | "requires": { 128 | "array-back": "^3.0.1", 129 | "find-replace": "^3.0.0", 130 | "lodash.camelcase": "^4.3.0", 131 | "typical": "^4.0.0" 132 | } 133 | }, 134 | "content-disposition": { 135 | "version": "0.5.2", 136 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 137 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 138 | }, 139 | "content-type": { 140 | "version": "1.0.4", 141 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 142 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 143 | }, 144 | "cookie": { 145 | "version": "0.3.1", 146 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 147 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 148 | }, 149 | "cookie-signature": { 150 | "version": "1.0.6", 151 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 152 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 153 | }, 154 | "core-util-is": { 155 | "version": "1.0.2", 156 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 157 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 158 | }, 159 | "dashdash": { 160 | "version": "1.14.1", 161 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 162 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 163 | "requires": { 164 | "assert-plus": "^1.0.0" 165 | } 166 | }, 167 | "debug": { 168 | "version": "2.6.9", 169 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 170 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 171 | "requires": { 172 | "ms": "2.0.0" 173 | }, 174 | "dependencies": { 175 | "ms": { 176 | "version": "2.0.0", 177 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 178 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 179 | } 180 | } 181 | }, 182 | "defaults": { 183 | "version": "1.0.3", 184 | "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", 185 | "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", 186 | "requires": { 187 | "clone": "^1.0.2" 188 | } 189 | }, 190 | "delayed-stream": { 191 | "version": "1.0.0", 192 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 193 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 194 | }, 195 | "depd": { 196 | "version": "1.1.2", 197 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 198 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 199 | }, 200 | "destroy": { 201 | "version": "1.0.4", 202 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 203 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 204 | }, 205 | "ecc-jsbn": { 206 | "version": "0.1.2", 207 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 208 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 209 | "requires": { 210 | "jsbn": "~0.1.0", 211 | "safer-buffer": "^2.1.0" 212 | } 213 | }, 214 | "ee-first": { 215 | "version": "1.1.1", 216 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 217 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 218 | }, 219 | "encodeurl": { 220 | "version": "1.0.2", 221 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 222 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 223 | }, 224 | "escape-html": { 225 | "version": "1.0.3", 226 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 227 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 228 | }, 229 | "etag": { 230 | "version": "1.8.1", 231 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 232 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 233 | }, 234 | "express": { 235 | "version": "4.16.4", 236 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 237 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 238 | "requires": { 239 | "accepts": "~1.3.5", 240 | "array-flatten": "1.1.1", 241 | "body-parser": "1.18.3", 242 | "content-disposition": "0.5.2", 243 | "content-type": "~1.0.4", 244 | "cookie": "0.3.1", 245 | "cookie-signature": "1.0.6", 246 | "debug": "2.6.9", 247 | "depd": "~1.1.2", 248 | "encodeurl": "~1.0.2", 249 | "escape-html": "~1.0.3", 250 | "etag": "~1.8.1", 251 | "finalhandler": "1.1.1", 252 | "fresh": "0.5.2", 253 | "merge-descriptors": "1.0.1", 254 | "methods": "~1.1.2", 255 | "on-finished": "~2.3.0", 256 | "parseurl": "~1.3.2", 257 | "path-to-regexp": "0.1.7", 258 | "proxy-addr": "~2.0.4", 259 | "qs": "6.5.2", 260 | "range-parser": "~1.2.0", 261 | "safe-buffer": "5.1.2", 262 | "send": "0.16.2", 263 | "serve-static": "1.13.2", 264 | "setprototypeof": "1.1.0", 265 | "statuses": "~1.4.0", 266 | "type-is": "~1.6.16", 267 | "utils-merge": "1.0.1", 268 | "vary": "~1.1.2" 269 | } 270 | }, 271 | "express-rate-limit": { 272 | "version": "3.5.0", 273 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-3.5.0.tgz", 274 | "integrity": "sha512-DLUgv9lqUCEil5RV5naS/rABzfi/zOEfgU7Fb/0f+QyRbM5pHCvZozhWEeD01b0V5RsyGBNtRhp2YxfvrlgAaA==", 275 | "requires": { 276 | "defaults": "^1.0.3" 277 | } 278 | }, 279 | "extend": { 280 | "version": "3.0.2", 281 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 282 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 283 | }, 284 | "extsprintf": { 285 | "version": "1.3.0", 286 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 287 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 288 | }, 289 | "fast-deep-equal": { 290 | "version": "2.0.1", 291 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 292 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 293 | }, 294 | "fast-json-stable-stringify": { 295 | "version": "2.0.0", 296 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 297 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 298 | }, 299 | "finalhandler": { 300 | "version": "1.1.1", 301 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 302 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 303 | "requires": { 304 | "debug": "2.6.9", 305 | "encodeurl": "~1.0.2", 306 | "escape-html": "~1.0.3", 307 | "on-finished": "~2.3.0", 308 | "parseurl": "~1.3.2", 309 | "statuses": "~1.4.0", 310 | "unpipe": "~1.0.0" 311 | } 312 | }, 313 | "find-replace": { 314 | "version": "3.0.0", 315 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 316 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 317 | "requires": { 318 | "array-back": "^3.0.1" 319 | } 320 | }, 321 | "forever-agent": { 322 | "version": "0.6.1", 323 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 324 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 325 | }, 326 | "form-data": { 327 | "version": "2.3.3", 328 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 329 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 330 | "requires": { 331 | "asynckit": "^0.4.0", 332 | "combined-stream": "^1.0.6", 333 | "mime-types": "^2.1.12" 334 | } 335 | }, 336 | "forwarded": { 337 | "version": "0.1.2", 338 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 339 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 340 | }, 341 | "fresh": { 342 | "version": "0.5.2", 343 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 344 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 345 | }, 346 | "getpass": { 347 | "version": "0.1.7", 348 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 349 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 350 | "requires": { 351 | "assert-plus": "^1.0.0" 352 | } 353 | }, 354 | "har-schema": { 355 | "version": "2.0.0", 356 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 357 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 358 | }, 359 | "har-validator": { 360 | "version": "5.1.3", 361 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 362 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 363 | "requires": { 364 | "ajv": "^6.5.5", 365 | "har-schema": "^2.0.0" 366 | } 367 | }, 368 | "http-errors": { 369 | "version": "1.6.3", 370 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 371 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 372 | "requires": { 373 | "depd": "~1.1.2", 374 | "inherits": "2.0.3", 375 | "setprototypeof": "1.1.0", 376 | "statuses": ">= 1.4.0 < 2" 377 | } 378 | }, 379 | "http-signature": { 380 | "version": "1.2.0", 381 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 382 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 383 | "requires": { 384 | "assert-plus": "^1.0.0", 385 | "jsprim": "^1.2.2", 386 | "sshpk": "^1.7.0" 387 | } 388 | }, 389 | "iconv-lite": { 390 | "version": "0.4.23", 391 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 392 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 393 | "requires": { 394 | "safer-buffer": ">= 2.1.2 < 3" 395 | } 396 | }, 397 | "inherits": { 398 | "version": "2.0.3", 399 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 400 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 401 | }, 402 | "ipaddr.js": { 403 | "version": "1.9.0", 404 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 405 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 406 | }, 407 | "is-typedarray": { 408 | "version": "1.0.0", 409 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 410 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 411 | }, 412 | "isstream": { 413 | "version": "0.1.2", 414 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 415 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 416 | }, 417 | "jsbn": { 418 | "version": "0.1.1", 419 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 420 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 421 | }, 422 | "json-schema": { 423 | "version": "0.2.3", 424 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 425 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 426 | }, 427 | "json-schema-traverse": { 428 | "version": "0.4.1", 429 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 430 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 431 | }, 432 | "json-stringify-safe": { 433 | "version": "5.0.1", 434 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 435 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 436 | }, 437 | "jsprim": { 438 | "version": "1.4.1", 439 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 440 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 441 | "requires": { 442 | "assert-plus": "1.0.0", 443 | "extsprintf": "1.3.0", 444 | "json-schema": "0.2.3", 445 | "verror": "1.10.0" 446 | } 447 | }, 448 | "lodash": { 449 | "version": "4.17.15", 450 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 451 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 452 | }, 453 | "lodash.camelcase": { 454 | "version": "4.3.0", 455 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 456 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 457 | }, 458 | "media-typer": { 459 | "version": "0.3.0", 460 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 461 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 462 | }, 463 | "merge-descriptors": { 464 | "version": "1.0.1", 465 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 466 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 467 | }, 468 | "methods": { 469 | "version": "1.1.2", 470 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 471 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 472 | }, 473 | "mime": { 474 | "version": "1.4.1", 475 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 476 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 477 | }, 478 | "mime-db": { 479 | "version": "1.40.0", 480 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 481 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 482 | }, 483 | "mime-types": { 484 | "version": "2.1.24", 485 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 486 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 487 | "requires": { 488 | "mime-db": "1.40.0" 489 | } 490 | }, 491 | "negotiator": { 492 | "version": "0.6.1", 493 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 494 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 495 | }, 496 | "node-fetch": { 497 | "version": "2.3.0", 498 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", 499 | "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" 500 | }, 501 | "oauth-sign": { 502 | "version": "0.9.0", 503 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 504 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 505 | }, 506 | "on-finished": { 507 | "version": "2.3.0", 508 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 509 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 510 | "requires": { 511 | "ee-first": "1.1.1" 512 | } 513 | }, 514 | "packet-reader": { 515 | "version": "1.0.0", 516 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", 517 | "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" 518 | }, 519 | "parseurl": { 520 | "version": "1.3.3", 521 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 522 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 523 | }, 524 | "path-to-regexp": { 525 | "version": "0.1.7", 526 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 527 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 528 | }, 529 | "performance-now": { 530 | "version": "2.1.0", 531 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 532 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 533 | }, 534 | "pg": { 535 | "version": "7.10.0", 536 | "resolved": "https://registry.npmjs.org/pg/-/pg-7.10.0.tgz", 537 | "integrity": "sha512-aE6FZomsyn3OeGv1oM50v7Xu5zR75c15LXdOCwA9GGrfjXsQjzwYpbcTS6OwEMhYfZQS6m/FVU/ilPLiPzJDCw==", 538 | "requires": { 539 | "buffer-writer": "2.0.0", 540 | "packet-reader": "1.0.0", 541 | "pg-connection-string": "0.1.3", 542 | "pg-pool": "^2.0.4", 543 | "pg-types": "~2.0.0", 544 | "pgpass": "1.x", 545 | "semver": "4.3.2" 546 | } 547 | }, 548 | "pg-connection-string": { 549 | "version": "0.1.3", 550 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", 551 | "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" 552 | }, 553 | "pg-int8": { 554 | "version": "1.0.1", 555 | "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", 556 | "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" 557 | }, 558 | "pg-pool": { 559 | "version": "2.0.6", 560 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", 561 | "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" 562 | }, 563 | "pg-types": { 564 | "version": "2.0.1", 565 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", 566 | "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", 567 | "requires": { 568 | "pg-int8": "1.0.1", 569 | "postgres-array": "~2.0.0", 570 | "postgres-bytea": "~1.0.0", 571 | "postgres-date": "~1.0.4", 572 | "postgres-interval": "^1.1.0" 573 | } 574 | }, 575 | "pgpass": { 576 | "version": "1.0.2", 577 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", 578 | "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", 579 | "requires": { 580 | "split": "^1.0.0" 581 | } 582 | }, 583 | "postgres-array": { 584 | "version": "2.0.0", 585 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", 586 | "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" 587 | }, 588 | "postgres-bytea": { 589 | "version": "1.0.0", 590 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 591 | "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" 592 | }, 593 | "postgres-date": { 594 | "version": "1.0.4", 595 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", 596 | "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" 597 | }, 598 | "postgres-interval": { 599 | "version": "1.2.0", 600 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", 601 | "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", 602 | "requires": { 603 | "xtend": "^4.0.0" 604 | } 605 | }, 606 | "proxy-addr": { 607 | "version": "2.0.5", 608 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 609 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 610 | "requires": { 611 | "forwarded": "~0.1.2", 612 | "ipaddr.js": "1.9.0" 613 | } 614 | }, 615 | "psl": { 616 | "version": "1.3.0", 617 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", 618 | "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" 619 | }, 620 | "punycode": { 621 | "version": "2.1.1", 622 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 623 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 624 | }, 625 | "qs": { 626 | "version": "6.5.2", 627 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 628 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 629 | }, 630 | "range-parser": { 631 | "version": "1.2.0", 632 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 633 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 634 | }, 635 | "raw-body": { 636 | "version": "2.3.3", 637 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 638 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 639 | "requires": { 640 | "bytes": "3.0.0", 641 | "http-errors": "1.6.3", 642 | "iconv-lite": "0.4.23", 643 | "unpipe": "1.0.0" 644 | } 645 | }, 646 | "request": { 647 | "version": "2.88.0", 648 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 649 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 650 | "requires": { 651 | "aws-sign2": "~0.7.0", 652 | "aws4": "^1.8.0", 653 | "caseless": "~0.12.0", 654 | "combined-stream": "~1.0.6", 655 | "extend": "~3.0.2", 656 | "forever-agent": "~0.6.1", 657 | "form-data": "~2.3.2", 658 | "har-validator": "~5.1.0", 659 | "http-signature": "~1.2.0", 660 | "is-typedarray": "~1.0.0", 661 | "isstream": "~0.1.2", 662 | "json-stringify-safe": "~5.0.1", 663 | "mime-types": "~2.1.19", 664 | "oauth-sign": "~0.9.0", 665 | "performance-now": "^2.1.0", 666 | "qs": "~6.5.2", 667 | "safe-buffer": "^5.1.2", 668 | "tough-cookie": "~2.4.3", 669 | "tunnel-agent": "^0.6.0", 670 | "uuid": "^3.3.2" 671 | } 672 | }, 673 | "request-promise": { 674 | "version": "4.2.4", 675 | "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", 676 | "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", 677 | "requires": { 678 | "bluebird": "^3.5.0", 679 | "request-promise-core": "1.1.2", 680 | "stealthy-require": "^1.1.1", 681 | "tough-cookie": "^2.3.3" 682 | } 683 | }, 684 | "request-promise-core": { 685 | "version": "1.1.2", 686 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", 687 | "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", 688 | "requires": { 689 | "lodash": "^4.17.11" 690 | } 691 | }, 692 | "safe-buffer": { 693 | "version": "5.1.2", 694 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 695 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 696 | }, 697 | "safer-buffer": { 698 | "version": "2.1.2", 699 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 700 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 701 | }, 702 | "semver": { 703 | "version": "4.3.2", 704 | "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", 705 | "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" 706 | }, 707 | "send": { 708 | "version": "0.16.2", 709 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 710 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 711 | "requires": { 712 | "debug": "2.6.9", 713 | "depd": "~1.1.2", 714 | "destroy": "~1.0.4", 715 | "encodeurl": "~1.0.2", 716 | "escape-html": "~1.0.3", 717 | "etag": "~1.8.1", 718 | "fresh": "0.5.2", 719 | "http-errors": "~1.6.2", 720 | "mime": "1.4.1", 721 | "ms": "2.0.0", 722 | "on-finished": "~2.3.0", 723 | "range-parser": "~1.2.0", 724 | "statuses": "~1.4.0" 725 | }, 726 | "dependencies": { 727 | "ms": { 728 | "version": "2.0.0", 729 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 730 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 731 | } 732 | } 733 | }, 734 | "serve-static": { 735 | "version": "1.13.2", 736 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 737 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 738 | "requires": { 739 | "encodeurl": "~1.0.2", 740 | "escape-html": "~1.0.3", 741 | "parseurl": "~1.3.2", 742 | "send": "0.16.2" 743 | } 744 | }, 745 | "setprototypeof": { 746 | "version": "1.1.0", 747 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 748 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 749 | }, 750 | "simple-vdf": { 751 | "version": "1.1.1", 752 | "resolved": "https://registry.npmjs.org/simple-vdf/-/simple-vdf-1.1.1.tgz", 753 | "integrity": "sha1-B+LE3sBs9hTtb0IRsYzOEjyT/Kk=" 754 | }, 755 | "split": { 756 | "version": "1.0.1", 757 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", 758 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", 759 | "requires": { 760 | "through": "2" 761 | } 762 | }, 763 | "sshpk": { 764 | "version": "1.16.1", 765 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 766 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 767 | "requires": { 768 | "asn1": "~0.2.3", 769 | "assert-plus": "^1.0.0", 770 | "bcrypt-pbkdf": "^1.0.0", 771 | "dashdash": "^1.12.0", 772 | "ecc-jsbn": "~0.1.1", 773 | "getpass": "^0.1.1", 774 | "jsbn": "~0.1.0", 775 | "safer-buffer": "^2.0.2", 776 | "tweetnacl": "~0.14.0" 777 | } 778 | }, 779 | "statuses": { 780 | "version": "1.4.0", 781 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 782 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 783 | }, 784 | "stealthy-require": { 785 | "version": "1.1.1", 786 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 787 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 788 | }, 789 | "through": { 790 | "version": "2.3.8", 791 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 792 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 793 | }, 794 | "tough-cookie": { 795 | "version": "2.4.3", 796 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 797 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 798 | "requires": { 799 | "psl": "^1.1.24", 800 | "punycode": "^1.4.1" 801 | }, 802 | "dependencies": { 803 | "punycode": { 804 | "version": "1.4.1", 805 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 806 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 807 | } 808 | } 809 | }, 810 | "tunnel-agent": { 811 | "version": "0.6.0", 812 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 813 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 814 | "requires": { 815 | "safe-buffer": "^5.0.1" 816 | } 817 | }, 818 | "tweetnacl": { 819 | "version": "0.14.5", 820 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 821 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 822 | }, 823 | "type-is": { 824 | "version": "1.6.17", 825 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.17.tgz", 826 | "integrity": "sha512-jYZzkOoAPVyQ9vlZ4xEJ4BBbHC4a7hbY1xqyCPe6AiQVVqfbZEulJm0VpqK4B+096O1VQi0l6OBGH210ejx/bA==", 827 | "requires": { 828 | "media-typer": "0.3.0", 829 | "mime-types": "~2.1.24" 830 | } 831 | }, 832 | "typical": { 833 | "version": "4.0.0", 834 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 835 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" 836 | }, 837 | "unpipe": { 838 | "version": "1.0.0", 839 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 840 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 841 | }, 842 | "uri-js": { 843 | "version": "4.2.2", 844 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 845 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 846 | "requires": { 847 | "punycode": "^2.1.0" 848 | } 849 | }, 850 | "utils-merge": { 851 | "version": "1.0.1", 852 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 853 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 854 | }, 855 | "uuid": { 856 | "version": "3.3.3", 857 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 858 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 859 | }, 860 | "vary": { 861 | "version": "1.1.2", 862 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 863 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 864 | }, 865 | "verror": { 866 | "version": "1.10.0", 867 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 868 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 869 | "requires": { 870 | "assert-plus": "^1.0.0", 871 | "core-util-is": "1.0.2", 872 | "extsprintf": "^1.2.0" 873 | } 874 | }, 875 | "xtend": { 876 | "version": "4.0.1", 877 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 878 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 879 | } 880 | } 881 | } 882 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CSGOFloat-DB", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "command-line-args": "^5.1.1", 6 | "express": "^4.16.4", 7 | "express-rate-limit": "^3.5.0", 8 | "node-fetch": "^2.3.0", 9 | "pg": "^7.10.0", 10 | "simple-vdf": "latest", 11 | "request": "^2.88.0", 12 | "request-promise": "^4.2.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /profile_fetcher.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise'); 2 | const utils = require('./utils'); 3 | 4 | const BASE_PROFILE_SUMMARY_URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/'; 5 | 6 | class ExpiringDictionary { 7 | constructor(expirationMs) { 8 | this.dict = {}; 9 | this.expirationMs = expirationMs; 10 | } 11 | 12 | put(key, value) { 13 | this.dict[key] = { 14 | data: value, 15 | expires: Date.now() + this.expirationMs, 16 | } 17 | } 18 | 19 | has(key) { 20 | return key in this.dict && this.dict[key].expires > Date.now(); 21 | } 22 | 23 | get(key) { 24 | if (!(key in this.dict)) return; 25 | 26 | if (this.dict[key].expires < Date.now()) { 27 | delete this.dict[key]; 28 | return; 29 | } 30 | 31 | return this.dict[key].data; 32 | } 33 | } 34 | 35 | class ProfileFetcher { 36 | constructor(pool, steamApiKeys, cacheExpiringMs) { 37 | this.pool = pool; 38 | this.steamApiKeys = steamApiKeys; 39 | 40 | this.cache = new utils.ExpiringDictionary(cacheExpiringMs); 41 | } 42 | 43 | async getProfilesForSteamIds(steamIds) { 44 | const nonCachedSteamIds = steamIds.filter(steamId => !this.cache.has(steamId)); 45 | 46 | const chunkedIds = utils.chunkArray(nonCachedSteamIds, 100); 47 | const requestPromises = []; 48 | 49 | for (const idChunk of chunkedIds) { 50 | requestPromises.push(request({ 51 | uri: `${BASE_PROFILE_SUMMARY_URL}?key=${this._getRandomApiKey()}&steamids=${idChunk.join(',')}`, 52 | json: true 53 | }).catch(() => { 54 | console.error(`Failed to retrieve steam profile list`); 55 | return {}; 56 | })); 57 | } 58 | 59 | await Promise.all(requestPromises).then(responses => { 60 | for (const response of responses) { 61 | if (!response.response || !response.response.players) { 62 | continue; 63 | } 64 | 65 | for (const player of response.response.players) { 66 | this.cache.put(player.steamid, player); 67 | } 68 | } 69 | }); 70 | 71 | return steamIds.filter(steamId => this.cache.has(steamId)) 72 | .map(steamId => this.cache.get(steamId)) 73 | .reduce((map, profile) => { 74 | map[profile.steamid] = profile; 75 | return map; 76 | }, {}); 77 | } 78 | 79 | _getRandomApiKey() { 80 | return this.steamApiKeys[Math.floor(Math.random() * this.steamApiKeys.length)]; 81 | } 82 | 83 | canFetch() { 84 | return this.steamApiKeys.length > 0; 85 | } 86 | } 87 | 88 | module.exports = ProfileFetcher; 89 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | class ExpiringDictionary { 2 | constructor(expirationMs) { 3 | this.dict = {}; 4 | this.expirationMs = expirationMs; 5 | 6 | setInterval(() => this.cleanup(), expirationMs); 7 | } 8 | 9 | put(key, value) { 10 | this.dict[key] = { 11 | data: value, 12 | expires: Date.now() + this.expirationMs, 13 | } 14 | } 15 | 16 | has(key) { 17 | return key in this.dict && this.dict[key].expires > Date.now(); 18 | } 19 | 20 | get(key) { 21 | if (!(key in this.dict)) return; 22 | 23 | if (this.dict[key].expires < Date.now()) { 24 | delete this.dict[key]; 25 | return; 26 | } 27 | 28 | return this.dict[key].data; 29 | } 30 | 31 | cleanup() { 32 | for (const key of Object.keys(this.dict)) { 33 | if (this.dict[key].expires < Date.now()) { 34 | delete this.dict[key]; 35 | } 36 | } 37 | } 38 | } 39 | 40 | module.exports.ExpiringDictionary = ExpiringDictionary; 41 | 42 | module.exports.chunkArray = (arr, size) => arr.reduce((chunks, el, i) => (i % size 43 | ? chunks[chunks.length - 1].push(el) 44 | : chunks.push([el])) && chunks, []); 45 | --------------------------------------------------------------------------------