Initial commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Giesel 2020-04-29 15:54:12 +02:00
commit 4e51dafdc0
12 changed files with 731 additions and 0 deletions

6
.auth.example Normal file
View file

@ -0,0 +1,6 @@
{
"instructions": "Replace the following three values as appropriate, then rename this file to '.auth' (without '-template' at the end).",
"discordToken": "discord token goes here",
"trelloKey": "trello public key goes here",
"trelloToken": "trello app token goes here"
}

27
.drone.yml Normal file
View file

@ -0,0 +1,27 @@
kind: pipeline
name: default
steps:
- name: install
image: node:14.0
commands:
- npm install
- name: deploy
image: plugins/docker
settings:
registry: registry.cliffbreak.de
repo: registry.cliffbreak.de/trellobot
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
- push
- tag
- deployment
trigger:
branch:
- master

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.auth
.latestActivityID
node_modules/
.vscode/
old-confs/
conf.json

9
Dockerfile Normal file
View file

@ -0,0 +1,9 @@
FROM node:14.0
ENV NODE_ENV dev
WORKDIR /usr/src/app
COPY package*.json ./
COPY .auth.example ./.auth
COPY conf.json.example ./conf.json
RUN npm install --production --silent
COPY . .
CMD npm start

16
conf.json.example Normal file
View file

@ -0,0 +1,16 @@
{
"boardIDs": [
"tNbPCydx"
],
"serverID": "138520312697454592",
"channelID": "453042376898904080",
"pollInterval": 10000,
"contentString": "",
"enabledEvents": [
"cardCreated"
],
"userIDs": {
"theangush": "138520076427984896"
},
"prefix": "."
}

9
docker-compose.yml Normal file
View file

@ -0,0 +1,9 @@
version: '2.1'
services:
trellobot:
container_name: trellobot
restart: always
image: registry.cliffbreak.de/trellobot
environment:
NODE_ENV: production

91
events.md Normal file
View file

@ -0,0 +1,91 @@
# Event Types
## Supported Events:
Put any of these in the `enabledEvents` array of your `conf.json` file to utilize the event whitelist. They should all be self-explanatory.
* `cardCreated`
* `cardDescriptionChanged`
* `cardDueDateChanged`
* `cardPositionChanged`
* `cardListChanged`
* `cardNameChanged`
* `cardUnarchived`
* `cardArchived`
* `cardDeleted`
* `commentEdited`
* `commentAdded`
* `memberAddedToCard`
* `memberAddedToCardBySelf`
* `memberRemovedFromCard`
* `memberRemovedFromCardBySelf`
* `listCreated`
* `listNameChanged`
* `listPositionChanged`
* `listUnarchived`
* `listArchived`
* `attachmentAddedToCard`
* `attachmentRemovedFromCard`
* `checklistAddedToCard`
* `checklistRemovedFromCard`
* `checklistItemMarkedComplete`
* `checklistItemMarkedIncomplete`
## Unsupported Events:
These are other events that *ostensibly exist*, but have not yet been implemented in Trellobot, or aren't available from the Trello API, so you can't get alerts for them.
* `addAdminToBoard`
* `addAdminToOrganization`
* `addBoardsPinnedToMember`
* `addLabelToCard`
* `addMemberToBoard`
* `addMemberToOrganization`
* `addToOrganizationBoard`
* `convertToCardFromCheckItem`
* `copyBoard`
* `copyCard`
* `copyChecklist`
* `copyCommentCard`
* `createBoard`
* `createBoardInvitation`
* `createBoardPreference`
* `createChecklist`
* `createLabel`
* `createOrganization`
* `createOrganizationInvitation`
* `deleteBoardInvitation`
* `deleteCheckItem`
* `deleteLabel`
* `deleteOrganizationInvitation`
* `disablePlugin`
* `disablePowerUp`
* `emailCard`
* `enablePlugin`
* `enablePowerUp`
* `makeAdminOfBoard`
* `makeAdminOfOrganization`
* `makeNormalMemberOfBoard`
* `makeNormalMemberOfOrganization`
* `makeObserverOfBoard`
* `memberJoinedTrello`
* `moveCardFromBoard`
* `moveCardToBoard`
* `moveListFromBoard`
* `moveListToBoard`
* `removeAdminFromBoard`
* `removeAdminFromOrganization`
* `removeBoardsPinnedFromMember`
* `removeFromOrganizationBoard`
* `removeLabelFromCard`
* `removeMemberFromBoard`
* `removeMemberFromOrganization`
* `unconfirmedBoardInvitation`
* `unconfirmedOrganizationInvitation`
* `updateBoard`
* `updateCheckItem`
* `updateChecklist`
* `updateLabel`
* `updateMember`
* `updateOrganization`
* `voteOnCard`

BIN
example-alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

136
package-lock.json generated Normal file
View file

