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