require('./src/scripts/server/boot'); const mongoose = require('mongoose'); const fs = require('fs'); const _ = require('lodash'); const { decompressPony } = require('./src/scripts/common/compressPony'); const ponyInfo = require('./src/scripts/common/ponyInfo'); const db = require('./src/scripts/server/db'); const config = require('./src/scripts/server/config').config; const argv = require('yargs').option('addrole', { type: 'array' }).option('removerole', { type: 'array' }).argv; mongoose.connect(config.db, { reconnectTries: Number.MAX_VALUE, useNewUrlParser: true, useCreateIndex: true, useFindAndModify: false, }); function iterate(query, onData, onEnd = () => process.exit()) { query.cursor().on('data', onData).on('end', onEnd); } function generatePonyStats() { const sets = {}; const fields = {}; const defaultPony = ponyInfo.createDefaultPony(); const ignore = [ 'cm', 'eyeshadowColor', 'eyeWhites', 'eyeColorRight', 'eyeColorLeft', 'coatFill', 'coatOutline', 'frecklesColor', ]; const keys = Object.keys(defaultPony).filter(key => !_.includes(ignore, key)); const setFields = keys.filter(key => typeof defaultPony[key] === 'object' && 'type' in defaultPony[key]); const otherFields = keys.filter(key => typeof defaultPony[key] !== 'object' || !('type' in defaultPony[key])); let done = 0; setFields.forEach(key => sets[key] = new Map()); otherFields.forEach(key => fields[key] = new Map()); iterate(db.Character.find({}, '_id name info'), doc => { try { const pony = decompressPony(doc.info); for (const key of setFields) { const stats = sets[key]; const { type = 0, pattern = 0 } = pony[key] || {}; const row = stats.get(type) || new Map(); stats.set(type, row); row.set(pattern, 1 + (row.get(pattern) || 0)); } for (const key of otherFields) { const stats = fields[key]; const value = pony[key]; stats.set(value, 1 + (stats.get(value) || 0)); } if ((++done % 10000) === 0) { console.log('done', done); } } catch (e) { console.log(e); console.log('id: ', doc._id); console.log('info: ', doc._info); } }, () => { const setsData = _.flatten(Object.keys(sets) .sort() .map(key => { const patterns = _.range(0, 1 + _.max(entries(sets[key]).map(e => _.max(entries(e[1]).map(x => x[0]))))); return [ [], [key], ['', '', 'pattern'], ['', '', ...patterns], ...entries(sets[key]) .sort(([a], [b]) => a - b) .map(([key, value], i) => [i === 0 ? 'type' : '', key, ...patterns.map(pat => value.get(pat) || 0)]), ]; })); const fieldsData = _.flatten(Object.keys(fields) .sort() .map(key => [ [], [key], ...entries(fields[key]).sort(([a], [b]) => a - b).map(([key, value]) => ['', key, value]), ])); fs.writeFileSync('stats.csv', `${toCsv(setsData)}\n\n${toCsv(fieldsData)}`, 'utf8'); console.log('done'); process.exit(); }); function entries(map) { return Array.from(map.entries()); } function toCsv(data) { return data.map(x => x.map(v => v === undefined ? 'undefined' : v).join(';')).join('\n'); } } function fixCharacterStates() { const config = require('./config.json'); db.Character.countDocuments({ state: { $exists: true } }, (err, total) => { let migrated = 0; let updating = 0; let done = false; if (err) { console.error(err); process.exit(); } console.log('started', total); iterate(db.Character.find({ state: { $exists: true } }, '_id name state'), doc => { const before = doc.state; const after = {}; for (const { id, name } of config.servers) { const state = _.clone(before[name]); if (state) { if (!state.hold) { delete state.hold; } if (!state.map) { delete state.map; } const flags = (state.right ? 1 : 0) | (state.extra ? 2 : 0); delete state.right; delete state.extra; if (flags) { state.flags = flags; } after[id] = state; } if (before[id]) { after[id] = _.clone(before[id]); } } updating++; db.Character.update({ _id: doc._id }, { state: after }, e => { migrated++; updating--; if (e) { console.error(e); } if ((migrated % 10000) === 0) { console.log('migrated', migrated, 'of', total); } if (done && updating === 0) { console.log('ALL DONE'); } }); }, () => { done = true; console.log('done iterating'); }); }); } async function updateCharacterPositions() { const servers = ['main', 'safe', 'safe-ru']; const total = await db.Character.countDocuments({ state: { $exists: true } }).exec(); let done = 0; console.log('updating', total); iterate(db.Character.find({ state: { $exists: true } }, '_id name state'), doc => { const state = doc.state; if (state) { let changed = false; for (const server of servers) { if (state[server]) { state[server].x += 40; state[server].y += 40; changed = true; } } if (changed) { db.Character.updateOne({ _id: doc._id }, { state }, () => { done++; if ((done % 10000) === 0) { console.log(`done ${done} / ${total}`); } }); } else { done++; if ((done % 10000) === 0) { console.log(`done ${done} / ${total}`); } } } }, () => console.log('done')); } async function createPerfTestAccounts() { const ponies = await db.Character.find({ account: '57ae2336a67f4dc52e123ed1' }).exec(); for (let i = 0; i < 1000; i++) { const account = await db.Account.create({ name: `perf-${i}` }); const char = ponies[i % ponies.length]; await db.Character.create({ account: account._id, name: `perf-${i}`, info: char.info }); } process.exit(); } function settingsStats() { let filterSwearWords = [0, 0]; let filterCyrillic = [0, 0]; let ignorePartyInvites = [0, 0]; let ignorePublicChat = [0, 0]; let seeThroughObjects = [0, 0]; let chatlogOpacity = _.times(100, () => 0); let chatlogRange = _.times(13, () => 0); let filterWords = []; iterate(db.Account.find({}, 'settings'), doc => { if (doc.settings) { filterSwearWords[doc.settings.filterSwearWords | 0]++; filterCyrillic[doc.settings.filterCyrillic | 0]++; ignorePartyInvites[doc.settings.ignorePartyInvites | 0]++; ignorePublicChat[doc.settings.ignorePublicChat | 0]++; seeThroughObjects[doc.settings.seeThroughObjects | 0]++; chatlogOpacity[doc.settings.chatlogOpacity | 0]++; chatlogRange[doc.settings.chatlogRange | 0]++; doc.settings.filterWords && filterWords.push(doc.settings.filterWords); } }, () => { const pad = 20; filterWords.forEach(x => console.log(JSON.stringify(x))); console.log('---------------------------------------------'); console.log(`${_.padStart(`filterSwearWords`, pad)}: ${filterSwearWords.join(', ')}`); console.log(`${_.padStart(`filterCyrillic`, pad)}: ${filterCyrillic.join(', ')}`); console.log(`${_.padStart(`ignorePartyInvites`, pad)}: ${ignorePartyInvites.join(', ')}`); console.log(`${_.padStart(`ignorePublicChat`, pad)}: ${ignorePublicChat.join(', ')}`); console.log(`${_.padStart(`seeThroughObjects`, pad)}: ${seeThroughObjects.join(', ')}`); console.log(`${_.padStart(`chatlogRange`, pad)}: ${chatlogRange.join(', ')}`); console.log(`${_.padStart(`chatlogOpacity`, pad)}: ${chatlogOpacity.join(', ')}`); console.log('done'); process.exit(); }); } async function clearOrphanedIgnores() { const ids = new Set(); let removed = 0; iterate(db.Account.find({}, '_id').lean(), doc => ids.add(doc._id.toString()), () => { iterate(db.Account.find({ ignores: { $exists: true, $not: { $size: 0 } } }, '_id ignores').lean(), doc => { const remove = []; if (doc.ignores) { for (const ignore of doc.ignores) { if (!ids.has(ignore) || doc._id.toString() === ignore) { remove.push(ignore); } } } if (remove.length) { removed += remove.length; db.Account.updateOne({ _id: doc._id }, { $pull: { ignores: remove } }, err => console.log(err || 'removed')); } }, () => { console.log('done, removed: ', removed); process.exit(); }); }); } async function toggleRole(give, id, roles) { if (typeof id === 'string' && id.length === 24 && ['superadmin', 'admin', 'mod', 'dev'].indexOf(roles) !== -1) { if (give) { await db.Account.updateOne({ _id: id }, { $addToSet: { roles } }).exec(); } else { await db.Account.updateOne({ _id: id }, { $pull: { roles } }).exec(); } console.log('done'); } else { console.log('error: usage: node cli.js --addrole '); console.log(' roles: superadmin, admin, mod, dev'); } process.exit(); } if (argv.settingsstats) { settingsStats(); } else if (argv.shift) { updateCharacterPositions(); } else if (argv.clearignores) { clearOrphanedIgnores(); } else if (argv.addrole) { toggleRole(true, argv.addrole[0], argv.addrole[1]); } else if (argv.removerole) { toggleRole(false, argv.removerole[0], argv.removerole[1]); } else { console.log('invalid arg'); process.exit(); }