Skip to content

Commit 2ecc1fa

Browse files
author
–ステラ
committed
fix(Commands): add autocomplete to getVtubers
1 parent 9647baa commit 2ecc1fa

File tree

6 files changed

+204
-123
lines changed

6 files changed

+204
-123
lines changed

.github/COMMIT_CONVENTION.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
## Git Commit Message Convention
2+
3+
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
4+
5+
### TL;DR:
6+
7+
Messages must be matched by the following regex:
8+
9+
```js
10+
/^(revert: )?(feat|fix|style|refactor|perf|test|workflow|ci|chore|types|wip)(\(.+\))?: .{1,72}/;
11+
```
12+
13+
### Known issues
14+
15+
husky hooks are currently not compatible with GitHub Desktop's commit tool on Node 16+. There is an [open issue](https://github.com/desktop/desktop/issues/12562) about this on their repo, therefore there is nothing we can do about this. We recommend either committing through the command line, or using VSCode's source control.
16+
17+
### Examples
18+
19+
Appears under the "Features" header, `Command` subheader in a potential changelog:
20+
21+
```
22+
feat(Command): add 'foo' command
23+
```
24+
25+
Appears under the "Bug Fixes" header, `Database` subheader, with a link to issue #28 in a potential changelog:
26+
27+
```
28+
fix(Database): handle caching
29+
30+
close #28
31+
```
32+
33+
Appears under the "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation in a potential changelog:
34+
35+
```
36+
perf(Server): improve patching by removing 'bar' option
37+
38+
BREAKING CHANGE: The 'bar' option has been removed.
39+
```
40+
41+
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
42+
43+
```
44+
revert: feat: Discussions (#4662)
45+
46+
This reverts commit c8cb81ec50aae940efa35ad3d69ee69c2db89125.
47+
```
48+
49+
### Full Message Format
50+
51+
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
52+
53+
```
54+
<type>(<scope>): <subject>
55+
<BLANK LINE>
56+
<body>
57+
<BLANK LINE>
58+
<footer>
59+
```
60+
61+
The **header** and the **subject** are mandatory, while the **scope** of the header is optional.
62+
63+
### Revert
64+
65+
If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
66+
67+
### Type
68+
69+
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
70+
71+
Other prefixes are up to your discretion. Suggested prefixes are `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
72+
73+
### Scope
74+
75+
The scope could be anything specifying the place of the commit change, usually the name of a Presence. For example `Command`, `Database`, `SE` (syntax enforcer script), `Server` etc...
76+
77+
### Subject
78+
79+
The subject contains a succinct description of the change:
80+
81+
- use the imperative, present tense: "change" not "changed" nor "changes"
82+
- don't capitalize the first letter
83+
- no dot (.) at the end
84+
85+
### Body
86+
87+
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
88+
89+
### Footer
90+
91+
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
92+
93+
**Breaking Changes** should start with the phrase `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.

commitlint.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
extends: ['@commitlint/config-conventional'],
3+
};
4+

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"dev::idle": "ts-node src/app.ts",
1111
"deploy-commands": "ts-node src/deploy-commands.ts",
1212
"postinstall": "npm run build",
13-
"build": "tsc"
13+
"build": "tsc",
14+
"commitmsg": "commitlint --edit $HUSKY_GIT_PARAMS"
1415
},
1516
"post_data": {
1617
"allowDb": false
@@ -30,6 +31,7 @@
3031
"@types/express": "^4.17.18",
3132
"@types/nodemailer": "^6.4.13",
3233
"eslint": "^8.52.0",
34+
"husky": "^8.0.3",
3335
"nodemon": "^3.0.1",
3436
"ts-loader": "^9.4.4",
3537
"ts-node": "^10.9.1",
@@ -58,5 +60,10 @@
5860
"engines": {
5961
"node": "20.10.0",
6062
"npm": "10.2.4"
61-
}
63+
},
64+
"husky": {
65+
"hooks": {
66+
"commit-msg": "npm run commitmsg"
67+
}
68+
}
6269
}
Lines changed: 67 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { SlashCommandBuilder, CommandInteraction, EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, Events } from "discord.js";
1+
import { SlashCommandBuilder, Interaction, EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, Events, CommandInteraction } from 'discord.js';
22
import ISlashCommand from "../../../interfaces/ISlashCommand";
33
import Sdk from "vtuberwiki-sdk";
44

55

6-
6+
const sdk = Sdk.getInstance();
77

88

99

