-
Notifications
You must be signed in to change notification settings - Fork 6
/
eigenbot.js
256 lines (238 loc) · 8.84 KB
/
eigenbot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import fetch from 'node-fetch'
import {replyNoMention, editNoMention} from './utils.js'
import {SlashCommandBuilder} from '@discordjs/builders'
const PROJECTS = ['MC', 'MCAPI', 'MCCE', 'MCD', 'MCL', 'MCPE', 'REALMS', 'BDS', 'WEB']
let jira, client, config
export default (_client, _config, _jira) => {
client = _client
config = _config
jira = _jira
client.on('messageCreate', onMessage)
client.on('interactionCreate', onInteraction)
return [
new SlashCommandBuilder().setName('upcoming').setDescription('Shows bugs that are likely fixed in the next snapshot')
.addStringOption(option => option.setName('project').setDescription('The project to search in, for example "MC"')),
new SlashCommandBuilder().setName('mcstatus').setDescription('Checks Mojang server status'),
new SlashCommandBuilder().setName('bug').setDescription('Shows information for a bug')
.addStringOption(option => option.setName('id').setDescription('The bug id (for example MC-88959)'))
]
}
function onInteraction(interaction) {
if (!interaction.isCommand()) return
switch(interaction.commandName) {
case 'upcoming': return sendUpcoming(interaction, interaction.options.getString('project'))
case 'mcstatus': return sendStatus(interaction)
case 'bug': {
const key = interaction.options.getString('id')
const dash = key.indexOf('-')
const bugNumber = key.substr(dash + 1)
if (dash < 0 || parseInt(bugNumber).toString() !== bugNumber) {
replyNoMention(interaction, 'Invalid issue id')
return
}
if (!PROJECTS.includes(key.substr(0, dash))) {
replyNoMention(interaction, 'Unknown project')
return
}
return respondWithIssue(interaction, key)
}
}
}
function onMessage (msg) {
const escapedPrefix = config.prefix.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
const regexPattern = new RegExp(escapedPrefix + '(' + PROJECTS.join('|') + ')-[0-9]{1,7}', 'gi')
const urlRegex = new RegExp('https?:\/\/bugs.mojang.com\/browse\/(' + PROJECTS.join('|') + ')-[0-9]{1,7}', 'gi')
// We don't want our bot to react to other bots or itself
if (msg.author.bot) {
return
}
// help: Gives usage information
if (msg.content.startsWith(config.prefix + 'help')) {
sendHelp(msg)
return
}
// upcoming: Checks for fixes in unreleased snapshots
if (msg.content.startsWith(config.prefix + 'upcoming')) {
let project = 'MC'
const args = msg.content.split(' ')
if (args.length > 1) {
project = args[1].toUpperCase()
}
sendUpcoming(msg, project)
return
}
// mcstatus: Checks Mojang server status
if (msg.content.startsWith(config.prefix + 'mcstatus')) {
sendStatus(msg)
return
}
let matches = []
// Check for prefixed issue keys (!MC-1)
const piks = msg.content.match(regexPattern)
if (piks) matches = piks.map(prefixedIssueKey => prefixedIssueKey.slice(config.prefix.length))
// Check for bugs.mojang.com urls
const urls = msg.content.match(urlRegex)
if (urls) {
matches = matches.concat(urls.map(function (url) {
return url.split('/')[4]
}))
}
if (matches.length <= 5) {
for (const issueKey of new Set(matches)) {
respondWithIssue(msg, issueKey)
}
}
}
function respondWithIssue(msg, issueKey) {
jira.findIssue(issueKey).then(issue =>{
// Send info about the bug in the form of an embed to the Discord channel
sendEmbed(msg, issue)
}).catch(error => {
if (error && error.error && error.error.errorMessages) {
if (error.error.errorMessages.includes('Issue Does Not Exist')) {
replyNoMention(msg, 'No issue was found for ' + issueKey + '.')
} else if (error.error.errorMessages.includes('You do not have the permission to see the specified issue.')) {
replyNoMention(msg,'Issue ' + issueKey + ' is private and you don\'t have permission to see it.');
} else {
replyNoMention(msg, 'An unknown error has occurred.')
console.log(error)
}
} else {
replyNoMention(msg, 'An unknown error has occurred.')
console.log(error)
}
})
}
function sendHelp (interaction) {
replyNoMention(interaction, {embeds: [{
title: config.name + ' help',
description: 'I listen for Minecraft bug report links or ' + config.prefix + 'PROJECT-NUMBER\n' +
'For example, saying https://bugs.mojang.com/browse/MC-81098 or ' + config.prefix + 'MC-81098 will give quick info on those bugs',
fields: [
{
name: 'Other commands: ',
value: '**' + config.prefix + 'help:** Shows this help screen.\n' +
'**' + config.prefix + 'mcstatus:** Checks Mojang server status.\n' +
'**' + config.prefix + 'upcoming:** Shows bugs that are likely fixed in the next snapshot.'
}
],
url: config.url,
color: 9441545,
footer: {
text: config.name
}
}]})
}
async function sendUpcoming (interaction, project) {
project = project ? project.toUpperCase() : 'MC'
if (!PROJECTS.includes(project)) {
replyNoMention(interaction, 'Invalid project ID.')
return
}
let sendNext = replyNoMention.bind(null, interaction)
let done = false
if (interaction.deferReply) {
await interaction.deferReply()
} else {
setTimeout(async () => {
if (!done) {
const msg = await replyNoMention(interaction, 'Searching for upcoming bugfixes, please wait...')
sendNext = editNoMention.bind(null, msg)
}
}, 500)
}
const search = 'project = ' + project + ' AND fixVersion in unreleasedVersions() ORDER BY resolved DESC'
jira.searchJira(search).then(async function (results) {
done = true
if (!results.issues || !results.issues.length) {
return replyNoMention(interaction, 'No upcoming bugfixes were found.')
}
let messageContent = 'The following bugs will likely be fixed in the next snapshot:'
async function addLine(line) {
const newContent = line !== null ? messageContent + '\n' + line : messageContent
if (newContent.length >= 2000 || line === null) {
const msg = await sendNext(messageContent)
sendNext = interaction.followUp ? interaction.followUp.bind(interaction) : msg.reply.bind(msg)
messageContent = line || ''
} else {
messageContent = newContent
}
}
for (const issue of results.issues) {
await addLine('**' + issue.key + '**: *' + issue.fields.summary.trim() + '*')
}
await addLine(null)
}).catch(function (error) {
done = true
replyNoMention(interaction, 'An error has occurred.')
console.log('Error when processing upcoming command:')
console.log(error)
})
}
async function sendStatus (interaction) {
// Request json object with the status of services
try {
if (interaction.deferReply) {
await interaction.deferReply()
}
const res = await fetch('https://status.mojang.com/check')
const statuses = await res.json()
const colors = {
red: 0xff0000,
yellow: 0x00ffff,
green: 0x00ff00
}
let color = colors.green
const embed = {
title: 'Mojang Service Status',
fields: []
}
for (const service of statuses) {
const name = Object.keys(service)[0]
embed.fields.push({
name, value: `:${service[name]}_square: ${service[name]}`, inline: true
})
color = Math.max(color, colors[service[name]])
}
while (embed.fields.length % 3 !== 0) embed.fields.push({name: '\u200b', value: '\u200b', inline: true})
embed.color = color
await replyNoMention(interaction, {embeds: [embed]})
} catch (e) {
console.error(e)
try {
await replyNoMention(interaction, 'Could not get status from Mojang API')
} catch (e2) {
console.error(e2)
}
}
}
// Send info about the bug in the form of an embed to the Discord channel
function sendEmbed (interaction, issue) {
let descriptionString = '**Status:** ' + issue.fields.status.name
if (!issue.fields.resolution) {
// For unresolved issues
descriptionString += ' | **Votes:** ' + issue.fields.votes.votes
} else {
// For resolved issues
descriptionString += ' | **Resolution:** ' + issue.fields.resolution.name
}
// Generate the message
// Pick a color based on the status
let color = config.colors[issue.fields.status.name]
// Additional colors for different resolutions
if (issue.fields.resolution && ['Invalid', 'Duplicate', 'Incomplete', 'Cannot Reproduce'].includes(issue.fields.resolution.name)) {
color = config.colors['Invalid']
} else if (issue.fields.resolution && ["Won't Fix", 'Works As Intended'].includes(issue.fields.resolution.name)) {
color = config.colors['Working']
}
replyNoMention(interaction, {embeds: [{
title: issue.key + ': ' + issue.fields.summary,
url: 'https://bugs.mojang.com/browse/' + issue.key,
description: descriptionString,
color: color,
timestamp: new Date(Date.parse(issue.fields.created)),
footer: {
text: 'Created'
}
}]})
}