Skip to content

Commit dfa51d0

Browse files
committed
feat: Library command + service.
1 parent e52a4c2 commit dfa51d0

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Command } from 'discord.js-commando'
2+
import { getLibrary } from '../../services/libraries'
3+
import { RichEmbed } from 'discord.js'
4+
import { EMPTY_MESSAGE } from '../../utils/constants'
5+
import { tryDelete } from '../../utils/messages'
6+
import { uppercaseFirst } from '../../utils/string'
7+
8+
const DELETE_ERRORS_AFTER = 30000
9+
10+
module.exports = class DocumentationLibraryCommand extends Command {
11+
constructor(client) {
12+
super(client, {
13+
args: [
14+
{
15+
key: 'name',
16+
type: 'string',
17+
prompt: 'which library to look up?',
18+
},
19+
],
20+
name: 'library',
21+
group: 'documentation',
22+
aliases: ['lib', 'l'],
23+
examples: [
24+
`!library quasar`,
25+
`!library vuetify`,
26+
`!library nuxt`,
27+
`!library gridsome`,
28+
`!library saber`,
29+
`!library vuepress`,
30+
],
31+
guildOnly: false,
32+
memberName: 'library',
33+
description: 'Look up a library/framework by name.',
34+
})
35+
}
36+
37+
hasPermission() {
38+
return true
39+
}
40+
41+
async run(msg, args) {
42+
const { name } = args
43+
44+
try {
45+
let library = getLibrary(name)
46+
const embed = this.buildResponseEmbed(library)
47+
48+
return msg.channel.send(EMPTY_MESSAGE, { embed })
49+
} catch (error) {
50+
console.error(error)
51+
const embed = this.buildErrorEmbed(name)
52+
const response = await msg.channel.send(EMPTY_MESSAGE, { embed })
53+
54+
tryDelete(msg)
55+
tryDelete(response, DELETE_ERRORS_AFTER)
56+
}
57+
}
58+
59+
buildResponseEmbed(library) {
60+
const embed = new RichEmbed()
61+
.setURL(library.url.site)
62+
.setTitle(library.name)
63+
.setColor(library.colour)
64+
65+
if (library.tagline) {
66+
embed.setDescription(library.tagline)
67+
}
68+
69+
if (library.tags.length) {
70+
embed.setFooter('Tags: ' + library.tags.join(', '))
71+
}
72+
73+
if (library.icon) {
74+
embed.setThumbnail(`attachment://${library.icon}`).attachFile({
75+
attachment: `assets/images/icons/${library.icon}`,
76+
name: library.icon,
77+
})
78+
}
79+
80+
if (library.author) {
81+
embed.setAuthor(
82+
library.author.name,
83+
library.author.avatar,
84+
library.author.url
85+
)
86+
}
87+
88+
if (library.fields) {
89+
for (const field of library.fields) {
90+
if (typeof field === 'object') {
91+
embed.addField(field.name, field.value)
92+
} else {
93+
embed.addField(EMPTY_MESSAGE, field)
94+
}
95+
}
96+
}
97+
98+
for (const [name, url] of Object.entries(library.url)) {
99+
embed.addField(uppercaseFirst(name), url, true)
100+
}
101+
102+
return embed
103+
}
104+
105+
buildErrorEmbed(name) {
106+
return new RichEmbed()
107+
.setTitle('Library Lookup')
108+
.setColor('RED')
109+
.setDescription(
110+
`Could not find a library with the name: ${name}.\n\nThink it should be included?`
111+
)
112+
.addField('Submit PR', 'https://git.io/JenP0', true)
113+
.addField('File issue', 'https://git.io/JenP2', true)
114+
}
115+
}

src/services/libraries.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { join, resolve } from 'path'
2+
3+
const DATA_DIR = resolve(__dirname, '../../data/libraries')
4+
const AVATAR_BASE_URL =
5+
'https://raw.githubusercontent.com/sustained/vue-land-bot/feat-library/assets/images/avatars/'
6+
7+
/*
8+
TODO: We should probably just scan the directory but not sure how to handle
9+
exporting things which are loaded in a callback or a promise then handler?
10+
*/
11+
const LIBRARY_NAMES = [
12+
'gridsome',
13+
'nuxt',
14+
'quasar',
15+
'saber',
16+
'vuepress',
17+
'vuetify',
18+
]
19+
20+
const libraries = {}
21+
22+
for (const libraryName of LIBRARY_NAMES) {
23+
try {
24+
const library = require(join(DATA_DIR, `${libraryName}.json`))
25+
libraries[libraryName] = _validateLibrary(library)
26+
} catch (error) {
27+
console.warn(
28+
`[LibraryService] Something went wrong when requiring or validating "${libraryName}.json":`
29+
)
30+
console.error(error)
31+
}
32+
}
33+
34+
export default libraries
35+
36+
console.log(libraries['saber'])
37+
38+
/**
39+
* @typedef {Object} LibraryDefinition
40+
* @property {string} name The name of the library.
41+
* @property {string} [icon] An optional icon path.
42+
* @property {Array<string>} [tags=[]] An array of tags.
43+
* @property {string} tagline The library's tagline or description.
44+
* @property {Array<{name: string, value: string}>} fields An array of field objects.
45+
* @property {{name: string, url: string, avatar: string}} [author] The author of the library.
46+
* @property {{site: string, docs: string, repo: string, bugs: string}} url Various URLs relating to the library.
47+
*/
48+
49+
/**
50+
*
51+
* @param {string} name The name of the library.
52+
* @returns {LibraryDefinition} The library object.
53+
*/
54+
export function getLibrary(name) {
55+
if (libraries[name]) {
56+
return libraries[name]
57+
}
58+
59+
// TODO: Check aliases and/or use an algorithm such as the
60+
// Levenshtein distance to find the nearest match.
61+
62+
throw new Error(`[LibraryService] Could not find library: ${name}`)
63+
}
64+
65+
/**
66+
* Ensure that the structure of the JSON is as we expect it to be,
67+
* including required fields etc.
68+
*
69+
* @param {Object} library The parsed library JSON file.
70+
* @returns {LibraryDefinition} The validated library object.
71+
* @throws If certain fields are missing or of the wrong type.
72+
*/
73+
function _validateLibrary(library) {
74+
if (typeof library !== 'object') {
75+
throw new TypeError('Object expected')
76+
}
77+
78+
if (typeof library.name === 'undefined') {
79+
throw new Error('Field "name" required')
80+
}
81+
82+
if (typeof library.url === 'undefined') {
83+
throw new Error('Field "url" required')
84+
}
85+
86+
if (typeof library.fields === 'undefined') {
87+
throw new Error('Field "fields" required')
88+
}
89+
90+
if (!Array.isArray(library.fields)) {
91+
throw new TypeError('Field "fields" must be of type "Array"')
92+
}
93+
94+
if (!Array.isArray(library.tags)) {
95+
library.tags = []
96+
}
97+
98+
if (typeof library.colour === 'undefined') {
99+
library.colour = 'RANDOM'
100+
}
101+
102+
if (library.author && library.author.avatar) {
103+
library.author.avatar = AVATAR_BASE_URL + library.author.avatar
104+
}
105+
106+
return library
107+
}

0 commit comments

Comments
 (0)