@@ -12,112 +12,72 @@ export const command: ISlashCommand = {
1212
.setName("get-vtuber")
1313
.setDescription("Get info about a vtuber.")
1414
.addStringOption(option =>
15-
option.setName("name")
16-
.setDescription("The name of the vtuber you want to get info for.")
17-
.setRequired(false)),
15+
option.setName('query')
16+
.setDescription('Phrase to search for')
17+
.setAutocomplete(true)),
18+
async autocomplete(interaction: Interaction) {
19+
20+
const vtubers = await sdk.getVtubers();
21+
//@ts-ignore
22+
const focusedValue = interaction.options.getFocused();
23+
const choices = vtubers.map((choice: any) => choice.name);
24+
const filtered = choices.filter((choice: any) => choice.startsWith(focusedValue));
25+
//@ts-ignore
26+
await interaction.respond(
27+
filtered.map((choice: any) => ({ name: choice, value: choice.toLowerCase() })),
28+
);
29+
30+
},
31+
1832
async execute(interaction: CommandInteraction) {
19-
try {
20-
//@ts-ignore
21-
const name = interaction.options.getString("name");
22-
23-
const sdk = Sdk.getInstance();
24-
25-
const vtubers = await sdk.getVtubers();
26-
27-
if (!name) {
28-
29-
const embed = new EmbedBuilder()
30-
.setTitle("Vtubers")
31-
.setDescription("A list of all available vtubers.\n\nView more: https://vtubers.wiki/wiki/vtubers")
32-
.setTimestamp()
33-
.setFooter({
34-
text: "Powered by https://vtubers.wiki/sdk/node",
35-
iconURL: "https://pbs.twimg.com/profile_images/1713923311858593792/doH2HOXp_400x400.png"
36-
});
37-
38-
vtubers.forEach((vtuber: any, index: number) => {
39-
// Check if the index is more than 10
40-
if (index > 10) return;
41-
42-
embed.addFields({
43-
name: vtuber.name,
44-
value: vtuber.description,
45-
});
46-
});
47-
48-
await interaction.reply({ embeds: [embed] });
49-
return;
50-
}
51-
52-
// Find anything that matches the name lowercase (using a regex) (make it return an array)
53-
const matches = vtubers.filter((vtuber: any) => {
54-
const vtuberName = vtuber.name.toLowerCase();
55-
return name && vtuberName.match(new RegExp(name.toLowerCase()));
33+
//@ts-ignore
34+
const query = interaction.options.getString('query');
35+
36+
if (!query) return interaction.reply({ content: "Please provide a query!", ephemeral: true });
37+
38+
const vtubers = await sdk.getVtubers();
39+
40+
const vtuber = vtubers.find((vtuber: any) => vtuber.name.toLowerCase() === query.toLowerCase());
41+
42+
const embed = new EmbedBuilder()
43+
.setTitle(vtuber.name)
44+
.setURL(`https://vtubers.wiki${vtuber.link}`)
45+
.setColor(vtuber.border_color)
46+
.setThumbnail(`https://vtubers.wiki${vtuber.image}`)
47+
.setImage(`https://vtubers.wiki${vtuber.banner}`)
48+
.setDescription(vtuber.description)
49+
.addFields(
50+
{ name: "Category:", value: vtuber.category, inline: true },
51+
{ name: "Author:", value: `[@${vtuber.author}](https://github.com/${vtuber.author})`, inline: true },
52+
{ name: "Graduated:", value: vtuber.graduated, inline: true },
53+
{ name: "Is a Draft", value: vtuber.is_draft, inline: true },
54+
)
55+
.setTimestamp()
56+
.setFooter({
57+
text: "Powered by https://vtubers.wiki/sdk/node",
58+
iconURL: "https://pbs.twimg.com/profile_images/1713923311858593792/doH2HOXp_400x400.png"
5659
});
5760

58-
// If there are no matches, return an error
59-
if (matches.length === 0) return await interaction.reply({ content: "No matches found.", ephemeral: true });
60-
61-
// If there is only one match, use that
62-
if (matches.length === 1) {
63-
const vtuber = matches[0];
64-
65-
const embed = new EmbedBuilder()
66-
.setTitle(vtuber.name)
67-
.setURL(`https://vtubers.wiki${vtuber.link}`)
68-
.setColor(vtuber.border_color)
69-
.setThumbnail(`https://vtubers.wiki${vtuber.image}`)
70-
.setImage(`https://vtubers.wiki${vtuber.banner}`)
71-
.setDescription(vtuber.description)
72-
.addFields(
73-
{ name: "Category:", value: vtuber.category, inline: true },
74-
{ name: "Author:", value: `[@${vtuber.author}](https://github.com/${vtuber.author})`, inline: true },
75-
{ name: "Graduated:", value: vtuber.graduated, inline: true },
76-
{ name: "Is a Draft", value: vtuber.is_draft, inline: true },
77-
)
78-
.setTimestamp()
79-
.setFooter({
80-
text: "Powered by https://vtubers.wiki/sdk/node",
81-
iconURL: "https://pbs.twimg.com/profile_images/1713923311858593792/doH2HOXp_400x400.png"
82-
});
83-
84-
const showLinkButton = new ButtonBuilder()
85-
.setLabel("Show on vtubers.wiki")
86-
.setStyle(ButtonStyle.Link)
87-
.setURL(`https://vtubers.wiki${vtuber.link}`)
88-
.setEmoji("ℹ️");
89-
90-
const row = new ActionRowBuilder()
91-
.addComponents(showLinkButton);
92-
93-
//@ts-ignore
94-
await interaction.reply({ embeds: [embed], components: [row] });
95-
96-
} else {
97-
// If there are multiple matches, return a list of them
98-
function makeList(array: any[]) {
99-
let list = "";
100-
array.forEach((item: any) => {
101-
list += `[**${item.name}**](https://vtubers.wiki${item.link})\n`;
102-
})
103-
return list;
104-
}
105-
const embed = new EmbedBuilder()
106-
.setTitle("Multiple matches found.")
107-
.setDescription(makeList(matches))
108-
.setTimestamp()
109-
.setFooter({
110-
text: "Powered by https://vtubers.wiki/sdk/node",
111-
iconURL: "https://pbs.twimg.com/profile_images/1713923311858593792/doH2HOXp_400x400.png"
112-
});
113-
114-
115-
await interaction.reply({ embeds: [embed] });
116-
}
117-
118-
} catch (error) {
119-
console.error(error);
120-
await interaction.reply({ content: "There was an error while executing this command!", ephemeral: true });
121-
}
122-
},
61+
const slug = vtuber.link.split('/').pop();
62+
63+
const showLinkButton = new ButtonBuilder()
64+
.setLabel("Show on vtubers.wiki")
65+
.setStyle(ButtonStyle.Link)
66+
.setURL(`https://vtubers.wiki${vtuber.link}`)
67+
.setEmoji("ℹ️");
68+
69+
const editLinkButton = new ButtonBuilder()
70+
.setLabel(`Want to edit this entry?`)
71+
.setStyle(ButtonStyle.Link)
72+
.setURL(`https://github.com/vtuberwiki/wiki/blob/main/src/content/vtubers/${slug}.md`)
73+
.setEmoji("📝");
74+
75+
const row = new ActionRowBuilder()
76+
.addComponents(showLinkButton, editLinkButton);
77+
78+
//@ts-ignore
79+
await interaction.reply({ embeds: [embed], components: [row] });
80+
81+
return;
82+
}
12383
} as ISlashCommand;

