Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
{
"version": 3,
"sources": ["../../../server/private-messages/index.ts"],
"sourcesContent": ["/**\n * Private message handling, particularly for offline messages.\n * By Mia.\n * @author mia-pi-git\n */\nimport { SQL, Utils } from '../../lib';\nimport { Config } from '../config-loader';\nimport { Auth } from '../user-groups';\nimport { statements } from './database';\n/** The time until a PM sent offline expires. Presently, 60 days. */\nexport const EXPIRY_TIME = 60 * 24 * 60 * 60 * 1000;\n/** The time until a PM that has been seen by the user expires. Presently, one week. */\nexport const SEEN_EXPIRY_TIME = 7 * 24 * 60 * 60 * 1000;\n/** The max PMs that one user can have pending to a specific user at one time */\nexport const MAX_PENDING = 20;\n\n// this would be in database.ts, but for some weird reason, if the extension and the pm are the same\n// it doesn't work. all the keys in the require() result are there, but they're also set to undefined.\n// no idea why.\nexport const PM = SQL(module, {\n\tfile: 'databases/offline-pms.db',\n\textension: 'server/private-messages/database.js',\n});\n\nexport interface ReceivedPM {\n\ttime: number;\n\tsender: string;\n\treceiver: string;\n\tseen: number | null;\n\tmessage: string;\n}\n\nexport const PrivateMessages = new class {\n\tdatabase = PM;\n\tclearInterval = this.nextClear();\n\tofflineIsEnabled = Config.usesqlitepms && Config.usesqlite;\n\tasync sendOffline(to: string, from: User | string, message: string, context?: Chat.CommandContext) {\n\t\tawait this.checkCanSend(to, from);\n\t\tconst result = await PM.transaction('send', [toID(from), toID(to), message]);\n\t\tif (result.error) throw new Chat.ErrorMessage(result.error);\n\t\tif (typeof from === 'object') {\n\t\t\tfrom.send(`|pm|${this.getIdentity(from)}|${this.getIdentity(to)}|${message} __[sent offline]__`);\n\t\t}\n\t\tconst changed = !!result.changes;\n\t\tif (changed && context) {\n\t\t\tChat.runHandlers('onMessageOffline', context, message, toID(to));\n\t\t}\n\t\treturn changed;\n\t}\n\tgetSettings(userid: string) {\n\t\treturn PM.get(statements.getSettings, [toID(userid)]);\n\t}\n\tdeleteSettings(userid: string) {\n\t\treturn PM.run(statements.deleteSettings, [toID(userid)]);\n\t}\n\tasync checkCanSend(to: string, from: User | string) {\n\t\tfrom = toID(from);\n\t\tto = toID(to);\n\t\tconst setting = await this.getSettings(to);\n\t\tconst requirement = setting?.view_only || Config.usesqlitepms || \"friends\";\n\t\tswitch (requirement) {\n\t\tcase 'friends':\n\t\t\tif (!(await Chat.Friends.findFriendship(to, from))) {\n\t\t\t\tif (Config.usesqlitepms === 'friends') {\n\t\t\t\t\tthrow new Chat.ErrorMessage(`At this time, you may only send offline PMs to friends. ${to} is not friends with you.`);\n\t\t\t\t}\n\t\t\t\tthrow new Chat.ErrorMessage(`${to} is only accepting offline PMs from friends at this time.`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'trusted':\n\t\t\tif (!Users.globalAuth.has(toID(from))) {\n\t\t\t\tthrow new Chat.ErrorMessage(`${to} is currently blocking offline PMs from non-trusted users.`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'none':\n\t\t\t// drivers+ can override\n\t\t\tif (!Auth.atLeast(Users.globalAuth.get(from as ID), '%')) {\n\t\t\t\tthrow new Chat.ErrorMessage(`${to} has indicated that they do not wish to receive offline PMs.`);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (!Auth.atLeast(Users.globalAuth.get(from as ID), requirement)) {\n\t\t\t\tif (setting?.view_only) {\n\t\t\t\t\tthrow new Chat.ErrorMessage(`That user is not allowing offline PMs from your rank at this time.`);\n\t\t\t\t}\n\t\t\t\tthrow new Chat.ErrorMessage('You do not meet the rank requirement to send offline PMs at this time.');\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\tsetViewOnly(user: User | string, val: string | null) {\n\t\tconst id = toID(user);\n\t\tif (!val) { // if null, no need to save\n\t\t\treturn PM.run(statements.deleteSettings, [id]);\n\t\t}\n\t\treturn PM.run(statements.setBlock, [id, val]);\n\t}\n\tcheckCanUse(user: User, options = { forceBool: false, isLogin: false }) {\n\t\tif (!this.offlineIsEnabled) {\n\t\t\tif (options.forceBool) return false;\n\t\t\tthrow new Chat.ErrorMessage(`Offline PMs are currently disabled.`);\n\t\t}\n\t\tif (!(options.isLogin ? user.registered : user.autoconfirmed)) {\n\t\t\tif (options.forceBool) return false;\n\t\t\tthrow new Chat.ErrorMessage(\"You must be autoconfirmed to use offline messaging.\");\n\t\t}\n\t\tif (!Users.globalAuth.atLeast(user, Config.usesqlitepms)) {\n\t\t\tif (options.forceBool) return false;\n\t\t\tthrow new Chat.ErrorMessage(\"You do not have the needed rank to send offline PMs.\");\n\t\t}\n\t\treturn true;\n\t}\n\tcheckCanPM(user: User, pmTarget: ID) {\n\t\tthis.checkCanUse(user);\n\t\tif (Config.usesqlitepms === 'friends' && !user.friends?.has(pmTarget)) {\n\t\t\tthrow new Chat.ErrorMessage(\n\t\t\t\t`At this time, you may only send offline messages to friends. You do not have ${pmTarget} friended.`\n\t\t\t);\n\t\t}\n\t}\n\tasync sendReceived(user: User) {\n\t\tconst userid = toID(user);\n\t\t// we only want to send the unseen pms to them when they login - they can replay the rest at will otherwise\n\t\tconst messages = await this.fetchUnseen(userid);\n\t\tfor (const { message, time, sender } of messages) {\n\t\t\tuser.send(\n\t\t\t\t`|pm|${this.getIdentity(sender)}|${this.getIdentity(user)}|/html ` +\n\t\t\t\t`${Utils.escapeHTML(message)} <i>[sent offline, <time>${new Date(time).toISOString()}</time>]</i>`\n\t\t\t);\n\t\t}\n\t}\n\tprivate getIdentity(user: User | string) {\n\t\tuser = Users.getExact(user) || user;\n\t\tif (typeof user === 'object') {\n\t\t\treturn user.getIdentity();\n\t\t}\n\t\treturn `${Users.globalAuth.get(toID(user))}${user}`;\n\t}\n\tnextClear(): NodeJS.Timeout {\n\t\tif (!PM.isParentProcess) return null!;\n\t\tconst time = Date.now();\n\t\t// even though we expire once a week atm, we check once a day\n\t\tconst nextMidnight = new Date();\n\t\tnextMidnight.setHours(24, 0, 0, 0);\n\t\tif (this.clearInterval) clearTimeout(this.clearInterval);\n\t\tthis.clearInterval = setTimeout(() => {\n\t\t\tvoid this.clearOffline();\n\t\t\tvoid this.clearSeen();\n\t\t\tthis.nextClear();\n\t\t}, nextMidnight.getTime() - time);\n\t\treturn this.clearInterval;\n\t}\n\tclearSeen() {\n\t\treturn PM.run(statements.clearSeen, [Date.now(), SEEN_EXPIRY_TIME]);\n\t}\n\tsend(message: string, user: User, pmTarget: User, onlyRecipient: User | null = null) {\n\t\tconst buf = `|pm|${user.getIdentity()}|${pmTarget.getIdentity()}|${message}`;\n\t\tif (onlyRecipient) return onlyRecipient.send(buf);\n\t\tuser.send(buf);\n\t\tif (pmTarget !== user) pmTarget.send(buf);\n\t\tpmTarget.lastPM = user.id;\n\t\tuser.lastPM = pmTarget.id;\n\t}\n\tasync fetchUnseen(user: User | string): Promise<ReceivedPM[]> {\n\t\tconst userid = toID(user);\n\t\treturn (await PM.transaction('listNew', [userid])) || [];\n\t}\n\tasync fetchAll(user: User | string): Promise<ReceivedPM[]> {\n\t\treturn (await PM.all(statements.fetch, [toID(user)])) || [];\n\t}\n\tasync renderReceived(user: User) {\n\t\tconst all = await this.fetchAll(user);\n\t\tlet buf = `<div class=\"ladder pad\">`;\n\t\tbuf += `<h2>PMs received offline in the last ${Chat.toDurationString(SEEN_EXPIRY_TIME)}</h2>`;\n\t\tconst sortedPMs: { [userid: string]: ReceivedPM[] } = {};\n\t\tfor (const curPM of all) {\n\t\t\tif (!sortedPMs[curPM.sender]) sortedPMs[curPM.sender] = [];\n\t\t\tsortedPMs[curPM.sender].push(curPM);\n\t\t}\n\t\tfor (const k in sortedPMs) {\n\t\t\tUtils.sortBy(sortedPMs[k], pm => -pm.time);\n\t\t}\n\t\tbuf += `<div class=\"mainmenuwrapper\" style=\"margin-left:40px\">`;\n\t\tfor (const pair of Utils.sortBy(Object.entries(sortedPMs), ([id]) => id)) {\n\t\t\tconst [sender, messages] = pair;\n\t\t\tconst group = Users.globalAuth.get(toID(sender));\n\t\t\tconst name = Users.getExact(sender)?.name || sender;\n\t\t\tconst id = toID(name);\n\t\t\tbuf += Utils.html`<div class=\"pm-window pm-window-${id}\" width=\"30px\" data-userid=\"${id}\" data-name=\"${group}${name}\" style=\"width:300px\">`;\n\t\t\tbuf += Utils.html`<h3><small>${group}</small>${name}</h3>`;\n\t\t\tbuf += `<div class=\"pm-log\"><div class=\"pm-buttonbar\">`;\n\t\t\tfor (const { message, time } of messages) {\n\t\t\t\tbuf += `<div class=\"chat chatmessage-${toID(sender)}\">&nbsp;&nbsp;`;\n\t\t\t\tbuf += `<small>[<time>${new Date(time).toISOString()}</time>] </small>`;\n\t\t\t\tbuf += Utils.html`<small>${group}</small>`;\n\t\t\t\tbuf += Utils.html`<span class=\"username\" data-roomgroup=\"${group}\" data-name=\"${name}\"><username>${name}</username></span>: `;\n\t\t\t\tbuf += `<em>${message}</em></div>`;\n\t\t\t}\n\t\t\tbuf += `</div></div></div>`;\n\t\t\tbuf += `<br />`;\n\t\t}\n\t\tbuf += `</div>`;\n\t\treturn buf;\n\t}\n\tclearOffline() {\n\t\treturn PM.run(statements.clearDated, [Date.now(), EXPIRY_TIME]);\n\t}\n\tdestroy() {\n\t\tvoid PM.destroy();\n\t}\n};\n\nif (Config.usesqlite) {\n\tif (!process.send) {\n\t\tPM.spawn(Config.pmprocesses || 1);\n\t\t// clear super old pms on startup\n\t\tvoid PM.run(statements.clearDated, [Date.now(), EXPIRY_TIME]);\n\t} else if (process.send && process.mainModule === module) {\n\t\tglobal.Monitor = {\n\t\t\tcrashlog(error: Error, source = 'A private message child process', details: AnyObject | null = null) {\n\t\t\t\tconst repr = JSON.stringify([error.name, error.message, source, details]);\n\t\t\t\tprocess.send!(`THROW\\n@!!@${repr}\\n${error.stack}`);\n\t\t\t},\n\t\t};\n\t\tprocess.on('uncaughtException', err => {\n\t\t\tMonitor.crashlog(err, 'A private message database process');\n\t\t});\n\t\tprocess.on('unhandledRejection', err => {\n\t\t\tMonitor.crashlog(err as Error, 'A private message database process');\n\t\t});\n\t}\n}\n"],
"mappings": "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",
"names": []
}