-
Notifications
You must be signed in to change notification settings - Fork 40
/
deploy-commands.ts
123 lines (113 loc) · 3.7 KB
/
deploy-commands.ts
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
import {
ContextMenuCommandBuilder,
SlashCommandBuilder,
} from "@discordjs/builders";
import { REST } from "@discordjs/rest";
import {
APIApplicationCommand,
ApplicationCommandType,
Routes,
} from "discord-api-types/v9";
import { applicationId, discordToken, guildId } from "../src/constants";
import { logger } from "../src/features/log";
import { difference } from "../src/helpers/sets";
import * as report from "../src/commands/report";
// TODO: make this a global command in production
const upsertUrl = () => Routes.applicationGuildCommands(applicationId, guildId);
const deleteUrl = (commandId: string) =>
Routes.applicationGuildCommand(applicationId, guildId, commandId);
interface CommandConfig {
name: string;
description: string;
type: ApplicationCommandType;
}
const cmds: CommandConfig[] = [report];
const commands = [
...cmds
.filter((x) => x.type === ApplicationCommandType.ChatInput)
.map((c) =>
new SlashCommandBuilder()
.setName(c.name)
.setDescription(c.description)
.toJSON(),
),
...cmds
.filter((x) => x.type === ApplicationCommandType.Message)
.map((c) =>
new ContextMenuCommandBuilder()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Discord.js doesn't export the union we need
.setType(ApplicationCommandType.Message)
.setName(c.name)
.toJSON(),
),
...cmds
.filter((x) => x.type === ApplicationCommandType.User)
.map((c) =>
new ContextMenuCommandBuilder()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Discord.js doesn't export the union we need
.setType(ApplicationCommandType.User)
.setName(c.name)
.toJSON(),
),
];
const names = new Set(commands.map((c) => c.name));
const rest = new REST({ version: "9" }).setToken(discordToken);
const deploy = async () => {
const remoteCommands = (await rest.get(
upsertUrl(),
)) as APIApplicationCommand[];
// Take the list of names to delete and swap it out for IDs to delete
const remoteNames = new Set(remoteCommands.map((c) => c.name));
const deleteNames = [...difference(remoteNames, names)];
const toDelete = deleteNames
.map((x) => remoteCommands.find((y) => y.name === x)?.id)
.filter((x): x is string => Boolean(x));
logger.log(
"DEPLOY",
`Removing ${toDelete.length} commands: [${deleteNames.join(",")}]`,
);
await Promise.allSettled(
toDelete.map((commandId) => rest.delete(deleteUrl(commandId))),
);
// Grab a list of commands that need to be updated
const toUpdate = remoteCommands.filter(
(c) =>
// Check all necessary fields to see if any changed. User and Message
// commands don't have a description.
!commands.find((x) => {
const {
type = ApplicationCommandType.ChatInput,
name,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Unions are weird
description = "",
} = x;
switch (type as ApplicationCommandType) {
case ApplicationCommandType.User:
case ApplicationCommandType.Message:
return name === c.name && type === c.type;
case ApplicationCommandType.ChatInput:
default:
return (
name === c.name &&
type === c.type &&
description === c.description
);
}
}),
);
logger.log(
"DEPLOY",
`Updating ${toUpdate.length} commands: [${toUpdate
.map((x) => x.name)
.join(",")}]`,
);
await rest.put(upsertUrl(), { body: commands });
};
try {
deploy();
} catch (e) {
logger.log("DEPLOY EXCEPTION", e as string);
}