/* jslint node: true */ 'use strict'; // ENiGMA½ const Config = require('./config.js').get; const Log = require('./logger.js').log; const { getMessageAreaByTag, getMessageConferenceByTag } = require('./message_area.js'); const clientConnections = require('./client_connections.js'); const StatLog = require('./stat_log.js'); const FileBaseFilters = require('./file_base_filter.js'); const { formatByteSize } = require('./string_util.js'); const ANSI = require('./ansi_term.js'); const UserProps = require('./user_property.js'); const SysProps = require('./system_property.js'); const SysLogKeys = require('./system_log.js'); const ActivityPubSettings = require('./activitypub/settings'); const { userNameToSubject } = require('./activitypub/util'); const { getServer } = require('./listening_server'); // deps const packageJson = require('../package.json'); const os = require('os'); const _ = require('lodash'); const moment = require('moment'); const async = require('async'); exports.getPredefinedMCIValue = getPredefinedMCIValue; exports.getPredefinedMCIFormatObject = getPredefinedMCIFormatObject; exports.init = init; function init(cb) { async.series( [ callback => { return setNextRandomRumor(callback); }, callback => { // by fetching a memory or load we'll force a refresh now StatLog.getSystemStat(SysProps.SystemMemoryStats); return callback(null); }, ], err => { return cb(err); } ); } // :TODO: move this to stat_log.js like system memory is handled function setNextRandomRumor(cb) { StatLog.getSystemLogEntries( SysLogKeys.UserAddedRumorz, StatLog.Order.Random, 1, (err, entry) => { if (entry) { entry = entry[0]; } const randRumor = entry && entry.log_value ? entry.log_value : ''; StatLog.setNonPersistentSystemStat(SysProps.NextRandomRumor, randRumor); if (cb) { return cb(null); } } ); } function getUserRatio(client, propA, propB) { const a = StatLog.getUserStatNum(client.user, propA); const b = StatLog.getUserStatNum(client.user, propB); const ratio = ~~((a / b) * 100); return `${ratio}%`; } function userStatAsString(client, statName, defaultValue) { return (StatLog.getUserStat(client.user, statName) || defaultValue).toLocaleString(); } function toNumberWithCommas(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function userStatAsCountString(client, statName, defaultValue) { const value = StatLog.getUserStatNum(client.user, statName) || defaultValue; return toNumberWithCommas(value); } // lazy cache let cachedWebServer; function getWebServer() { if (undefined === cachedWebServer) { cachedWebServer = getServer('codes.l33t.enigma.web.server'); } return cachedWebServer ? cachedWebServer.instance : null; } const PREDEFINED_MCI_GENERATORS = { // // Board // BN: function boardName() { return Config().general.boardName; }, // ENiGMA VL: function versionLabel() { return 'ENiGMA½ v' + packageJson.version; }, VN: function version() { return packageJson.version; }, // +op info SN: function opUserName() { return StatLog.getSystemStat(SysProps.SysOpUsername); }, SR: function opRealName() { return StatLog.getSystemStat(SysProps.SysOpRealName); }, SL: function opLocation() { return StatLog.getSystemStat(SysProps.SysOpLocation); }, SA: function opAffils() { return StatLog.getSystemStat(SysProps.SysOpAffiliations); }, SS: function opSex() { return StatLog.getSystemStat(SysProps.SysOpSex); }, SE: function opEmail() { return StatLog.getSystemStat(SysProps.SysOpEmailAddress); }, // // Current user / session // UN: function userName(client) { return client.user.username; }, UI: function userId(client) { return client.user.userId.toString(); }, UG: function groups(client) { return _.values(client.user.groups).join(', '); }, UR: function realName(client) { return userStatAsString(client, UserProps.RealName, ''); }, AS: function activityPubSubjectName(client) { const activityPubSettings = ActivityPubSettings.fromUser(client.user); if (!activityPubSettings.enabled) { return '(disabled)'; } const webServer = getWebServer(); if (!webServer) { return 'N/A'; } return userNameToSubject(client.user.username); }, LO: function location(client) { return userStatAsString(client, UserProps.Location, ''); }, UA: function age(client) { return client.user.getAge().toString(); }, BD: function birthdate(client) { // iNiQUiTY return moment(client.user.properties[UserProps.Birthdate]).format( client.currentTheme.helpers.getDateFormat() ); }, US: function sex(client) { return userStatAsString(client, UserProps.Sex, ''); }, UE: function emailAddress(client) { return userStatAsString(client, UserProps.EmailAddress, ''); }, UW: function webAddress(client) { return userStatAsString(client, UserProps.WebAddress, ''); }, UF: function affils(client) { return userStatAsString(client, UserProps.Affiliations, ''); }, UT: function themeName(client) { return _.get( client, 'currentTheme.info.name', userStatAsString(client, UserProps.ThemeId, '') ); }, UD: function themeId(client) { return userStatAsString(client, UserProps.ThemeId, ''); }, UC: function loginCount(client) { return userStatAsCountString(client, UserProps.LoginCount, 0); }, ND: function connectedNode(client) { return client.node.toString(); }, IP: function clientIpAddress(client) { return client.friendlyRemoteAddress(); }, ST: function serverName(client) { return client.session.serverName; }, FN: function activeFileBaseFilterName(client) { const activeFilter = FileBaseFilters.getActiveFilter(client); return activeFilter ? activeFilter.name : '(Unknown)'; }, DN: function userNumDownloads(client) { return userStatAsCountString(client, UserProps.FileDlTotalCount, 0); }, // Obv/2 DK: function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes const byteSize = StatLog.getUserStatNum(client.user, UserProps.FileDlTotalBytes); return formatByteSize(byteSize, true); // true=withAbbr }, UP: function userNumUploads(client) { return userStatAsCountString(client, UserProps.FileUlTotalCount, 0); }, // Obv/2 UK: function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes const byteSize = StatLog.getUserStatNum(client.user, UserProps.FileUlTotalBytes); return formatByteSize(byteSize, true); // true=withAbbr }, NR: function userUpDownRatio(client) { // Obv/2 return getUserRatio( client, UserProps.FileUlTotalCount, UserProps.FileDlTotalCount ); }, KR: function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio return getUserRatio( client, UserProps.FileUlTotalBytes, UserProps.FileDlTotalBytes ); }, MS: function accountCreated(client) { return moment(client.user.properties[UserProps.AccountCreated]).format( client.currentTheme.helpers.getDateFormat() ); }, PS: function userPostCount(client) { return userStatAsCountString(client, UserProps.MessagePostCount, 0); }, PC: function userPostCallRatio(client) { return getUserRatio(client, UserProps.MessagePostCount, UserProps.LoginCount); }, MD: function currentMenuDescription(client) { return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : ''; }, MA: function messageAreaName(client) { const area = getMessageAreaByTag( client.user.properties[UserProps.MessageAreaTag] ); return area ? area.name : ''; }, MC: function messageConfName(client) { const conf = getMessageConferenceByTag( client.user.properties[UserProps.MessageConfTag] ); return conf ? conf.name : ''; }, ML: function messageAreaDescription(client) { const area = getMessageAreaByTag( client.user.properties[UserProps.MessageAreaTag] ); return area ? area.desc : ''; }, CM: function messageConfDescription(client) { const conf = getMessageConferenceByTag( client.user.properties[UserProps.MessageConfTag] ); return conf ? conf.desc : ''; }, SH: function termHeight(client) { return client.term.termHeight.toString(); }, SW: function termWidth(client) { return client.term.termWidth.toString(); }, AC: function achievementCount(client) { return userStatAsCountString(client, UserProps.AchievementTotalCount, 0); }, AP: function achievementPoints(client) { return userStatAsCountString(client, UserProps.AchievementTotalPoints, 0); }, DR: function doorRuns(client) { return userStatAsCountString(client, UserProps.DoorRunTotalCount, 0); }, DM: function doorFriendlyRunTime(client) { const minutes = client.user.properties[UserProps.DoorRunTotalMinutes] || 0; return moment.duration(minutes, 'minutes').humanize(); }, TO: function friendlyTotalTimeOnSystem(client) { const minutes = client.user.properties[UserProps.MinutesOnlineTotalCount] || 0; return moment.duration(minutes, 'minutes').humanize(); }, NM: function userNewMessagesAddressedToCount(client) { return StatLog.getUserStatNumByClient( client, UserProps.NewAddressedToMessageCount ); }, NP: function userNewPrivateMailCount(client) { return StatLog.getUserStatNumByClient(client, UserProps.NewPrivateMailCount); }, IA: function userStatusAvailableIndicator(client) { const indicators = client.currentTheme.helpers.getStatusAvailIndicators(); return client.user.isAvailable() ? indicators[0] || 'Y' : indicators[1] || 'N'; }, IV: function userStatusVisibleIndicator(client) { const indicators = client.currentTheme.helpers.getStatusVisibleIndicators(); return client.user.isVisible() ? indicators[0] || 'Y' : indicators[1] || 'N'; }, // // Date/Time // DT: function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); }, CT: function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()); }, // // OS/System Info // // https://github.com/nodejs/node-v0.x-archive/issues/25769 // OS: function operatingSystem() { return ( { linux: 'Linux', darwin: 'OS X', win32: 'Windows', sunos: 'SunOS', freebsd: 'FreeBSD', android: 'Android', openbsd: 'OpenBSD', aix: 'IBM AIX', }[os.platform()] || os.type() ); }, OA: function systemArchitecture() { return os.arch(); }, SC: function systemCpuModel() { // // Clean up CPU strings a bit for better display // return os .cpus()[0] .model.replace(/\(R\)|\(TM\)|processor|CPU/gi, '') .replace(/\s+(?= )/g, '') .trim(); }, MB: function totalMemoryBytes() { const stats = StatLog.getSystemStat(SysProps.SystemMemoryStats) || { totalBytes: 0, }; return formatByteSize(stats.totalBytes, true); // true=withAbbr }, MF: function totalMemoryFreeBytes() { const stats = StatLog.getSystemStat(SysProps.SystemMemoryStats) || { freeBytes: 0, }; return formatByteSize(stats.freeBytes, true); // true=withAbbr }, LA: function systemLoadAverage() { const stats = StatLog.getSystemStat(SysProps.SystemLoadStats) || { average: 0.0 }; return stats.average.toLocaleString(); }, CL: function systemCurrentLoad() { const stats = StatLog.getSystemStat(SysProps.SystemLoadStats) || { current: 0 }; return `${stats.current}%`; }, UU: function systemUptime() { return moment.duration(process.uptime(), 'seconds').humanize(); }, NV: function nodeVersion() { return process.version; }, AN: function activeNodes() { return clientConnections .getActiveConnections(clientConnections.UserVisibleConnections) .length.toString(); }, TC: function totalCalls() { return StatLog.getSystemStat(SysProps.LoginCount).toLocaleString(); }, TT: function totalCallsToday() { return StatLog.getSystemStat(SysProps.LoginsToday).toLocaleString(); }, PI: function processBytesIngress() { const stats = StatLog.getSystemStat(SysProps.ProcessTrafficStats) || { ingress: 0, }; return stats.ingress.toLocaleString(); }, PE: function processBytesEgress() { const stats = StatLog.getSystemStat(SysProps.ProcessTrafficStats) || { egress: 0, }; return stats.ingress.toLocaleString(); }, RR: function randomRumor() { // start the process of picking another random one setNextRandomRumor(); return StatLog.getSystemStat('random_rumor'); }, // // System File Base, Up/Download Info // SD: function systemNumDownloads() { return StatLog.getFriendlySystemStat(SysProps.FileDlTotalCount, 0); }, SO: function systemByteDownload() { const byteSize = StatLog.getSystemStatNum(SysProps.FileDlTotalBytes); return formatByteSize(byteSize, true); // true=withAbbr }, SU: function systemNumUploads() { return StatLog.getFriendlySystemStat(SysProps.FileUlTotalCount, 0); }, SP: function systemByteUpload() { const byteSize = StatLog.getSystemStatNum(SysProps.FileUlTotalBytes); return formatByteSize(byteSize, true); // true=withAbbr }, TF: function totalFilesOnSystem() { const areaStats = StatLog.getSystemStat(SysProps.FileBaseAreaStats); return _.get(areaStats, 'totalFiles', 0).toLocaleString(); }, TB: function totalBytesOnSystem() { const areaStats = StatLog.getSystemStat(SysProps.FileBaseAreaStats); const totalBytes = parseInt(_.get(areaStats, 'totalBytes', 0)); return formatByteSize(totalBytes, true); // true=withAbbr }, PT: function messagesPostedToday() { // Obv/2 return StatLog.getFriendlySystemStat(SysProps.MessagesToday, 0); }, TP: function totalMessagesOnSystem() { // Obv/2 return StatLog.getFriendlySystemStat(SysProps.MessageTotalCount, 0); }, FT: function totalUploadsToday() { // Obv/2 return StatLog.getFriendlySystemStat(SysProps.FileUlTodayCount, 0); }, FB: function totalUploadBytesToday() { const byteSize = StatLog.getSystemStatNum(SysProps.FileUlTodayBytes); return formatByteSize(byteSize, true); // true=withAbbr }, DD: function totalDownloadsToday() { // iNiQUiTY return StatLog.getFriendlySystemStat(SysProps.FileDlTodayCount, 0); }, DB: function totalDownloadBytesToday() { const byteSize = StatLog.getSystemStatNum(SysProps.FileDlTodayBytes); return formatByteSize(byteSize, true); // true=withAbbr }, NT: function totalNewUsersToday() { // Obv/2 return StatLog.getSystemStatNum(SysProps.NewUsersTodayCount); }, // :TODO: TZ - Average *system* post/call ratio (iNiQUiTY) // :TODO: ?? - Total users on system TU: function totalSystemUsers() { return StatLog.getSystemStatNum(SysProps.TotalUserCount) || 1; }, LC: function lastCallerUserName() { // Obv/2 const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; return lastLogin.userName || 'N/A'; }, LD: function lastCallerDate(client) { const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; if (!lastLogin.timestamp) { return 'N/A'; } return lastLogin.timestamp.format(client.currentTheme.helpers.getDateFormat()); }, LT: function lastCallerTime(client) { const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; if (!lastLogin.timestamp) { return 'N/A'; } return lastLogin.timestamp.format(client.currentTheme.helpers.getTimeFormat()); }, // // Special handling for XY // XY: function xyHack() { return; /* nothing */ }, // // Various movement by N // CF: function cursorForwardBy(client, n = 1) { return ANSI.forward(n); }, CB: function cursorBackBy(client, n = 1) { return ANSI.back(n); }, CU: function cursorUpBy(client, n = 1) { return ANSI.up(n); }, CD: function cursorDownBy(client, n = 1) { return ANSI.down(n); }, }; function getPredefinedMCIValue(client, code, extra) { if (!client || !code) { return; } const generator = PREDEFINED_MCI_GENERATORS[code]; if (generator) { let value; try { value = generator(client, extra); } catch (e) { Log.error( { code: code, exception: e.message }, `Failed generating predefined MCI value (${code})` ); } return value; } } function getPredefinedMCIFormatObject(client, text) { const re = /\{([A-Z]{2})(?:[!:][^}]+)?\}/g; let m; const formatObj = {}; while ((m = re.exec(text))) { const v = getPredefinedMCIValue(client, m[1]); if (v) { if (!isNaN(v)) { formatObj[m[1]] = parseInt(v); } else { formatObj[m[1]] = v; } } } return _.isEmpty(formatObj) ? null : formatObj; }