First pass formatting with Prettier
* Added .prettierrc.json * Added .prettierignore * Formatted
This commit is contained in:
@@ -2,89 +2,91 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const Log = require('../../logger.js').log;
|
||||
const { ServerModule } = require('../../server_module.js');
|
||||
const Config = require('../../config.js').get;
|
||||
const { Errors } = require('../../enig_error.js');
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
stripAnsiControlCodes
|
||||
} = require('../../string_util.js');
|
||||
stripAnsiControlCodes,
|
||||
} = require('../../string_util.js');
|
||||
const {
|
||||
getMessageConferenceByTag,
|
||||
getMessageAreaByTag,
|
||||
getMessageListForArea,
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
const { wordWrapText } = require('../../word_wrap.js');
|
||||
const { stripMciColorCodes } = require('../../color_codes.js');
|
||||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
const { wordWrapText } = require('../../word_wrap.js');
|
||||
const { stripMciColorCodes } = require('../../color_codes.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
const net = require('net');
|
||||
const _ = require('lodash');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Gopher',
|
||||
desc : 'A RFC-1436-ish Gopher Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.gopher.server',
|
||||
notes : 'https://tools.ietf.org/html/rfc1436',
|
||||
};
|
||||
const ModuleInfo = (exports.moduleInfo = {
|
||||
name: 'Gopher',
|
||||
desc: 'A RFC-1436-ish Gopher Server',
|
||||
author: 'NuSkooler',
|
||||
packageName: 'codes.l33t.enigma.gopher.server',
|
||||
notes: 'https://tools.ietf.org/html/rfc1436',
|
||||
});
|
||||
|
||||
const Message = require('../../message.js');
|
||||
const Message = require('../../message.js');
|
||||
|
||||
const ItemTypes = {
|
||||
Invalid : '', // not really a type, of course!
|
||||
Invalid: '', // not really a type, of course!
|
||||
|
||||
// Canonical, RFC-1436
|
||||
TextFile : '0',
|
||||
SubMenu : '1',
|
||||
CCSONameserver : '2',
|
||||
Error : '3',
|
||||
BinHexFile : '4',
|
||||
DOSFile : '5',
|
||||
UuEncodedFile : '6',
|
||||
FullTextSearch : '7',
|
||||
Telnet : '8',
|
||||
BinaryFile : '9',
|
||||
AltServer : '+',
|
||||
GIFFile : 'g',
|
||||
ImageFile : 'I',
|
||||
Telnet3270 : 'T',
|
||||
TextFile: '0',
|
||||
SubMenu: '1',
|
||||
CCSONameserver: '2',
|
||||
Error: '3',
|
||||
BinHexFile: '4',
|
||||
DOSFile: '5',
|
||||
UuEncodedFile: '6',
|
||||
FullTextSearch: '7',
|
||||
Telnet: '8',
|
||||
BinaryFile: '9',
|
||||
AltServer: '+',
|
||||
GIFFile: 'g',
|
||||
ImageFile: 'I',
|
||||
Telnet3270: 'T',
|
||||
|
||||
// Non-canonical
|
||||
HtmlFile : 'h',
|
||||
InfoMessage : 'i',
|
||||
SoundFile : 's',
|
||||
HtmlFile: 'h',
|
||||
InfoMessage: 'i',
|
||||
SoundFile: 's',
|
||||
};
|
||||
|
||||
exports.getModule = class GopherModule extends ServerModule {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.log = Log.child( { server : 'Gopher' } );
|
||||
this.routes = new Map(); // selector->generator => gopher item
|
||||
this.log = Log.child({ server: 'Gopher' });
|
||||
}
|
||||
|
||||
createServer(cb) {
|
||||
if(!this.enabled) {
|
||||
if (!this.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
this.publicHostname = config.contentServers.gopher.publicHostname;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
this.publicPort = config.contentServers.gopher.publicPort;
|
||||
|
||||
this.addRoute(/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator);
|
||||
this.addRoute(
|
||||
/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/,
|
||||
this.messageAreaGenerator
|
||||
);
|
||||
this.addRoute(/^(\/?[^\t\r\n]*)\r\n$/, this.staticGenerator);
|
||||
|
||||
this.server = net.createServer( socket => {
|
||||
this.server = net.createServer(socket => {
|
||||
socket.setEncoding('ascii');
|
||||
|
||||
socket.on('data', data => {
|
||||
@@ -100,8 +102,9 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
});
|
||||
|
||||
socket.on('error', err => {
|
||||
if('ECONNRESET' !== err.code) { // normal
|
||||
this.log.trace( { error : err.message }, 'Socket error');
|
||||
if ('ECONNRESET' !== err.code) {
|
||||
// normal
|
||||
this.log.trace({ error: err.message }, 'Socket error');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -110,37 +113,46 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
listen(cb) {
|
||||
if(!this.enabled) {
|
||||
if (!this.enabled) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
const port = parseInt(config.contentServers.gopher.port);
|
||||
if(isNaN(port)) {
|
||||
this.log.warn( { port : config.contentServers.gopher.port, server : ModuleInfo.name }, 'Invalid port' );
|
||||
return cb(Errors.Invalid(`Invalid port: ${config.contentServers.gopher.port}`));
|
||||
if (isNaN(port)) {
|
||||
this.log.warn(
|
||||
{ port: config.contentServers.gopher.port, server: ModuleInfo.name },
|
||||
'Invalid port'
|
||||
);
|
||||
return cb(
|
||||
Errors.Invalid(`Invalid port: ${config.contentServers.gopher.port}`)
|
||||
);
|
||||
}
|
||||
|
||||
return this.server.listen(port, config.contentServers.gopher.address, cb);
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return _.get(Config(), 'contentServers.gopher.enabled', false) && this.isConfigured();
|
||||
return (
|
||||
_.get(Config(), 'contentServers.gopher.enabled', false) && this.isConfigured()
|
||||
);
|
||||
}
|
||||
|
||||
isConfigured() {
|
||||
// public hostname & port must be set; responses contain them!
|
||||
const config = Config();
|
||||
return _.isString(_.get(config, 'contentServers.gopher.publicHostname')) &&
|
||||
_.isNumber(_.get(config, 'contentServers.gopher.publicPort'));
|
||||
return (
|
||||
_.isString(_.get(config, 'contentServers.gopher.publicHostname')) &&
|
||||
_.isNumber(_.get(config, 'contentServers.gopher.publicPort'))
|
||||
);
|
||||
}
|
||||
|
||||
addRoute(selectorRegExp, generatorHandler) {
|
||||
if(_.isString(selectorRegExp)) {
|
||||
if (_.isString(selectorRegExp)) {
|
||||
try {
|
||||
selectorRegExp = new RegExp(`${selectorRegExp}\r\n`);
|
||||
} catch(e) {
|
||||
this.log.warn( { pattern : selectorRegExp }, 'Invalid RegExp for selector' );
|
||||
} catch (e) {
|
||||
this.log.warn({ pattern: selectorRegExp }, 'Invalid RegExp for selector');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -149,9 +161,9 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
|
||||
routeRequest(selector, socket) {
|
||||
let match;
|
||||
for(let [regex, gen] of this.routes) {
|
||||
for (let [regex, gen] of this.routes) {
|
||||
match = selector.match(regex);
|
||||
if(match) {
|
||||
if (match) {
|
||||
return gen(match, res => {
|
||||
return socket.end(`${res}`);
|
||||
});
|
||||
@@ -163,14 +175,17 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
makeItem(itemType, text, selector, hostname, port) {
|
||||
selector = selector || ''; // e.g. for info
|
||||
selector = selector || ''; // e.g. for info
|
||||
hostname = hostname || this.publicHostname;
|
||||
port = port || this.publicPort;
|
||||
return `${itemType}${text}\t${selector}\t${hostname}\t${port}\r\n`;
|
||||
}
|
||||
|
||||
staticGenerator(selectorMatch, cb) {
|
||||
this.log.debug( { selector : selectorMatch[1] || '(gophermap)' }, 'Serving static content');
|
||||
this.log.debug(
|
||||
{ selector: selectorMatch[1] || '(gophermap)' },
|
||||
'Serving static content'
|
||||
);
|
||||
|
||||
const requestedPath = selectorMatch[1];
|
||||
let path = this.resolveContentPath(requestedPath);
|
||||
@@ -192,7 +207,11 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
fs.readFile(path, isGopherMap ? 'utf8' : null, (err, content) => {
|
||||
if (err) {
|
||||
let content = 'You have reached an ENiGMA½ Gopher server!\r\n';
|
||||
content += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea');
|
||||
content += this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
'Public Message Area',
|
||||
'/msgarea'
|
||||
);
|
||||
return cb(content);
|
||||
}
|
||||
|
||||
@@ -220,12 +239,17 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
}
|
||||
|
||||
notFoundGenerator(selector, cb) {
|
||||
this.log.debug( { selector }, 'Serving not found content');
|
||||
this.log.debug({ selector }, 'Serving not found content');
|
||||
return cb('Not found');
|
||||
}
|
||||
|
||||
isAreaAndConfExposed(confTag, areaTag) {
|
||||
const conf = _.get(Config(), [ 'contentServers', 'gopher', 'messageConferences', confTag ]);
|
||||
const conf = _.get(Config(), [
|
||||
'contentServers',
|
||||
'gopher',
|
||||
'messageConferences',
|
||||
confTag,
|
||||
]);
|
||||
return Array.isArray(conf) && conf.includes(areaTag);
|
||||
}
|
||||
|
||||
@@ -248,14 +272,14 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
// to follow the KISS principle: Wrap at 79.
|
||||
//
|
||||
const WordWrapColumn = 79;
|
||||
if(isAnsi(body)) {
|
||||
if (isAnsi(body)) {
|
||||
AnsiPrep(
|
||||
body,
|
||||
{
|
||||
cols : WordWrapColumn, // See notes above
|
||||
forceLineTerm : true, // Ensure each line is term'd
|
||||
asciiMode : true, // Export to ASCII
|
||||
fillLines : false, // Don't fill up to |cols|
|
||||
cols: WordWrapColumn, // See notes above
|
||||
forceLineTerm: true, // Ensure each line is term'd
|
||||
asciiMode: true, // Export to ASCII
|
||||
fillLines: false, // Don't fill up to |cols|
|
||||
},
|
||||
(err, prepped) => {
|
||||
return cb(prepped || body);
|
||||
@@ -263,23 +287,24 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
);
|
||||
} else {
|
||||
const cleaned = stripMciColorCodes(
|
||||
stripAnsiControlCodes(body, { all : true } )
|
||||
stripAnsiControlCodes(body, { all: true })
|
||||
);
|
||||
const prepped =
|
||||
splitTextAtTerms(cleaned)
|
||||
.map(l => (wordWrapText(l, { width : WordWrapColumn } ).wrapped || []).join('\n'))
|
||||
.join('\n');
|
||||
const prepped = splitTextAtTerms(cleaned)
|
||||
.map(l =>
|
||||
(wordWrapText(l, { width: WordWrapColumn }).wrapped || []).join('\n')
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return cb(prepped);
|
||||
}
|
||||
}
|
||||
|
||||
shortenSubject(subject) {
|
||||
return _.truncate(subject, { length : 30 } );
|
||||
return _.truncate(subject, { length: 30 });
|
||||
}
|
||||
|
||||
messageAreaGenerator(selectorMatch, cb) {
|
||||
this.log.debug( { selector : selectorMatch[0] }, 'Serving message area content');
|
||||
this.log.debug({ selector: selectorMatch[0] }, 'Serving message area content');
|
||||
//
|
||||
// Selector should be:
|
||||
// /msgarea - list confs
|
||||
@@ -288,28 +313,40 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||
// /msgarea/conftag/areatag/<UUID> - message as text
|
||||
// /msgarea/conftag/areatag/<UUID>_raw - full message as text + headers
|
||||
//
|
||||
if(selectorMatch[3] || selectorMatch[4]) {
|
||||
if (selectorMatch[3] || selectorMatch[4]) {
|
||||
// message
|
||||
//const raw = selectorMatch[4] ? true : false;
|
||||
// :TODO: support 'raw'
|
||||
const msgUuid = selectorMatch[3].replace(/\r\n|\//g, '');
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const message = new Message();
|
||||
const msgUuid = selectorMatch[3].replace(/\r\n|\//g, '');
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const message = new Message();
|
||||
|
||||
return message.load( { uuid : msgUuid }, err => {
|
||||
if(err) {
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existent message UUID!');
|
||||
return message.load({ uuid: msgUuid }, err => {
|
||||
if (err) {
|
||||
this.log.debug(
|
||||
{ uuid: msgUuid },
|
||||
'Attempted access to non-existent message UUID!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
if(message.areaTag !== areaTag || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to non-exposed conference and/or area!');
|
||||
if (
|
||||
message.areaTag !== areaTag ||
|
||||
!this.isAreaAndConfExposed(confTag, areaTag)
|
||||
) {
|
||||
this.log.warn(
|
||||
{ areaTag },
|
||||
'Attempted access to non-exposed conference and/or area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to message in private area!');
|
||||
if (Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn(
|
||||
{ areaTag },
|
||||
'Attempted access to message in private area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
@@ -326,26 +363,29 @@ ${msgBody}
|
||||
return cb(response);
|
||||
});
|
||||
});
|
||||
} else if(selectorMatch[2]) {
|
||||
} else if (selectorMatch[2]) {
|
||||
// list messages in area
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const area = getMessageAreaByTag(areaTag);
|
||||
const confTag = selectorMatch[1].substr(1).split('/')[0];
|
||||
const areaTag = selectorMatch[2].replace(/\r\n|\//g, '');
|
||||
const area = getMessageAreaByTag(areaTag);
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn( { areaTag }, 'Attempted access to private area!');
|
||||
if (Message.isPrivateAreaTag(areaTag)) {
|
||||
this.log.warn({ areaTag }, 'Attempted access to private area!');
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'Area is private'));
|
||||
}
|
||||
|
||||
if(!area || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn( { confTag, areaTag }, 'Attempted access to non-exposed conference and/or area!');
|
||||
if (!area || !this.isAreaAndConfExposed(confTag, areaTag)) {
|
||||
this.log.warn(
|
||||
{ confTag, areaTag },
|
||||
'Attempted access to non-exposed conference and/or area!'
|
||||
);
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
const filter = {
|
||||
resultType : 'messageList',
|
||||
sort : 'messageId',
|
||||
order : 'descending', // we want newest messages first for Gopher
|
||||
resultType: 'messageList',
|
||||
sort: 'messageId',
|
||||
order: 'descending', // we want newest messages first for Gopher
|
||||
};
|
||||
|
||||
return getMessageListForArea(null, areaTag, filter, (err, msgList) => {
|
||||
@@ -354,30 +394,48 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||
this.makeItem(ItemTypes.InfoMessage, '(newest first)'),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
...msgList.map(msg => this.makeItem(
|
||||
ItemTypes.TextFile,
|
||||
`${moment(msg.modTimestamp).format('YYYY-MM-DD hh:mma')}: ${this.shortenSubject(msg.subject)} (${msg.fromUserName} to ${msg.toUserName})`,
|
||||
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
||||
))
|
||||
...msgList.map(msg =>
|
||||
this.makeItem(
|
||||
ItemTypes.TextFile,
|
||||
`${moment(msg.modTimestamp).format(
|
||||
'YYYY-MM-DD hh:mma'
|
||||
)}: ${this.shortenSubject(msg.subject)} (${
|
||||
msg.fromUserName
|
||||
} to ${msg.toUserName})`,
|
||||
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
});
|
||||
} else if(selectorMatch[1]) {
|
||||
} else if (selectorMatch[1]) {
|
||||
// list areas in conf
|
||||
const sysConfig = Config();
|
||||
const confTag = selectorMatch[1].replace(/\r\n|\//g, '');
|
||||
const conf = _.get(sysConfig, [ 'contentServers', 'gopher', 'messageConferences', confTag ]) && getMessageConferenceByTag(confTag);
|
||||
if(!conf) {
|
||||
const confTag = selectorMatch[1].replace(/\r\n|\//g, '');
|
||||
const conf =
|
||||
_.get(sysConfig, [
|
||||
'contentServers',
|
||||
'gopher',
|
||||
'messageConferences',
|
||||
confTag,
|
||||
]) && getMessageConferenceByTag(confTag);
|
||||
if (!conf) {
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
const areas = _.get(sysConfig, [ 'contentServers', 'gopher', 'messageConferences', confTag ], {})
|
||||
.map(areaTag => Object.assign( { areaTag }, getMessageAreaByTag(areaTag)))
|
||||
const areas = _.get(
|
||||
sysConfig,
|
||||
['contentServers', 'gopher', 'messageConferences', confTag],
|
||||
{}
|
||||
)
|
||||
.map(areaTag => Object.assign({ areaTag }, getMessageAreaByTag(areaTag)))
|
||||
.filter(area => area && !Message.isPrivateAreaTag(area.areaTag));
|
||||
|
||||
if(0 === areas.length) {
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'No message areas available'));
|
||||
if (0 === areas.length) {
|
||||
return cb(
|
||||
this.makeItem(ItemTypes.InfoMessage, 'No message areas available')
|
||||
);
|
||||
}
|
||||
|
||||
sortAreasOrConfs(areas);
|
||||
@@ -386,18 +444,33 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, `Message areas in ${conf.name}`),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
...areas.map(area => this.makeItem(ItemTypes.SubMenu, `${area.name} ${area.desc ? '- ' + area.desc : ''}`, `/msgarea/${confTag}/${area.areaTag}`))
|
||||
...areas.map(area =>
|
||||
this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
`${area.name} ${area.desc ? '- ' + area.desc : ''}`,
|
||||
`/msgarea/${confTag}/${area.areaTag}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
} else {
|
||||
// message area base (list confs)
|
||||
const confs = Object.keys(_.get(Config(), 'contentServers.gopher.messageConferences', {}))
|
||||
.map(confTag => Object.assign( { confTag }, getMessageConferenceByTag(confTag)))
|
||||
.filter(conf => conf); // remove any baddies
|
||||
const confs = Object.keys(
|
||||
_.get(Config(), 'contentServers.gopher.messageConferences', {})
|
||||
)
|
||||
.map(confTag =>
|
||||
Object.assign({ confTag }, getMessageConferenceByTag(confTag))
|
||||
)
|
||||
.filter(conf => conf); // remove any baddies
|
||||
|
||||
if(0 === confs.length) {
|
||||
return cb(this.makeItem(ItemTypes.InfoMessage, 'No message conferences available'));
|
||||
if (0 === confs.length) {
|
||||
return cb(
|
||||
this.makeItem(
|
||||
ItemTypes.InfoMessage,
|
||||
'No message conferences available'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
sortAreasOrConfs(confs);
|
||||
@@ -407,10 +480,16 @@ ${msgBody}
|
||||
this.makeItem(ItemTypes.InfoMessage, 'Available Message Conferences'),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, ''),
|
||||
...confs.map(conf => this.makeItem(ItemTypes.SubMenu, `${conf.name} ${conf.desc ? '- ' + conf.desc : ''}`, `/msgarea/${conf.confTag}`))
|
||||
...confs.map(conf =>
|
||||
this.makeItem(
|
||||
ItemTypes.SubMenu,
|
||||
`${conf.name} ${conf.desc ? '- ' + conf.desc : ''}`,
|
||||
`/msgarea/${conf.confTag}`
|
||||
)
|
||||
),
|
||||
].join('');
|
||||
|
||||
return cb(response);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user