Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
56c6828
other: Ignore RFCs cache file.
sustained Oct 6, 2019
7ac93df
deps: Add github-api.
sustained Oct 6, 2019
3301a70
other: Register RFCs command group.
sustained Oct 6, 2019
7e8f809
other: Github API client file.
sustained Oct 6, 2019
be150ff
other: Verify GITHUB_TOKEN is present.
sustained Oct 6, 2019
a31b35c
other: Add DATA_DIR constant.
sustained Oct 6, 2019
f40fb11
feat: Add OptionalKeyValuePairArgumentType.
sustained Oct 6, 2019
c4a6bc0
feat: Add RFC service.
sustained Oct 6, 2019
19c2046
feat: Add RFC commands.
sustained Oct 6, 2019
16423ac
other: Merged isn't yet implemented either.
sustained Oct 6, 2019
5703240
other: Remove stray comment.
sustained Oct 6, 2019
928e66a
other: Adjust RFC cache TTL + add dev/prod check.
sustained Oct 7, 2019
547ca5b
other: Show how many RFCs were cached to disc.
sustained Oct 7, 2019
a0ea735
fix: Missing RFCs.
sustained Oct 7, 2019
edb4a11
other: Remove "popular" filter.
sustained Oct 7, 2019
873a9e8
feat: Filter RFCs by merged.
sustained Oct 7, 2019
c347eb0
other: Display count of RFCs.
sustained Oct 7, 2019
7526639
other: Show who requested the RFCs.
sustained Oct 7, 2019
8a4383e
other: Delete triggering message.
sustained Oct 7, 2019
6dad2a9
other: Show how to view a specific RFC.
sustained Oct 7, 2019
a3d1662
refactor: Extract method to create RFC fields.
sustained Oct 7, 2019
9f4dd3d
other: Clean up reload command output slightly.
sustained Oct 7, 2019
d47717f
other: Check permissions to reload RFCs.
sustained Oct 7, 2019
b77b43f
refactor: Prefer async/await.
sustained Dec 3, 2019
6e46b88
Merge branch 'master' into feat-rfcs
sustained Dec 30, 2019
da228cd
feat: Display Vue icon.
sustained Dec 30, 2019
9758100
fix: Missing constant.
sustained Dec 30, 2019
a5fa31a
Merge remote-tracking branch 'upstream/master'
sustained Jan 3, 2020
881f9ef
Merge branch 'master' into feat-rfcs
sustained Jan 3, 2020
290f834
other: Use inline code for examples.
sustained Jan 3, 2020
60e4d8e
fix: Allow !rfc in DMs.
sustained Jan 3, 2020
f03e1e3
other: Make footer show only dates.
sustained Jan 3, 2020
051debf
other: Change the way rfc/msg authors are displayed.
sustained Jan 3, 2020
c622342
fix: Issue with spaces in Github code blocks.
sustained Jan 3, 2020
d6af8be
fix: Remove stuff we're not using anymore.
sustained Jan 3, 2020
3e8ae34
fix: Use cleanupErrorResponse.
sustained Jan 3, 2020
0df077c
fix: Add missing constant.
sustained Jan 3, 2020
6e38a2a
other: Tiny little refactoring.
sustained Jan 3, 2020
1ba0cf4
fix: Some examples failed because those RFCs have since been deleted.
sustained Jan 3, 2020
816a589
fix: RFCs with a body over 2048 characters.
sustained Jan 3, 2020
c6db0e8
other: Use cleanupInvocation.
sustained Jan 3, 2020
b13597c
other: Simplify disambiguation embed.
sustained Jan 3, 2020
f16b9a2
other: Simplify utility.
sustained Jan 3, 2020
01c4a8c
other: Refactor.
sustained Jan 3, 2020
102b33b
other: Remove old debug logging.
sustained Jan 3, 2020
5b7ddf4
other: More alignment of embed styling between this and other commands.
sustained Jan 3, 2020
f21d9d1
other: Use cleanup utils.
sustained Jan 3, 2020
607da7c
fix: Value should be lowercase.
sustained Jan 3, 2020
30b156a
other: Prettify error response.
sustained Jan 3, 2020
2eb6895
fix: Exhaust possibilities + disallow duplicates.
sustained Jan 3, 2020
26f3ac7
other: Mention advanced usage of !rfc too.
sustained Jan 3, 2020
5364995
other: Show less RFCs per page.
sustained Jan 3, 2020
2d7cefa
fix: Missing Vue logo.
sustained Jan 3, 2020
4b9ff19
fix: Passing options incorrectly.
sustained Jan 3, 2020
83af270
fix: Missing author.
sustained Jan 3, 2020
90641af
fix: Author icons.
sustained Jan 3, 2020
c507b7b
fix: Bail if no value.
sustained Jan 6, 2020
b90d102
feat: Fuzzy searching.
sustained Jan 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
BOT_TOKEN=
CLIENT_ID=
OWNERS_IDS=
COMMAND_PREFIX=!
COMMAND_PREFIX=!
GITHUB_TOKEN=
1 change: 1 addition & 0 deletions data/rfcs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rfcs.json
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-vue": "^5.2.3",
"esm": "^3.2.25",
"github-api": "^3.3.0",
"fuse.js": "^3.4.6",
"hjson": "^3.1.2",
"prettier": "^1.18.2"
Expand Down
4 changes: 4 additions & 0 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ client.registry.registerGroups([
id: 'jobs',
name: 'Jobs',
},
{
id: 'rfcs',
name: 'RFCs',
},
])

