You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
3.8 KiB

  1. package main
  2. import (
  3. "context"
  4. "git.aiterp.net/lucifer/new-server/app/client"
  5. "git.aiterp.net/lucifer/new-server/models"
  6. "gopkg.in/yaml.v2"
  7. "log"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "unicode"
  12. )
  13. func sceneCmd(
  14. ctx context.Context,
  15. c client.Client,
  16. ) {
  17. cmd := parseCommand(os.Args[2:])
  18. switch cmd.Name {
  19. case "list":
  20. {
  21. scenes, err := c.GetScenes(ctx)
  22. if err != nil {
  23. log.Fatalln("Failed to load scenes:", err)
  24. }
  25. WriteSceneTable(os.Stdout, scenes)
  26. }
  27. case "create", "update":
  28. {
  29. fileName := cmd.Params.Get(0).String()
  30. if fileName == nil {
  31. log.Fatalln("Missing filename")
  32. }
  33. file, err := os.Open(*fileName)
  34. if err != nil {
  35. log.Fatalln("Failed to open file:", err)
  36. }
  37. defer file.Close()
  38. yamlData := make(map[string]interface{})
  39. err = yaml.NewDecoder(file).Decode(yamlData)
  40. if err != nil {
  41. log.Fatalln("Failed to decode file:", err)
  42. return
  43. }
  44. yamlData = camelCasify(yamlData)
  45. name, nameOk := yamlData["name"]
  46. if !nameOk {
  47. log.Fatalln("Missing name in yaml data.")
  48. }
  49. var scene models.Scene
  50. if cmd.Name == "create" {
  51. err := c.Fetch(ctx, "POST", "/api/scenes", &scene, yamlData)
  52. if err != nil {
  53. log.Fatalln("Failed to create scene:", err)
  54. return
  55. }
  56. } else {
  57. scenes, err := c.GetScenes(ctx)
  58. if err != nil {
  59. log.Fatalln("Failed to fetch existing scenes:", err)
  60. return
  61. }
  62. id := -1
  63. for _, scene := range scenes {
  64. if scene.Name == name {
  65. id = scene.ID
  66. break
  67. }
  68. }
  69. if id == -1 {
  70. log.Fatalln("Could not find scene with name", name)
  71. return
  72. }
  73. err = c.Fetch(ctx, "PUT", "/api/scenes/"+strconv.Itoa(id), &scene, yamlData)
  74. if err != nil {
  75. log.Fatalln("Failed to update scene:", err)
  76. return
  77. }
  78. }
  79. }
  80. case "push", "assign":
  81. {
  82. fetch := cmd.Params.Get(0).String()
  83. id := cmd.Params.Get(1).Int()
  84. name := cmd.Params.Get(1).String()
  85. if fetch == nil || (id == nil && name == nil) {
  86. log.Println("Usage: lucy scene assign <fetch> <id|name> <group=S> <duration=I>")
  87. }
  88. assignment := models.DeviceSceneAssignment{
  89. Group: cmd.Params.Get("group").StringOr(*fetch),
  90. DurationMS: int64(cmd.Params.Get("duration").IntOr(0)),
  91. }
  92. if id != nil {
  93. assignment.SceneID = *id
  94. } else {
  95. assignment.SceneName = *name
  96. }
  97. devices, err := c.AssignDevice(ctx, *fetch, cmd.Name == "push", assignment)
  98. if err != nil {
  99. log.Println("Could not assign devices:", err)
  100. return
  101. }
  102. WriteDeviceInfoTable(os.Stdout, devices)
  103. }
  104. case "clear":
  105. fetch := cmd.Params.Get(0).String()
  106. if fetch == nil {
  107. log.Println("Usage: lucy scene clear <fetch>")
  108. }
  109. devices, err := c.ClearDevice(ctx, *fetch)
  110. if err != nil {
  111. log.Println("Could not clear devices:", err)
  112. return
  113. }
  114. WriteDeviceInfoTable(os.Stdout, devices)
  115. }
  116. }
  117. func camelCasify(m map[string]interface{}) map[string]interface{} {
  118. m2 := make(map[string]interface{}, len(m))
  119. for key, value := range m {
  120. b := strings.Builder{}
  121. snake := false
  122. for _, ch := range key {
  123. if ch == '_' {
  124. snake = true
  125. } else if snake {
  126. b.WriteRune(unicode.ToUpper(ch))
  127. snake = false
  128. } else {
  129. b.WriteRune(ch)
  130. }
  131. }
  132. switch value := value.(type) {
  133. case []interface{}:
  134. valueCopy := make([]interface{}, len(value))
  135. for i, elem := range value {
  136. switch elem := elem.(type) {
  137. case map[interface{}]interface{}:
  138. m3 := make(map[string]interface{})
  139. for k, v := range elem {
  140. if kStr, ok := k.(string); ok {
  141. m3[kStr] = v
  142. }
  143. }
  144. valueCopy[i] = camelCasify(m3)
  145. case map[string]interface{}:
  146. valueCopy[i] = camelCasify(elem)
  147. default:
  148. valueCopy[i] = elem
  149. }
  150. }
  151. m2[b.String()] = valueCopy
  152. case map[string]interface{}:
  153. m2[b.String()] = camelCasify(value)
  154. default:
  155. m2[b.String()] = value
  156. }
  157. }
  158. return m2
  159. }