src/events/interactionCreate.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,43 @@
1-
import { Events, CommandInteraction } from "discord.js";
1+
import { Events, CommandInteraction, Interaction } from "discord.js";
22
import IClient from "../interfaces/IClient";
33

44

55

6+
67
module.exports = {
78
name: Events.InteractionCreate,
89
once: false,
9-
async execute(interaction: CommandInteraction, client: IClient) {
10-
if (!interaction.isChatInputCommand()) return;
10+
async execute(interaction: Interaction, client: IClient) {
11+
if (interaction.isChatInputCommand()) {
12+
const command = client.commands.get(interaction.commandName);
13+
14+
if (!command) return;
15+
16+
if (!interaction.guild) return;
1117

12-
const command = client.commands.get(interaction.commandName);
18+
try {
19+
if (!command.execute) return;
20+
await command.execute(interaction);
21+
console.log(`${interaction.user.tag} used command ${interaction.commandName} in guild ${interaction.guild?.name} (${interaction.guild?.id}) in the channel ${interaction.guild?.channels.cache.get(interaction.channel?.id as string)?.name} (${interaction.channel?.id})`);
22+
} catch (error) {
23+
console.error(`Error while executing command ${interaction.commandName}: ${error}`);
24+
await interaction.reply({ content: "There was an error while executing this command!", ephemeral: true });
1325

14-
if (!command) return;
26+
}
27+
} else if (interaction.isAutocomplete()) {
28+
const command = client.commands.get(interaction.commandName);
1529

16-
if (!interaction.guild) return;
30+
if (!command) return;
1731

18-
try {
19-
await command.execute(interaction);
20-
console.log(`${interaction.user.tag} used command ${interaction.commandName} in guild ${interaction.guild?.name} (${interaction.guild?.id}) in the channel ${interaction.guild?.channels.cache.get(interaction.channel?.id as string)?.name} (${interaction.channel?.id})`);
21-
} catch (error) {
22-
console.error(`Error while executing command ${interaction.commandName}: ${error}`);
23-
await interaction.reply({ content: "There was an error while executing this command!", ephemeral: true });
32+
if (!interaction.guild) return;
2433

34+
try {
35+
if (!command.autocomplete) return;
36+
await command.autocomplete(interaction);
37+
console.log(`${interaction.user.tag} autocompleted command ${interaction.commandName} in guild ${interaction.guild?.name} (${interaction.guild?.id}) in the channel ${interaction.guild?.channels.cache.get(interaction.channel?.id as string)?.name} (${interaction.channel?.id})`);
38+
} catch (error) {
39+
console.error(`Error while executing command ${interaction.commandName}: ${error}`);
40+
}
2541
}
2642
}
2743
}

src/interfaces/ISlashCommand.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { SlashCommandBuilder, CommandInteraction } from "discord.js";
1+
import { SlashCommandBuilder, CommandInteraction, AutocompleteInteraction } from "discord.js";
22

33
interface ISlashCommand {
44
data: SlashCommandBuilder;
5-
execute: (interaction: CommandInteraction) => Promise<void>;
5+
execute?: (interaction: CommandInteraction) => Promise<void>;
6+
autocomplete?: (interaction: AutocompleteInteraction) => Promise<void>;
67
}
78

89
export default ISlashCommand;

0 commit comments

Comments
 (0)