import { FS, Utils } from '../../lib';
export const ROOMFAQ_FILE = 'config/chat-plugins/faqs.json';
const MAX_ROOMFAQ_LENGTH = 8192;
export const roomFaqs: { [k: string]: { [k: string]: RoomFAQ } } = (() => {
const data = JSON.parse(FS(ROOMFAQ_FILE).readIfExistsSync() || "{}");
let save = false;
for (const k in data) {
for (const name in data[k]) {
if (typeof data[k][name] === 'string') {
data[k][name] = convertFaq(data[k][name]);
save = true;
}
}
}
if (save) saveRoomFaqs(data);
return data;
})();
interface RoomFAQ {
source: string;
alias?: boolean;
html?: boolean;
}
function saveRoomFaqs(table?: { [k: string]: { [k: string]: RoomFAQ } }) {
FS(ROOMFAQ_FILE).writeUpdate(() => JSON.stringify(table || roomFaqs));
}
function convertFaq(faq: string): RoomFAQ {
if (faq.startsWith('>')) {
return {
alias: true,
source: faq.slice(1),
};
}
return {
source: faq,
};
}
export function visualizeFaq(faq: RoomFAQ) {
if (faq.html) {
return faq.source;
}
return Chat.formatText(faq.source, true);
}
export function getAlias(roomid: RoomID, key: string) {
if (!roomFaqs[roomid]) return false;
const value = roomFaqs[roomid][key];
if (value?.alias) return value.source;
return false;
}
export const commands: Chat.ChatCommands = {
addhtmlfaq: 'addfaq',
addfaq(target, room, user, connection) {
room = this.requireRoom();
const useHTML = this.cmd.includes('html');
this.checkCan('ban', null, room);
if (useHTML && !user.can('addhtml', null, room, this.fullCmd)) {
return this.errorReply(`You are not allowed to use raw HTML in roomfaqs.`);
}
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
if (!target) return this.parse('/help roomfaq');
target = target.trim();
const input = this.filter(target);
if (target !== input) return this.errorReply("You are not allowed to use fitered words in roomfaq entries.");
let [topic, ...rest] = input.split(',');
topic = toID(topic);
if (!(topic && rest.length)) return this.parse('/help roomfaq');
let text = rest.join(',').trim();
if (topic.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
const lengthWithoutFormatting = Chat.stripFormatting(text).length;
if (lengthWithoutFormatting > MAX_ROOMFAQ_LENGTH) {
return this.errorReply(`FAQ entries must not exceed ${MAX_ROOMFAQ_LENGTH} characters.`);
} else if (lengthWithoutFormatting < 1) {
return this.errorReply(`FAQ entries must include at least one character.`);
}
if (!useHTML) {
text = text.replace(/^>/, '>');
} else {
text = text.replace(/\n/ig, ' ');
text = this.checkHTML(text);
}
if (!roomFaqs[room.roomid]) roomFaqs[room.roomid] = {};
const exists = topic in roomFaqs[room.roomid];
roomFaqs[room.roomid][topic] = {
source: text,
html: useHTML,
};
saveRoomFaqs();
this.sendReplyBox(visualizeFaq(roomFaqs[room.roomid][topic]));
this.privateModAction(`${user.name} ${exists ? 'edited' : 'added'} an FAQ for '${topic}'`);
this.modlog('RFAQ', null, `${exists ? 'edited' : 'added'} '${topic}'`);
},
removefaq(target, room, user) {
room = this.requireRoom();
this.checkChat();
this.checkCan('ban', null, room);
const topic = toID(target);
if (!topic) return this.parse('/help roomfaq');
if (!roomFaqs[room.roomid]?.[topic]) return this.errorReply("Invalid topic.");
if (
room.settings.repeats?.length &&
room.settings.repeats.filter(x => x.faq && x.id === topic).length
) {
this.parse(`/msgroom ${room.roomid},/removerepeat ${topic}`);
}
delete roomFaqs[room.roomid][topic];
Object.keys(roomFaqs[room.roomid]).filter(
val => getAlias(room.roomid, val) === topic
).map(
val => delete roomFaqs[room.roomid][val]
);
if (!Object.keys(roomFaqs[room.roomid]).length) delete roomFaqs[room.roomid];
saveRoomFaqs();
this.privateModAction(`${user.name} removed the FAQ for '${topic}'`);
this.modlog('ROOMFAQ', null, `removed ${topic}`);
this.refreshPage(`roomfaqs-${room.roomid}`);
},
addalias(target, room, user) {
room = this.requireRoom();
this.checkChat();
this.checkCan('ban', null, room);
if (!room.persist) return this.errorReply("This command is unavailable in temporary rooms.");
const [alias, topic] = target.split(',').map(val => toID(val));
if (!(alias && topic)) return this.parse('/help roomfaq');
if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
if (alias === topic) return this.errorReply("You cannot make the alias have the same name as the topic.");
if (roomFaqs[room.roomid][alias] && !roomFaqs[room.roomid][alias].alias) {
return this.errorReply("You cannot overwrite an existing topic with an alias; please delete the topic first.");
}
if (!(roomFaqs[room.roomid] && topic in roomFaqs[room.roomid])) {
return this.errorReply(`The topic ${topic} was not found in this room's faq list.`);
}
if (getAlias(room.roomid, topic)) {
return this.errorReply(`You cannot make an alias of an alias. Use /addalias ${alias}, ${getAlias(room.roomid, topic)} instead.`);
}
roomFaqs[room.roomid][alias] = {
alias: true,
source: topic,
};
saveRoomFaqs();
this.privateModAction(`${user.name} added an alias for '${topic}': ${alias}`);
this.modlog('ROOMFAQ', null, `alias for '${topic}' - ${alias}`);
},
viewfaq: 'roomfaq',
rfaq: 'roomfaq',
roomfaq(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!roomFaqs[room.roomid]) return this.errorReply("This room has no FAQ topics.");
let topic: string = toID(target);
if (topic === 'constructor') return false;
if (!topic) {
return this.parse(`/join view-roomfaqs-${room.roomid}`);
}
if (!roomFaqs[room.roomid][topic]) return this.errorReply("Invalid topic.");
topic = getAlias(room.roomid, topic) || topic;
if (!this.runBroadcast()) return;
const rfaq = roomFaqs[room.roomid][topic];
this.sendReplyBox(visualizeFaq(rfaq));
if (!this.broadcasting && user.can('ban', null, room, 'addfaq')) {
const code = Utils.escapeHTML(rfaq.source).replace(/\n/g, ' ');
const command = rfaq.html ? 'addhtmlfaq' : 'addfaq';
this.sendReplyBox(`Source/${command} ${topic}, ${code}`);
}
},
roomfaqhelp: [
`/roomfaq - Shows the list of all available FAQ topics`,
`/roomfaq - Shows the FAQ for .`,
`/addfaq , - Adds an entry for in this room or updates it. Requires: @ # ~`,
`/addhtmlfaq , - Adds or updates an entry for with HTML support. Requires: # ~`,
`/addalias , - Adds as an alias for , displaying it when users use /roomfaq . Requires: @ # ~`,
`/removefaq - Removes the entry for in this room. If used on an alias, removes the alias. Requires: @ # ~`,
],
};
export const pages: Chat.PageTable = {
roomfaqs(args, user) {
const room = this.requireRoom();
this.title = `[Room FAQs]`;
// allow it for users if they can access the room
if (!room.checkModjoin(user)) {
throw new Chat.ErrorMessage(`