359 lines
22 KiB
JavaScript
359 lines
22 KiB
JavaScript
const Discord = require('discord.js')
|
|
const bot = new Discord.Client()
|
|
const fs = require('fs')
|
|
const auth = JSON.parse(fs.readFileSync('.auth'))
|
|
const conf = JSON.parse(fs.readFileSync('conf.json'))
|
|
let latestActivityID = fs.existsSync('.latestActivityID') ? fs.readFileSync('.latestActivityID') : 0
|
|
|
|
const Trello = require('trello-events')
|
|
const events = new Trello({
|
|
pollFrequency: conf.pollInterval, // milliseconds
|
|
minId: latestActivityID, // auto-created and auto-updated
|
|
start: false,
|
|
trello: {
|
|
boards: conf.boardIDs, // array of Trello board IDs
|
|
key: auth.trelloKey, // your public Trello API key
|
|
token: auth.trelloToken // your private Trello token for Trellobot
|
|
}
|
|
})
|
|
|
|
|
|
|
|
/*
|
|
** =====================================
|
|
** Discord event handlers and functions.
|
|
** =====================================
|
|
*/
|
|
|
|
bot.login(auth.discordToken)
|
|
bot.on('ready', () => {
|
|
let guild = bot.guilds.get(conf.serverID)
|
|
let channel = bot.channels.get(conf.channelID)
|
|
if (!guild) {
|
|
console.log(`Server with ID "${conf.serverID}" not found! I can't function without a valid server and channel.\nPlease add the correct server ID to your conf file, or if the conf data is correct, ensure I have proper access.\nYou may need to add me to your server using this link:\n https://discordapp.com/api/oauth2/authorize?client_id=${bot.user.id}&permissions=0&scope=bot`)
|
|
process.exit()
|
|
} else if (!channel) {
|
|
console.log(`Channel with ID "${conf.channelID}" not found! I can't function without a valid channel.\nPlease add the correct channel ID to your conf file, or if the conf data is correct, ensure I have proper access.`)
|
|
process.exit()
|
|
} else if (!conf.boardIDs || conf.boardIDs.length < 1) {
|
|
console.log(`No board IDs provided! Please add at least one to your conf file. Check the readme if you need help finding a board ID.`)
|
|
}
|
|
conf.guild = guild
|
|
conf.channel = channel
|
|
/*
|
|
** Make contentString a map of event names to their paired strings
|
|
** like this: {"createCard": "someone created a card", ...}, so you
|
|
** can, for example, ping specific roles for specific events.
|
|
**
|
|
** Also add a new conf section for pairing lists within a board to
|
|
** contentStrings? That way you can ping one role for new Moderation
|
|
** cards, and another role for new Event cards, for example.
|
|
*/
|
|
if (!conf.contentString) conf.contentString = ""
|
|
if (!conf.enabledEvents) conf.enabledEvents = []
|
|
if (!conf.userIDs) conf.userIDs = {}
|
|
if (!conf.realNames) conf.realNames = true
|
|
// set default prefix is none provided in conf
|
|
if (!conf.prefix) {
|
|
conf.prefix = "."
|
|
fs.writeFileSync('conf.json', JSON.stringify(conf, null, 4), (err, data) => console.log(`Updated conf file with default prefix ('.')`))
|
|
}
|
|
// logInitializationData()
|
|
console.log(`== Bot logged in as @${bot.user.tag}. Ready for action! ==`)
|
|
events.start()
|
|
})
|
|
|
|
bot.on('message', (msg) => {
|
|
if (msg.channel.type !== "text") return
|
|
if (msg.content.startsWith(`${conf.prefix}ping`)) {
|
|
let now = Date.now()
|
|
msg.channel.send(`Ping!`).then(m => {
|
|
m.edit(`Pong! (took ${Date.now() - now}ms)`)
|
|
})
|
|
}
|
|
})
|
|
|
|
|
|
|
|
/*
|
|
** ====================================
|
|
** Trello event handlers and functions.
|
|
** ====================================
|
|
*/
|
|
|
|
// Fired when a card is created
|
|
events.on('createCard', (event, board) => {
|
|
if (!eventEnabled(`cardCreated`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`New card created under __${event.data.list.name}__!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card created under __${event.data.list.name}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a card is updated (description, due date, position, associated list, name, and archive status)
|
|
events.on('updateCard', (event, board) => {
|
|
let embed = getEmbedBase(event)
|
|
if (event.data.old.hasOwnProperty("desc")) {
|
|
if (!eventEnabled(`cardDescriptionChanged`)) return
|
|
embed
|
|
.setTitle(`Card description changed!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card description changed (see below) by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`New Description`, typeof event.data.card.desc === "string" && event.data.card.desc.trim().length > 0 ? (event.data.card.desc.length > 1024 ? `${event.data.card.desc.trim().slice(0, 1020)}...` : event.data.card.desc) : `*[No description]*`)
|
|
.addField(`Old Description`, typeof event.data.old.desc === "string" && event.data.old.desc.trim().length > 0 ? (event.data.old.desc.length > 1024 ? `${event.data.old.desc.trim().slice(0, 1020)}...` : event.data.old.desc) : `*[No description]*`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("due")) {
|
|
if (!eventEnabled(`cardDueDateChanged`)) return
|
|
embed
|
|
.setTitle(`Card due date changed!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card due date changed to __${event.data.card.due ? new Date(event.data.card.due).toUTCString() : `[No due date]`}__ from __${event.data.old.due ? new Date(event.data.old.due).toUTCString() : `[No due date]`}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("pos")) {
|
|
if (!eventEnabled(`cardPositionChanged`)) return
|
|
embed
|
|
.setTitle(`Card position changed!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card position in list __${event.data.list.name}__ changed by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("idList")) {
|
|
if (!eventEnabled(`cardListChanged`)) return
|
|
embed
|
|
.setTitle(`Card list changed!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card moved to list __${event.data.listAfter.name}__ from list __${event.data.listBefore.name}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("name")) {
|
|
if (!eventEnabled(`cardNameChanged`)) return
|
|
embed
|
|
.setTitle(`Card name changed!`)
|
|
.setDescription(`**CARD:** *[See below for card name]* — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card name changed (see below) by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`New Name`, event.data.card.name)
|
|
.addField(`Old Name`, event.data.old.name)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("closed")) {
|
|
if (event.data.old.closed) {
|
|
if (!eventEnabled(`cardUnarchived`)) return
|
|
embed
|
|
.setTitle(`Card unarchived!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card unarchived and returned to list __${event.data.list.name}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else {
|
|
if (!eventEnabled(`cardArchived`)) return
|
|
embed
|
|
.setTitle(`Card archived!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card under list __${event.data.list.name}__ archived by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
}
|
|
}
|
|
})
|
|
|
|
// Fired when a card is deleted
|
|
events.on('deleteCard', (event, board) => {
|
|
if (!eventEnabled(`cardDeleted`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Card deleted!`)
|
|
.setDescription(`**EVENT:** Card deleted from list __${event.data.list.name}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a comment is posted, or edited
|
|
events.on('commentCard', (event, board) => {
|
|
let embed = getEmbedBase(event)
|
|
if (event.data.hasOwnProperty("textData")) {
|
|
if (!eventEnabled(`commentEdited`)) return
|
|
embed
|
|
.setTitle(`Comment edited on card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card comment edited (see below for comment text) by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`Comment Text`, event.data.text.length > 1024 ? `${event.data.text.trim().slice(0, 1020)}...` : event.data.text)
|
|
.setTimestamp(event.data.dateLastEdited)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else {
|
|
if (!eventEnabled(`commentAdded`)) return
|
|
embed
|
|
.setTitle(`Comment added to card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Card comment added (see below for comment text) by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`Comment Text`, event.data.text.length > 1024 ? `${event.data.text.trim().slice(0, 1020)}...` : event.data.text)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
}
|
|
})
|
|
|
|
// Fired when a member is added to a card
|
|
events.on('addMemberToCard', (event, board) => {
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Member added to card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Member **[${conf.realNames ? event.member.fullName : event.member.username}](https://trello.com/${event.member.username})**`)
|
|
let editedEmbed = addDiscordUserData(embed, event.member)
|
|
|
|
if (event.member.id === event.memberCreator.id) {
|
|
if (!eventEnabled(`memberAddedToCardBySelf`)) return
|
|
editedEmbed.setDescription(editedEmbed.description + ` added themselves to card.`)
|
|
send(editedEmbed)
|
|
} else {
|
|
if (!eventEnabled(`memberAddedToCard`)) return
|
|
editedEmbed.setDescription(editedEmbed.description + ` added to card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(editedEmbed, event.memberCreator))
|
|
}
|
|
})
|
|
|
|
// Fired when a member is removed from a card
|
|
events.on('removeMemberFromCard', (event, board) => {
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Member removed from card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Member **[${conf.realNames ? event.member.fullName : event.member.username}](https://trello.com/${event.member.username})**`)
|
|
let editedEmbed = addDiscordUserData(embed, event.member)
|
|
|
|
if (event.member.id === event.memberCreator.id) {
|
|
if (!eventEnabled(`memberRemovedFromCardBySelf`)) return
|
|
editedEmbed.setDescription(editedEmbed.description + ` removed themselves from card.`)
|
|
send(editedEmbed)
|
|
} else {
|
|
if (!eventEnabled(`memberRemovedFromCard`)) return
|
|
editedEmbed.setDescription(editedEmbed.description + ` removed from card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(editedEmbed, event.memberCreator))
|
|
}
|
|
})
|
|
|
|
// Fired when a list is created
|
|
events.on('createList', (event, board) => {
|
|
if (!eventEnabled(`listCreated`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`New list created!`)
|
|
.setDescription(`**EVENT:** List __${event.data.list.name}__ created by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a list is renamed, moved, archived, or unarchived
|
|
events.on('updateList', (event, board) => {
|
|
let embed = getEmbedBase(event)
|
|
if (event.data.old.hasOwnProperty("name")) {
|
|
if (!eventEnabled(`listNameChanged`)) return
|
|
embed
|
|
.setTitle(`List name changed!`)
|
|
.setDescription(`**EVENT:** List renamed to __${event.data.list.name}__ from __${event.data.old.name}__ by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("pos")) {
|
|
if (!eventEnabled(`listPositionChanged`)) return
|
|
embed
|
|
.setTitle(`List position changed!`)
|
|
.setDescription(`**EVENT:** List __${event.data.list.name}__ position changed by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.old.hasOwnProperty("closed")) {
|
|
if (event.data.old.closed) {
|
|
if (!eventEnabled(`listUnarchived`)) return
|
|
embed
|
|
.setTitle(`List unarchived!`)
|
|
.setDescription(`**EVENT:** List __${event.data.list.name}__ unarchived by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else {
|
|
if (!eventEnabled(`listArchived`)) return
|
|
embed
|
|
.setTitle(`List archived!`)
|
|
.setDescription(`**EVENT:** List __${event.data.list.name}__ archived by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
}
|
|
}
|
|
})
|
|
|
|
// Fired when an attachment is added to a card
|
|
events.on('addAttachmentToCard', (event, board) => {
|
|
if (!eventEnabled(`attachmentAddedToCard`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Attachment added to card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Attachment named \`${event.data.attachment.name}\` added to card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when an attachment is removed from a card
|
|
events.on('deleteAttachmentFromCard', (event, board) => {
|
|
if (!eventEnabled(`attachmentRemovedFromCard`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Attachment removed from card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Attachment named \`${event.data.attachment.name}\` removed from card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a checklist is added to a card (same thing as created)
|
|
events.on('addChecklistToCard', (event, board) => {
|
|
if (!eventEnabled(`checklistAddedToCard`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Checklist added to card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Checklist named \`${event.data.checklist.name}\` added to card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a checklist is removed from a card (same thing as deleted)
|
|
events.on('removeChecklistFromCard', (event, board) => {
|
|
if (!eventEnabled(`checklistRemovedFromCard`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Checklist removed from card!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Checklist named \`${event.data.checklist.name}\` removed from card by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
})
|
|
|
|
// Fired when a checklist item's completion status is toggled
|
|
events.on('updateCheckItemStateOnCard', (event, board) => {
|
|
if (event.data.checkItem.state === "complete") {
|
|
if (!eventEnabled(`checklistItemMarkedComplete`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Checklist item marked complete!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Checklist item under checklist \`${event.data.checklist.name}\` marked complete by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`Checklist Item Name`, event.data.checkItem.name.length > 1024 ? `${event.data.checkItem.name.trim().slice(0, 1020)}...` : event.data.checkItem.name)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
} else if (event.data.checkItem.state === "incomplete") {
|
|
if (!eventEnabled(`checklistItemMarkedIncomplete`)) return
|
|
let embed = getEmbedBase(event)
|
|
.setTitle(`Checklist item marked incomplete!`)
|
|
.setDescription(`**CARD:** ${event.data.card.name} — **[CARD LINK](https://trello.com/c/${event.data.card.shortLink})**\n\n**EVENT:** Checklist item under checklist \`${event.data.checklist.name}\` marked incomplete by **[${conf.realNames ? event.memberCreator.fullName : event.memberCreator.username}](https://trello.com/${event.memberCreator.username})**`)
|
|
.addField(`Checklist Item Name`, event.data.checkItem.name.length > 1024 ? `${event.data.checkItem.name.trim().slice(0, 1020)}...` : event.data.checkItem.name)
|
|
send(addDiscordUserData(embed, event.memberCreator))
|
|
}
|
|
})
|
|
|
|
|
|
|
|
/*
|
|
** =======================
|
|
** Miscellaneous functions
|
|
** =======================
|
|
*/
|
|
events.on('maxId', (id) => {
|
|
if (latestActivityID == id) return
|
|
latestActivityID = id
|
|
fs.writeFileSync('.latestActivityID', id.toString())
|
|
})
|
|
|
|
const send = (embed, content = ``) => conf.channel.send(`${content} ${conf.contentString}`, { embed: embed }).catch(err => console.error(err))
|
|
|
|
const eventEnabled = (type) => conf.enabledEvents.length > 0 ? conf.enabledEvents.includes(type) : true
|
|
|
|
const logEventFire = (event) => console.log(`${new Date(event.date).toUTCString()} - ${event.type} fired`)
|
|
|
|
const getEmbedBase = (event) => new Discord.RichEmbed()
|
|
.setFooter(`${conf.guild.members.get(bot.user.id).displayName} • ${event.data.board.name} [${event.data.board.shortLink}]`, bot.user.displayAvatarURL)
|
|
.setTimestamp(event.hasOwnProperty(`date`) ? event.date : Date.now())
|
|
.setColor("#127ABD")
|
|
|
|
// Converts Trello @username mentions in titles to Discord mentions, finds channel and role mentions, and mirros Discord user mentions outside the embed
|
|
const convertMentions = (embed, event) => {
|
|
|
|
}
|
|
|
|
// adds thumbanil and appends user mention to the end of the description, if possible
|
|
const addDiscordUserData = (embed, member) => {
|
|
if (conf.userIDs[member.username]) {
|
|
let discordUser = conf.guild.members.get(conf.userIDs[member.username])
|
|
if (discordUser) embed
|
|
.setThumbnail(discordUser.user.displayAvatarURL)
|
|
.setDescription(`${embed.description} / ${discordUser.toString()}`)
|
|
}
|
|
return embed
|
|
}
|
|
|
|
// logs initialization data (stuff loaded from conf.json) - mostly for debugging purposes
|
|
const logInitializationData = () => console.log(`== INITIALIZING WITH:
|
|
latestActivityID - ${latestActivityID}
|
|
boardIDs --------- ${conf.boardIDs.length + " [" + conf.boardIDs.join(", ") + "]"}
|
|
serverID --------- ${conf.serverID} (${conf.guild.name})
|
|
channelID -------- ${conf.channelID} (#${conf.channel.name})
|
|
pollInterval ----- ${conf.pollInterval} ms (${conf.pollInterval / 1000} seconds)
|
|
prefix ----------- "${conf.prefix}"${conf.prefix === "." ? " (default)" : ""}
|
|
contentString ---- ${conf.contentString !== "" ? "\"" + conf.contentString + "\"" : "none"}
|
|
enabledEvents ---- ${conf.enabledEvents.length > 0 ? conf.enabledEvents.length + " [" + conf.enabledEvents.join(", ") + "]" : "all"}
|
|
userIDs ---------- ${Object.getOwnPropertyNames(conf.userIDs).length}`)
|