config.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package presence
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "net/url"
  7. "os"
  8. "regexp"
  9. "strings"
  10. "time"
  11. "goa.design/clue/log"
  12. "gopkg.in/yaml.v3"
  13. "douglasthrift.net/presence/wrap"
  14. )
  15. type (
  16. Config struct {
  17. Interval time.Duration `yaml:"interval"`
  18. Interfaces []string `yaml:"interfaces"`
  19. MACAddresses []string `yaml:"mac_addresses"`
  20. PingCount uint `yaml:"ping_count"`
  21. IFTTT IFTTT `yaml:"ifttt"`
  22. }
  23. IFTTT struct {
  24. BaseURL string `yaml:"base_url"`
  25. Key string `yaml:"key"`
  26. Events Events `yaml:"events"`
  27. }
  28. Events struct {
  29. Present string `yaml:"present"`
  30. Absent string `yaml:"absent"`
  31. }
  32. )
  33. const (
  34. defaultBaseURL = "https://maker.ifttt.com"
  35. defaultPresentEvent = "presence_detected"
  36. defaultAbsentEvent = "absence_detected"
  37. )
  38. var (
  39. eventName = regexp.MustCompile("^[_a-zA-Z]+$")
  40. )
  41. func ParseConfig(name string, wNet wrap.Net) (*Config, error) {
  42. return ParseConfigWithContext(context.Background(), name, wNet)
  43. }
  44. func ParseConfigWithContext(ctx context.Context, name string, wNet wrap.Net) (*Config, error) {
  45. f, err := os.Open(name)
  46. if err != nil {
  47. return nil, err
  48. }
  49. defer f.Close()
  50. d := yaml.NewDecoder(f)
  51. d.KnownFields(true)
  52. c := &Config{}
  53. err = d.Decode(c)
  54. if err != nil {
  55. return nil, err
  56. }
  57. if c.Interval < 0 {
  58. return nil, fmt.Errorf("negative interval (%v)", c.Interval)
  59. } else if c.Interval == 0 {
  60. c.Interval = 30 * time.Second
  61. }
  62. log.Print(ctx, log.KV{K: "msg", V: "interval"}, log.KV{K: "value", V: c.Interval})
  63. if len(c.Interfaces) == 0 {
  64. ifs, err := wNet.Interfaces()
  65. if err != nil {
  66. return nil, err
  67. }
  68. c.Interfaces = make([]string, 0, len(ifs))
  69. for _, i := range ifs {
  70. c.Interfaces = append(c.Interfaces, i.Name)
  71. }
  72. } else {
  73. for _, i := range c.Interfaces {
  74. _, err = wNet.InterfaceByName(i)
  75. if err != nil {
  76. return nil, fmt.Errorf("interface %v: %w", i, err)
  77. }
  78. }
  79. }
  80. log.Print(ctx, log.KV{K: "msg", V: "interfaces"}, log.KV{K: "value", V: c.Interfaces})
  81. if len(c.MACAddresses) == 0 {
  82. return nil, fmt.Errorf("no MAC addresses")
  83. }
  84. as := make(map[string]bool, len(c.MACAddresses))
  85. for i, a := range c.MACAddresses {
  86. hw, err := net.ParseMAC(a)
  87. if err != nil {
  88. return nil, err
  89. }
  90. a = hw.String()
  91. if as[a] {
  92. return nil, fmt.Errorf("duplicate MAC address (%v)", a)
  93. }
  94. as[a] = true
  95. c.MACAddresses[i] = a
  96. }
  97. log.Print(ctx, log.KV{K: "msg", V: "MAC addresses"}, log.KV{K: "value", V: c.MACAddresses})
  98. if c.PingCount == 0 {
  99. c.PingCount = 1
  100. }
  101. log.Print(ctx, log.KV{K: "msg", V: "ping count"}, log.KV{K: "value", V: c.PingCount})
  102. if c.IFTTT.BaseURL == "" {
  103. c.IFTTT.BaseURL = defaultBaseURL
  104. } else if _, err := url.Parse(c.IFTTT.BaseURL); err != nil {
  105. return nil, fmt.Errorf("IFTTT base URL: %w", err)
  106. }
  107. log.Print(ctx, log.KV{K: "msg", V: "IFTTT base URL"}, log.KV{K: "value", V: c.IFTTT.BaseURL})
  108. if c.IFTTT.Key == "" {
  109. return nil, fmt.Errorf("no IFTTT key")
  110. }
  111. log.Print(ctx, log.KV{K: "msg", V: "IFTTT key"}, log.KV{K: "value", V: strings.Repeat("*", len(c.IFTTT.Key))})
  112. if c.IFTTT.Events.Present == "" {
  113. c.IFTTT.Events.Present = defaultPresentEvent
  114. } else if !eventName.MatchString(c.IFTTT.Events.Present) {
  115. return nil, fmt.Errorf("invalid IFTTT present event name: %#v", c.IFTTT.Events.Present)
  116. }
  117. log.Print(ctx, log.KV{K: "msg", V: "IFTTT present event"}, log.KV{K: "value", V: c.IFTTT.Events.Present})
  118. if c.IFTTT.Events.Absent == "" {
  119. c.IFTTT.Events.Absent = defaultAbsentEvent
  120. } else if !eventName.MatchString(c.IFTTT.Events.Absent) {
  121. return nil, fmt.Errorf("invalid IFTTT absent event name: %#v", c.IFTTT.Events.Absent)
  122. }
  123. log.Print(ctx, log.KV{K: "msg", V: "IFTTT absent event"}, log.KV{K: "value", V: c.IFTTT.Events.Absent})
  124. return c, nil
  125. }