@ -0,0 +1,136 @@
{
"name": "trellobot",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"coffee-script": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.2.tgz",
"integrity": "sha1-Lg0rgjQiB3sPXLDKXJuSTUytB1g="
},
"discord.js": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.3.2.tgz",
"integrity": "sha512-Abw9CTMX3Jb47IeRffqx2VNSnXl/OsTdQzhvbw/JnqCyqc2imAocc7pX2HoRmgKd8CgSqsjBFBneusz/E16e6A==",
"requires": {
"long": "^4.0.0",
"prism-media": "^0.0.2",
"snekfetch": "^3.6.4",
"tweetnacl": "^1.0.0",
"ws": "^4.0.0"
}
},
"extend": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz",
"integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"node-trello": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/node-trello/-/node-trello-0.1.5.tgz",
"integrity": "sha1-rGWDVYn7iXLTS6nJXbKLcnoYNpQ=",
"requires": {
"coffee-script": "1.3.2",
"oauth": "0.9.7",
"request": "2.12.0"
}
},
"oauth": {
"version": "0.9.7",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.7.tgz",
"integrity": "sha1-wlVNA2jJZuswUL7JZYRiVXetHs0="
},
"prism-media": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.2.tgz",
"integrity": "sha512-L6yc8P5NVG35ivzvfI7bcTYzqFV+K8gTfX9YaJbmIFfMXTs71RMnAupvTQPTCteGsiOy9QcNLkQyWjAafY/hCQ=="
},
"request": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.12.0.tgz",
"integrity": "sha1-EfRvILPQ9ISMY4OZHIB5CvFsjkg=",
"requires": {
"form-data": "~0.0.3",
"mime": "~1.2.7"
},
"dependencies": {
"form-data": {
"version": "0.0.3",
"bundled": true,
"requires": {
"async": "~0.1.9",
"combined-stream": "0.0.3",
"mime": "~1.2.2"
},
"dependencies": {
"async": {
"version": "0.1.9",
"bundled": true
},
"combined-stream": {
"version": "0.0.3",
"bundled": true,
"requires": {
"delayed-stream": "0.0.5"
},
"dependencies": {
"delayed-stream": {
"version": "0.0.5",
"bundled": true
}
}
}
}
},
"mime": {
"version": "1.2.7",
"bundled": true
}
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"snekfetch": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
"integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
},
"trello-events": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/trello-events/-/trello-events-0.1.6.tgz",
"integrity": "sha1-hDQckGPU4SDHq0uwbXkT1/vGl9o=",
"requires": {
"extend": "^1.2.1",
"node-trello": "^0.1.4"
}
},
"tweetnacl": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
"integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
}
}
}

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "trellobot",
"version": "1.0.0",
"description": "A Discord bot for logging Trello events.",
"main": "trellobot.js",
"scripts": {
"start": "node trellobot.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Angush/trellobot.git"
},
"author": "Angush",
"license": "ISC",
"bugs": {
"url": "https://github.com/Angush/trellobot/issues"
},
"homepage": "https://github.com/Angush/trellobot#readme",
"dependencies": {
"discord.js": "^11.3.2",
"trello-events": "^0.1.6"
},
"nodemonConfig": {
"ignore": [
"*.md",
".latestActivityID"
]
}
}

43
readme.md Normal file
View file

@ -0,0 +1,43 @@
# Trellobot
A simple Discord bot to log and report events from your Trello boards in your Discord server.
![Image example of Trellobot alert](https://raw.githubusercontent.com/Angush/trellobot/master/example-alert.png "Image example of Trellobot alert")
## Setup
1. Clone repository.
2. Run `npm install`.
3. Configure `conf.json` file as desired ([see below](#confjson)).
4. Generate tokens and set up `.auth` file ([see below](#auth)).
5. All done. Run Trellobot with `node trellobot.js`.
## conf.json
There are several important values in here which inform Trellobot's operation. Here's what they're all for, and how to set them.
*(optional properties marked with * asterisks)*
Property | Explanation
---------------- | -----------
`boardIDs` | An array of board IDs (strings) determining on which boards Trellobot reports. IDs can be extracted from the URLs of your Trello boards. (eg. the board ID for [https://trello.com/b/**HF8XAoZd**/welcome-board](https://trello.com/b/HF8XAoZd/welcome-board) is `HF8XAoZd`).
`serverID` | An ID string determining which Discord server Trellobot uses. Enable developer mode in Discord and right click a server icon to copy its ID.
`channelID` | An ID string determining which channel on your Discord server Trellobot uses to post reports. Enable developer mode in Discord and right click a channel to copy its ID.
`pollInterval` | An integer determining how often (in milliseconds) Trellobot polls your boards for activity.
`prefix`* | A string determining the prefix for Trellobot commands in Discord. Currently unused. Defaults to `.` (period).
`contentString`* | A string included posted alongside all embeds. If you'd like to ping a certain role every time the bot posts, for example, you would put that string here.
`enabledEvents`* | An array of event names (strings) determining whitelisted events (ie. which events will be reported; if empty, all events are enabled). Eligible event names can be found [in the `events.md` file](https://github.com/angush/trellobot/blob/master/events.md).
`userIDs`* | An object mapping Discord IDs to Trello usernames, like so: `userIDs: {"TrelloUser": "1395184357104955", ...}`, so Trellobot can pull relevant user data from Discord.
`realNames`* | A boolean (defaulting to true) that determines whether Trellobot uses the full names or usernames from Trello (eg. `John Smith` vs `jsmiff2`)
You can refer to the `conf.json` included in the repository for an example.
## .auth
The `.auth` file is included as a template to save you time, but you will need to create the keys and tokens yourself to run Trellobot. Here's how:
Property | How to get the value
-------------- | ----------------------
`discordToken` | Create an app for Trellobot to work through on [Discord's developer site](https://discordapp.com/developers/applications/me/create), then create a bot user (below app description/icon) and copy the token.
`trelloKey` | Visit [this page](https://trello.com/1/appKey/generate) to generate your public Trello API key.
`trelloToken` | Visit `https://trello.com/1/connect?name=Trellobot&response_type=token&expiration=never&key=YOURPUBLICKEY` (replacing `YOURPUBLICKEY` with the appropriate key) to generate a token that does not expire. Remove `&expiration=never` from the URL if you'd prefer a temporary token.
That's all for now.
*i know the name is lame*

359
trellobot.js Normal file
View file

@ -0,0 +1,359 @@
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)
})
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}`)