@@ -3,16 +3,26 @@ import {
3
3
SlashCommandBuilder ,
4
4
} from "@discordjs/builders" ;
5
5
import { REST } from "@discordjs/rest" ;
6
- import { ApplicationCommandType , Routes } from "discord-api-types/v9" ;
6
+ import {
7
+ APIApplicationCommand ,
8
+ ApplicationCommandType ,
9
+ Routes ,
10
+ } from "discord-api-types/v9" ;
7
11
import { applicationId , discordToken , guildId } from "../src/constants" ;
8
12
import { logger } from "../src/features/log" ;
13
+ import { difference } from "../src/helpers/sets" ;
9
14
10
- const cmd = {
11
- name : "test action" ,
12
- description : "¡some kind of test action!" ,
13
- handler : ( ) => { } ,
14
- } ;
15
- const cmds = [ cmd ] ;
15
+ // TODO: make this a global command in production
16
+ const upsertUrl = ( ) => Routes . applicationGuildCommands ( applicationId , guildId ) ;
17
+ const deleteUrl = ( commandId : string ) =>
18
+ Routes . applicationGuildCommand ( applicationId , guildId , commandId ) ;
19
+
20
+ interface CommandConfig {
21
+ name : string ;
22
+ description : string ;
23
+ type : ApplicationCommandType ;
24
+ }
25
+ const cmds : CommandConfig [ ] = [ ] ;
16
26
17
27
const commands = [
18
28
...cmds
@@ -44,15 +54,68 @@ const commands = [
44
54
. toJSON ( ) ,
45
55
) ,
46
56
] ;
57
+ const names = new Set ( commands . map ( ( c ) => c . name ) ) ;
47
58
48
59
const rest = new REST ( { version : "9" } ) . setToken ( discordToken ) ;
60
+ const deploy = async ( ) => {
61
+ const remoteCommands = ( await rest . get (
62
+ upsertUrl ( ) ,
63
+ ) ) as APIApplicationCommand [ ] ;
64
+
65
+ // Take the list of names to delete and swap it out for IDs to delete
66
+ const remoteNames = new Set ( remoteCommands . map ( ( c ) => c . name ) ) ;
67
+ const deleteNames = [ ...difference ( remoteNames , names ) ] ;
68
+ const toDelete = deleteNames
69
+ . map ( ( x ) => remoteCommands . find ( ( y ) => y . name === x ) ?. id )
70
+ . filter ( ( x ) : x is string => Boolean ( x ) ) ;
49
71
50
- rest
51
- // TODO: make this a global command in production
52
- . put ( Routes . applicationGuildCommands ( applicationId , guildId ) , {
53
- body : commands ,
54
- } )
55
- . then ( ( ) =>
56
- logger . log ( "DEPLOY" , "Successfully registered application commands." ) ,
57
- )
58
- . catch ( ( e ) => logger . log ( "DEPLOY" , e ) ) ;
72
+ logger . log (
73
+ "DEPLOY" ,
74
+ `Removing ${ toDelete . length } commands: [${ deleteNames . join ( "," ) } ]` ,
75
+ ) ;
76
+ await Promise . allSettled (
77
+ toDelete . map ( ( commandId ) => rest . delete ( deleteUrl ( commandId ) ) ) ,
78
+ ) ;
79
+
80
+ // Grab a list of commands that need to be updated
81
+ const toUpdate = remoteCommands . filter (
82
+ ( c ) =>
83
+ // Check all necessary fields to see if any changed. User and Message
84
+ // commands don't have a description.
85
+ ! commands . find ( ( x ) => {
86
+ const {
87
+ type = ApplicationCommandType . ChatInput ,
88
+ name,
89
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
90
+ // @ts -expect-error Unions are weird
91
+ description = "" ,
92
+ } = x ;
93
+ switch ( type as ApplicationCommandType ) {
94
+ case ApplicationCommandType . User :
95
+ case ApplicationCommandType . Message :
96
+ return name === c . name && type === c . type ;
97
+ case ApplicationCommandType . ChatInput :
98
+ default :
99
+ return (
100
+ name === c . name &&
101
+ type === c . type &&
102
+ description === c . description
103
+ ) ;
104
+ }
105
+ } ) ,
106
+ ) ;
107
+
108
+ logger . log (
109
+ "DEPLOY" ,
110
+ `Updating ${ toUpdate . length } commands: [${ toUpdate
111
+ . map ( ( x ) => x . name )
112
+ . join ( "," ) } ]`,
113
+ ) ;
114
+
115
+ await rest . put ( upsertUrl ( ) , { body : commands } ) ;
116
+ } ;
117
+ try {
118
+ deploy ( ) ;
119
+ } catch ( e ) {
120
+ logger . log ( "DEPLOY EXCEPTION" , e as string ) ;
121
+ }
0 commit comments