if (NODE_ENV === 'development') {
Expand Down
51 changes: 51 additions & 0 deletions src/commands/rfcs/reload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Command } from 'discord.js-commando'
import { RichEmbed } from 'discord.js'
import { reloadCache } from '../../services/rfcs'
import { EMPTY_MESSAGE, ROLES } from '../../utils/constants'
import { cleanupErrorResponse, cleanupInvocation } from '../../utils/messages'

const ALLOWED_ROLES = [ROLES.MODERATORS, ROLES.CORE_TEAM, ROLES.BOT_DEVELOPERS]

module.exports = class RFCsCommand extends Command {
constructor(client) {
super(client, {
name: 'reload-rfcs',
group: 'rfcs',
guildOnly: true,
memberName: 'reload',
description: 'Reload the RFCs from the Github API and recache them.',
})
}

hasPermission(msg) {
if (msg.member.roles.some(role => ALLOWED_ROLES.includes(role.id))) {
return true
}

return false
}

async run(msg) {
const embed = new RichEmbed('Reload RFCs')

try {
await reloadCache()
embed
.setDescription(
'✅ Fetched RFC PRs from Github and re-cached them to disc.'
)
.setColor('GREEN')
} catch (error) {
console.error(error)
embed
.setDescription(
'❎ An error occured while fetching and recaching the RFC PRs.'
)
.setColor('RED')
} finally {
const reply = await msg.channel.send(EMPTY_MESSAGE, { embed })
cleanupInvocation(msg)
cleanupErrorResponse(reply)
}
}
}
209 changes: 209 additions & 0 deletions src/commands/rfcs/rfc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { Command } from 'discord.js-commando'
import { RichEmbed } from 'discord.js'
import {
findRFCs,
filterRFCsBy,
RFCDoesNotExistError,
} from '../../services/rfcs'
import {
EMPTY_MESSAGE,
DISCORD_EMBED_DESCRIPTION_LIMIT,
} from '../../utils/constants'
import { cleanupErrorResponse, cleanupInvocation } from '../../utils/messages'
import { inlineCode, addEllipsis } from '../../utils/string'
import {
respondWithPaginatedEmbed,
DEFAULT_EMBED_COLOUR,
} from '../../utils/embed'

