This commit is contained in:
commit
4e51dafdc0
12 changed files with 731 additions and 0 deletions
6
.auth.example
Normal file
6
.auth.example
Normal 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
27
.drone.yml
Normal 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
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
.auth
|
||||
.latestActivityID
|
||||
node_modules/
|
||||
.vscode/
|
||||
old-confs/
|
||||
conf.json
|
9
Dockerfile
Normal file
9
Dockerfile
Normal 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
16
conf.json.example
Normal 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
9
docker-compose.yml
Normal 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
91
events.md
Normal 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
BIN
example-alert.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
136
package-lock.json
generated
Normal file
136
package-lock.json
generated
Normal 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
29
package.json
Normal 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
43
readme.md
Normal 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
359
trellobot.js
Normal 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}`)
|
Loading…
Reference in a new issue