11package usercommands
22
33import (
4+ "errors"
45 "fmt"
56 "slices"
67 "sort"
@@ -18,6 +19,7 @@ import (
1819
1920var (
2021 memoryReportCache = map [string ]util.MemoryResult {}
22+ errValueLocked = errors .New ("This config value is locked. You must edit the config file directly." )
2123)
2224
2325/*
@@ -33,6 +35,10 @@ func Server(rest string, user *users.UserRecord, room *rooms.Room, flags events.
3335 }
3436
3537 args := util .SplitButRespectQuotes (rest )
38+ if args [0 ] == "config" {
39+ return server_Config (strings .TrimSpace (rest [1 :]), user , room , flags )
40+ }
41+
3642 if args [0 ] == "set" {
3743
3844 args = args [1 :]
@@ -287,3 +293,218 @@ func Server(rest string, user *users.UserRecord, room *rooms.Room, flags events.
287293
288294 return true , nil
289295}
296+
297+ func server_Config (_ string , user * users.UserRecord , room * rooms.Room , flags events.EventFlag ) (bool , error ) {
298+
299+ // Get if already exists, otherwise create new
300+ cmdPrompt , isNew := user .StartPrompt (`server config` , "" )
301+
302+ if isNew {
303+ user .SendText (`` )
304+ menuOptions , _ := getConfigOptions ("" )
305+ tplTxt , _ := templates .Process ("tables/numbered-list" , menuOptions , user .UserId )
306+ user .SendText (tplTxt )
307+ }
308+
309+ configPrefix := ""
310+ if selection , ok := cmdPrompt .Recall ("config-selected" ); ok {
311+ configPrefix = selection .(string )
312+ }
313+
314+ if configPrefix != "" {
315+ allConfigData := configs .GetConfig ().AllConfigData ()
316+ if configVal , ok := allConfigData [configPrefix ]; ok {
317+
318+ if ! isEditAllowed (configPrefix ) {
319+ user .SendText (errValueLocked .Error ())
320+ user .ClearPrompt ()
321+ return true , nil
322+ }
323+
324+ question := cmdPrompt .Ask (`New Value for ` + configPrefix , []string {fmt .Sprintf ("%v" , configVal )}, fmt .Sprintf ("%v" , configVal ))
325+ if ! question .Done {
326+ return true , nil
327+ }
328+
329+ user .ClearPrompt ()
330+
331+ err := configs .SetVal (configPrefix , question .Response )
332+ if err == nil {
333+ user .SendText (configPrefix + " has been set to: " + question .Response )
334+ return true , nil
335+ }
336+ user .SendText (err .Error ())
337+ return true , nil
338+ }
339+ }
340+
341+ question := cmdPrompt .Ask (`Choose a config option, or "quit"` , []string {`` }, `` )
342+ if ! question .Done {
343+ return true , nil
344+ }
345+
346+ if question .Response == "quit" {
347+ user .SendText ("Quitting..." )
348+ user .ClearPrompt ()
349+ return true , nil
350+ }
351+
352+ fullPath := strings .ToLower (configPrefix )
353+ if fullPath != `` {
354+ fullPath += "."
355+ }
356+ fullPath += question .Response
357+
358+ if ! isEditAllowed (fullPath ) {
359+ user .SendText (errValueLocked .Error ())
360+ question .RejectResponse ()
361+ return true , nil
362+ }
363+
364+ menuOptions , ok := getConfigOptions (fullPath )
365+ if ! ok {
366+ question .RejectResponse ()
367+ menuOptions , _ = getConfigOptions ("" )
368+ } else {
369+
370+ if len (menuOptions ) == 1 {
371+ fullPath = menuOptions [0 ].Id .(string )
372+
373+ cmdPrompt .Store ("config-selected" , fullPath )
374+
375+ if ! isEditAllowed (fullPath ) {
376+ user .SendText (errValueLocked .Error ())
377+ user .ClearPrompt ()
378+ return true , nil
379+ }
380+
381+ allConfigData := configs .GetConfig ().AllConfigData ()
382+ if configVal , ok := allConfigData [fullPath ]; ok {
383+ cmdPrompt .Ask (`New Value for ` + fullPath , []string {fmt .Sprintf ("%v" , configVal )}, fmt .Sprintf ("%v" , configVal ))
384+ return true , nil
385+ }
386+ }
387+
388+ cmdPrompt .Store ("config-selected" , fullPath )
389+ }
390+
391+ tplTxt , _ := templates .Process ("tables/numbered-list" , menuOptions , user .UserId )
392+ user .SendText (tplTxt )
393+
394+ question .RejectResponse ()
395+
396+ return true , nil
397+ }
398+
399+ func isEditAllowed (configPath string ) bool {
400+
401+ configPath = strings .ToLower (configPath )
402+
403+ if strings .HasSuffix (configPath , "locked" ) {
404+ return false
405+ }
406+
407+ sc := configs .GetServerConfig ()
408+ for _ , v := range sc .Locked {
409+ if strings .HasPrefix (configPath , strings .ToLower (v )) {
410+ return false
411+ }
412+ }
413+
414+ return true
415+ }
416+
417+ func getConfigOptions (input string ) ([]templates.NameDescription , bool ) {
418+
419+ input = strings .ToLower (input )
420+
421+ configOptions := []templates.NameDescription {}
422+
423+ allConfigData := configs .GetConfig ().AllConfigData ()
424+ pathLookup := map [string ]string {}
425+ for name , _ := range allConfigData {
426+
427+ lowerName := strings .ToLower (name )
428+ pathLookup [lowerName ] = name
429+
430+ builtPath := ""
431+ for _ , namePart := range strings .Split (name , "." ) {
432+ builtPath += namePart
433+ if _ , ok := pathLookup [builtPath ]; ! ok {
434+ pathLookup [strings .ToLower (builtPath )] = builtPath
435+ }
436+ builtPath += "."
437+ }
438+ }
439+
440+ inputProperCase := input
441+ if caseCheck , ok := pathLookup [input ]; ok {
442+
443+ inputProperCase = caseCheck
444+
445+ // Is this a full config path?
446+ if configVal , ok := allConfigData [inputProperCase ]; ok {
447+
448+ configOptions = append (configOptions , templates.NameDescription {
449+ Id : inputProperCase ,
450+ Name : inputProperCase ,
451+ Description : fmt .Sprintf ("%v" , configVal ),
452+ })
453+
454+ return configOptions , true
455+
456+ }
457+
458+ } else if input != "" {
459+ return configOptions , false
460+ }
461+
462+ // Find which partial path we are on and populate options
463+ usedNames := map [string ]struct {}{}
464+ for fullConfigPath , configVal := range allConfigData {
465+
466+ if input != "" {
467+ if len (fullConfigPath ) <= len (input ) || fullConfigPath [0 :len (inputProperCase )] != inputProperCase {
468+ continue
469+ }
470+ }
471+
472+ nextConfigPathSection := fullConfigPath
473+ if len (inputProperCase ) > 0 {
474+ nextConfigPathSection = nextConfigPathSection [len (inputProperCase )+ 1 :]
475+ }
476+
477+ desc := "..."
478+ if dotIdx := strings .Index (nextConfigPathSection , "." ); dotIdx != - 1 {
479+ nextConfigPathSection = nextConfigPathSection [:dotIdx ]
480+ } else {
481+ desc = fmt .Sprintf ("%v" , configVal )
482+ }
483+
484+ if _ , ok := usedNames [nextConfigPathSection ]; ok {
485+ continue
486+ }
487+
488+ usedNames [nextConfigPathSection ] = struct {}{}
489+
490+ pathWithSection := nextConfigPathSection
491+ if len (inputProperCase ) > 0 {
492+ pathWithSection = inputProperCase + "." + pathWithSection
493+ }
494+
495+ configOptions = append (configOptions , templates.NameDescription {
496+ Id : pathWithSection ,
497+ Name : nextConfigPathSection ,
498+ Description : desc ,
499+ })
500+
501+ }
502+
503+ if len (configOptions ) > 0 {
504+ sort .Slice (configOptions , func (i , j int ) bool {
505+ return configOptions [i ].Name < configOptions [j ].Name
506+ })
507+ }
508+
509+ return configOptions , true
510+ }
0 commit comments