11import * as Discord from "discord.js" ;
22import { EphemeralResponse , SlashCommand } from "@/extensions/discord.js" ;
3- import GameServer from "@/app/services/gamebridge/GameServer.js" ;
43import servers from "@/config/gamebridge.servers.json" with { type : "json" } ;
54
65// order matters for the menu
76const VALID_GSERV_COMMANDS : [ string , string ] [ ] = [
7+ [ "qu rehash" , "runs both qu and rehash" ] ,
88 [ "qu" , "Quickly updates repositories." ] ,
99 [ "rehash" , "Rehashes the server." ] ,
1010 [ "merge_repos" , "Prepares all repositories for rehash." ] ,
@@ -13,217 +13,115 @@ const VALID_GSERV_COMMANDS: [string, string][] = [
1313 [ "status" , "Shows server status." ] ,
1414] ;
1515
16- const SERVER_EMOJI_MAP = {
17- "1" : "1️⃣" ,
18- "2" : "2️⃣" ,
19- "3" : "3️⃣" ,
20- "4" : "4️⃣" ,
21- } ;
22-
23- const gserv = async (
24- ctx : Discord . ButtonInteraction ,
25- gameServer : GameServer ,
26- commands : string [ ] ,
27- solo : boolean ,
28- output : boolean
29- ) : Promise < boolean > => {
30- try {
31- let buffer = "" ;
32-
33- await gameServer . sshExec ( "gserv" , commands , {
34- stream : "stderr" ,
35- onStdout : buff => ( buffer += buff ) ,
36- onStderr : buff => ( buffer += buff ) ,
37- } ) ;
38-
39- const success = ! buffer . includes ( "GSERV FAILED" ) ;
40-
41- const fileName = `${ commands . join ( "_" ) } _${ gameServer . config . id } _${ Date . now ( ) } .ansi` ;
42- let msgContent = gameServer . config . name ;
43- if ( ! success ) msgContent += " FAILED" ;
44-
45- const response : Discord . BaseMessageOptions = {
46- content : msgContent ,
47- files : [ { attachment : Buffer . from ( buffer ) , name : fileName } ] ,
48- } ;
49-
50- if ( output || success === false ) {
51- solo ? await ctx . editReply ( response ) : await ctx . followUp ( response ) ;
52-
53- if ( solo ) {
54- const msg = await ctx . fetchReply ( ) ;
55- await msg . react ( success ? "✅" : "❌" ) ;
56- }
57- } else {
58- const msg = await ctx . fetchReply ( ) ;
59- await msg . react ( SERVER_EMOJI_MAP [ gameServer . config . id ] ?? "❓" ) ;
60- }
61- return success ;
62- } catch ( err ) {
63- const msg = gameServer . config . name + `\ngserv failed!\`\`\`\n${ err } \`\`\`` ;
64- if ( solo ) {
65- await ctx . editReply ( msg ) ;
66- } else {
67- await ctx . followUp ( msg ) ;
68- }
69- return false ;
70- }
71- } ;
72-
7316export const SlashGservCommand : SlashCommand = {
7417 options : {
7518 name : "gserv" ,
7619 description : "Gserv from discord" ,
7720 default_member_permissions : "0" ,
21+ options : [
22+ {
23+ type : Discord . ApplicationCommandOptionType . String ,
24+ name : "command" ,
25+ description : "the command to run" ,
26+ choices : VALID_GSERV_COMMANDS . map ( c => {
27+ return { name : c [ 0 ] , value : c [ 0 ] } ;
28+ } ) ,
29+ required : true ,
30+ } ,
31+ {
32+ type : Discord . ApplicationCommandOptionType . Integer ,
33+ name : "server" ,
34+ description : "The server to run the command on" ,
35+ choices : servers
36+ . filter ( s => ! ! s . ssh )
37+ . map ( s => {
38+ return { name : s . name , value : s . id } ;
39+ } ) ,
40+ } ,
41+ {
42+ type : Discord . ApplicationCommandOptionType . Boolean ,
43+ name : "show_output" ,
44+ description : "show gserv output" ,
45+ } ,
46+ ] ,
7847 } ,
7948
8049 async execute ( ctx , bot ) {
81- const filter = ( i : Discord . Interaction ) => i . user . id === ctx . user . id ;
8250 const bridge = bot . bridge ;
8351 if ( ! bridge ) {
8452 await ctx . reply ( EphemeralResponse ( "GameBridge is missing :(" ) ) ;
8553 console . error ( `SlashGserv: GameBridge missing?` , ctx ) ;
8654 return ;
8755 }
56+ const selectedServer = ctx . options . getInteger ( "server" ) ;
57+ const servers = selectedServer ? [ bridge . servers [ selectedServer ] ] : bridge . servers ;
58+ const command = ctx . options . getString ( "command" , true ) ;
59+ const showOutput = ctx . options . getBoolean ( "show_output" ) ?? false ;
8860
89- try {
90- const response = await ctx . reply ( {
91- content : "What command do you want to run?" ,
92- components : [
93- {
94- type : Discord . ComponentType . ActionRow ,
95- components : [
96- {
97- type : Discord . ComponentType . StringSelect ,
98- custom_id : "gserv_command" ,
99- placeholder : "Choose a command." ,
100- min_values : 1 ,
101- max_values : VALID_GSERV_COMMANDS . length ,
102- options : VALID_GSERV_COMMANDS . map (
103- cmd =>
104- < Discord . APISelectMenuOption > {
105- label : cmd [ 0 ] ,
106- value : cmd [ 0 ] ,
107- description : cmd [ 1 ] ,
108- }
109- ) ,
110- } ,
111- ] ,
112- } ,
113- ] ,
114- } ) ;
115-
116- const result = await response . awaitMessageComponent ( {
117- componentType : Discord . ComponentType . StringSelect ,
118- filter : filter ,
119- time : 60000 ,
120- } ) ;
121- const commands = result . values ;
61+ const reply = await ctx . deferReply ( { withResponse : true } ) ;
12262
123- const server = await result . update ( {
124- content : "What server do you want the command to run on?" ,
125- components : [
126- {
127- type : Discord . ComponentType . ActionRow ,
128- components : [
129- {
130- type : Discord . ComponentType . StringSelect ,
131- custom_id : "gserv_server" ,
132- placeholder : "Choose a server." ,
133- min_values : 1 ,
134- max_values : servers . filter ( s => ! ! s . ssh ) . length ,
135- options : servers
136- . filter ( s => ! ! s . ssh )
137- . map (
138- server =>
139- < Discord . APISelectMenuOption > {
140- label : server . ssh ?. host . slice ( 0 , 2 ) , // [g1].metastruct.net
141- value : server . id . toString ( ) , // g[1].metastruct.net
142- description : server . name , // g1.metastruct.net
143- }
144- ) ,
145- } ,
146- ] ,
147- } ,
148- ] ,
149- } ) ;
63+ await Promise . all (
64+ servers . map ( async gameServer => {
65+ const gSDiscord = gameServer . discord ;
66+ try {
67+ let buffer = "" ;
15068
151- try {
152- const result = await server . awaitMessageComponent ( {
153- componentType : Discord . ComponentType . StringSelect ,
154- filter : filter ,
155- time : 60000 ,
156- } ) ;
157- const selectedServers = result . values ;
69+ await gameServer . sshExec ( "gserv" , [ command ] , {
70+ stream : "stderr" ,
71+ onStdout : buff => ( buffer += buff ) ,
72+ onStderr : buff => ( buffer += buff ) ,
73+ } ) ;
15874
159- const output = await result . update ( {
160- content : "Display output?" ,
161- components : [
162- {
163- type : Discord . ComponentType . ActionRow ,
164- components : [
165- {
166- type : Discord . ComponentType . Button ,
167- custom_id : "gserv_output_n" ,
168- label : "No" ,
169- style : Discord . ButtonStyle . Danger ,
170- } ,
171- {
172- type : Discord . ComponentType . Button ,
173- custom_id : "gserv_output_y" ,
174- label : "Yes" ,
175- style : Discord . ButtonStyle . Success ,
176- } ,
177- ] ,
178- } ,
179- ] ,
180- } ) ;
75+ const success = ! buffer . includes ( "GSERV FAILED" ) ;
18176
182- try {
183- const result = await output . awaitMessageComponent ( {
184- componentType : Discord . ComponentType . Button ,
185- filter : filter ,
186- time : 60000 ,
187- } ) ;
188- await result . update ( {
189- content : `Running ${ commands . join ( " and " ) } on ${ selectedServers
190- . slice ( )
191- . sort ( )
192- . join ( ", " ) } please wait...`,
193- components : [ ] ,
194- } ) ;
77+ const fileName = `${ command } _${ gameServer . config . id } _${ Date . now ( ) } .ansi` ;
19578
196- await Promise . all (
197- bridge . servers
198- . filter ( s => selectedServers . includes ( s . config . id . toString ( ) ) )
199- . map ( gameServer =>
200- gserv (
201- result ,
202- gameServer ,
203- commands ,
204- selectedServers . length === 1 ,
205- result . customId === "gserv_output_y"
79+ if ( showOutput || success === false ) {
80+ gSDiscord . rest . post ( Discord . Routes . channelMessages ( ctx . channelId ) , {
81+ body : {
82+ content : ! success
83+ ? "<a:ALERTA:843518761160015933> FAILED <a:ALERTA:843518761160015933> "
84+ : undefined ,
85+ message_reference : reply . interaction . responseMessageId
86+ ? {
87+ type : 0 ,
88+ message_id : reply . interaction . responseMessageId ,
89+ }
90+ : undefined ,
91+ } ,
92+ files : [ { data : Buffer . from ( buffer ) , name : fileName } ] ,
93+ } ) ;
94+ } else {
95+ if ( reply . interaction . responseMessageId ) {
96+ gSDiscord . rest . put (
97+ Discord . Routes . channelMessageReaction (
98+ ctx . channelId ,
99+ reply . interaction . responseMessageId ,
100+ "👍"
206101 )
207- )
208- )
209- . then ( ( ) => {
210- if ( selectedServers . length === 1 ) return ;
211- result . editReply ( `sent ${ commands . join ( " and " ) } successfully!` ) ;
212- } )
213- . catch ( err =>
214- result . editReply ( `something went wrong!\`\`\`\n${ err } \`\`\`` )
215- ) ;
102+ ) ;
103+ }
104+ }
105+ return success ;
216106 } catch ( err ) {
217- console . error ( err ) ;
218- await ctx . editReply ( JSON . stringify ( err ) ) ;
107+ const msg = gameServer . config . name + `\ngserv failed!\`\`\`\n${ err } \`\`\`` ;
108+ gSDiscord . rest . post ( Discord . Routes . channelMessages ( ctx . channelId ) , {
109+ body : {
110+ content : `<a:ALERTA:843518761160015933> failed to run gerv <a:ALERTA:843518761160015933>\n\`\`\`${ err } \`\`\`` ,
111+
112+ message_reference : {
113+ type : 0 ,
114+ message_id : reply . interaction . responseMessageId ,
115+ } ,
116+ } ,
117+ } ) ;
118+ return false ;
219119 }
220- } catch ( err ) {
221- console . error ( err ) ;
222- await ctx . deleteReply ( ) ;
223- }
224- } catch ( err ) {
225- console . error ( err ) ;
226- await ctx . deleteReply ( ) ;
227- }
120+ } )
121+ )
122+ . then ( ( ) => {
123+ ctx . editReply ( `sent \`${ command } \` successfully!` ) ;
124+ } )
125+ . catch ( err => ctx . editReply ( `something went wrong!\`\`\`\n${ err } \`\`\`` ) ) ;
228126 } ,
229127} ;
0 commit comments