// ── The voice the module's own lines are said in (phase 17, D140) ────────── // // One fleet setting (§33.4 reading 5): the name of a permission group that has // a BetterChat style, or nothing for plain chat. News lines and `rust.announce` // lines are then said in that group's title and colours — composed here, said by // our plugin with no player as the sender, so they look right whether or not // BetterChat is loaded. // // The style is read at the moment a line is said, never copied into the // setting: an operator who recolours the group changes the voice with it, and a // group whose style is taken away stops being a voice rather than leaving a // stale one behind. const settingsDb = require('../visibility/visibility.db') const chatStyle = require('./chatStyle') const db = require('./permissions.db') const model = require('./permissions.model') const VOICE_KEY = 'announce.voice' /** * The chosen group's id as a string, or '' for plain chat. * * Since groups became per server (D189) a name can belong to several groups, so * the setting holds a group's id. A setting written before that holds a NAME, * and is read as the first styled group of that name until somebody chooses again. */ async function chosen() { return (await settingsDb.getSetting(VOICE_KEY)) || '' } /** The chosen group's style fields, or null. */ async function chosenFields() { const value = await chosen() if (!value) return { value, fields: null } if (/^\d+$/.test(value)) return { value, fields: await db.getGroupChat(Number(value)) } const groups = await db.listGroups() for (const group of groups.filter((g) => g.name === model.normaliseName(value))) { // eslint-disable-next-line no-await-in-loop const fields = await db.getGroupChat(group.id) if (fields) return { value: String(group.id), fields } } return { value, fields: null } } /** * The format a line is said in right now, or null for plain chat — which is * also the answer when the chosen group has since lost its style or gone. */ async function currentFormat() { const { fields } = await chosenFields() return fields ? chatStyle.voiceFormat(fields) : null } /** The setting, and every group that could be a voice, for the admin page. */ async function describe() { const [{ value }, rows, groups, groupServers] = await Promise.all([ chosenFields(), db.listGroupChat(), db.listGroups(), db.listGroupServers(), ]) const byId = new Map(groups.map((g) => [g.id, g])) const options = [] // Which servers each group is on, so two groups of one name can be told apart. const where = (group) => { if (group.allServers) return 'all servers' const ids = groupServers.filter((r) => r.groupId === group.id && r.included).map((r) => r.serverId) return ids.length ? ids.join(', ') : 'no server' } for (const [groupId, fields] of model.chatByGroup(rows)) { const format = chatStyle.voiceFormat(fields) const group = byId.get(groupId) if (format && group) { options.push({ group: String(groupId), name: group.name, where: where(group), title: fields.Title || group.name, format }) } } return { voice: value, options: options.sort((a, b) => a.name.localeCompare(b.name) || a.group.localeCompare(b.group)) } } /** * Choose the voice by group id. A group is accepted only when it has a style a * voice can be made from; '' goes back to plain chat. Resolves `{ ok }` or * `{ ok: false, message }`. */ async function choose(group, userId = null) { const value = String(group || '').trim() if (value) { const fields = /^\d+$/.test(value) ? await db.getGroupChat(Number(value)) : null if (!fields || !chatStyle.voiceFormat(fields)) { return { ok: false, message: 'That group has no chat style, so it cannot be a voice. Give it one under Permissions first.' } } } await settingsDb.setSetting(VOICE_KEY, value, userId) return { ok: true, voice: value } } module.exports = { VOICE_KEY, chosen, currentFormat, describe, choose }