module.exports = class RFCsCommand extends Command {
constructor(client) {
super(client, {
args: [
{
key: 'query',
type: 'optional-kv-pair',
prompt: 'an RFC number, title, body, author or label to search for?',
validate(val) {
if (Array.isArray(val)) {
return ['id', 'title', 'body', 'author', 'label'].includes(val[0])
}

return true
},
},
],
name: 'rfc',
examples: [
inlineCode('!rfcs'),
inlineCode('!rfc #23'),
inlineCode('!rfc initial placeholder'),
inlineCode('!rfc empty node'),
inlineCode('!rfc yyx'),
inlineCode('!rfc core'),
inlineCode('!rfc id:29'),
inlineCode('!rfc title:initial placeholder'),
inlineCode('!rfc body:empty node'),
inlineCode('!rfc author:yyx'),
inlineCode('!rfc label:core'),
inlineCode('!rfc label:breaking change,router'),
inlineCode('!rfc label:3.x | core'),
],
group: 'rfcs',
guildOnly: false,
memberName: 'rfc',
description: 'Search for and view a Vue RFC.',
argsPromptLimit: 1,
})
}

hasPermission() {
return true
}

async run(msg, args) {
let { query } = args
let success = false
let [filter, value] = query

let embed

try {
let rfcs

if (filter === 'empty') {
rfcs = await findRFCs(value)
} else {
rfcs = await filterRFCsBy(filter, value)
}

if (rfcs.length === 0) {
throw new RFCDoesNotExistError()
} else if (rfcs.length === 1) {
embed = this.buildResponseEmbed(msg, rfcs[0])
} else {
return respondWithPaginatedEmbed(
msg,
this.buildDisambiguationEmbed(msg, rfcs, filter, value),
rfcs.map(rfc => this.buildResponseEmbed(msg, rfc, filter, value))
)
}
success = true // For finally block.
} catch (error) {
if (error instanceof RFCDoesNotExistError) {
embed = this.buildErrorEmbed(
msg,
"Sorry, I couldn't find any matches for your query on the RFC repo.",
query
)
} else {
console.error(error)
embed = this.buildErrorEmbed(
msg,
'Sorry, an unspecified error occured!',
query
)
}
} finally {
const reply = await msg.channel.send(EMPTY_MESSAGE, embed)
cleanupInvocation(msg)

if (!success) {
cleanupErrorResponse(reply)
}
}
}

buildErrorEmbed(msg, error, query = []) {
let [filter, value] = query
let lookup = filter === 'empty' ? value : `${filter}:${value}`

return new RichEmbed()
.setTitle(`RFC Lookup - ${inlineCode(lookup)}`)
.setDescription(error)
.setAuthor(
(msg.member ? msg.member.displayName : msg.author.username) +
' requested:',
msg.author.avatarURL
)
.setColor('RED')
}

buildResponseEmbed(msg, rfc) {
const embed = new RichEmbed()
.setTitle(`RFC #${rfc.number} - ${rfc.title}`)
.setURL(rfc.html_url)
.setThumbnail('attachment://vue.png')
.attachFile({
attachment: 'assets/images/icons/vue.png',
name: 'vue.png',
})
.addField('Author', rfc.user.login, true)
.addField('Status', rfc.state, true)

embed.setDescription(
addEllipsis(
rfc.body.replace(/(?<=```)[ ]*(?=\w+)/g, ''),
DISCORD_EMBED_DESCRIPTION_LIMIT
)
)

let footerSections = []

if (rfc.created_at) {
footerSections.push(
'Created: ' + new Date(rfc.created_at).toLocaleDateString()
)
}

if (rfc.updated_at) {
footerSections.push(
'Updated: ' + new Date(rfc.updated_at).toLocaleDateString()
)
}

if (footerSections.length) {
embed.setFooter(footerSections.join(' | '))
}

if (rfc.labels.length) {
embed.addField(
'Labels',
rfc.labels.map(label => label.name).join(', '),
true
)
}

let labelsWithColours = rfc.labels.filter(label =>
['core', 'vuex', 'router'].includes(label.name)
)

if (labelsWithColours.length) {
embed.setColor(`#${labelsWithColours[0].color}`)
} else {
embed.setColor(DEFAULT_EMBED_COLOUR)
}

return embed
}

buildDisambiguationEmbed(msg, rfcs, filter, value) {
let query = filter === 'empty' ? value : `${filter}:${value}`

return new RichEmbed()
.setTitle(`RFC Request - ${inlineCode(query)}`)
.setDescription(
`Sorry, I couldn't find an exact match for your query on the RFC repo.`
)
.setThumbnail('attachment://vue.png')
.attachFile({
attachment: 'assets/images/icons/vue.png',
name: 'vue.png',
})
.addField(
'Perhaps you meant one of these:',
rfcs.map(rfc => inlineCode('#' + rfc.number)).join(', ')
)
.setColor('BLUE')
}
}
Loading