122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/spf13/viper"
|
||
|
"gopkg.in/yaml.v2"
|
||
|
)
|
||
|
|
||
|
type webhook struct {
|
||
|
Name string
|
||
|
Path string
|
||
|
SecretKey string `yaml:"secret_key"`
|
||
|
NumSwitches int `yaml:"num_switches"`
|
||
|
MomentarySwitches []int `yaml:"momentary_switches"`
|
||
|
SwitchStates []int `json:switch_states`
|
||
|
}
|
||
|
|
||
|
func debug(msg string, args ...interface{}) {
|
||
|
if viper.GetBool("Debug") {
|
||
|
log.Printf(msg, args...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func initWebhooks(filename string) {
|
||
|
bytes, err := ioutil.ReadFile(filename)
|
||
|
if err != nil {
|
||
|
log.Panicf("Couldn't read config file: %s", err)
|
||
|
}
|
||
|
|
||
|
var hooks []webhook
|
||
|
|
||
|
err = yaml.Unmarshal(bytes, &hooks)
|
||
|
if err != nil {
|
||
|
log.Panicf("Couldn't parse webhook file: %v", err)
|
||
|
}
|
||
|
|
||
|
debug("Webhook definitions:")
|
||
|
debug("%v", hooks)
|
||
|
|
||
|
for _, v := range hooks {
|
||
|
v.SwitchStates = make([]int, v.NumSwitches)
|
||
|
http.HandleFunc("/"+v.Path, makeWebhookHandler(v))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func runServer() {
|
||
|
http.ListenAndServe(":"+viper.GetString("ListenPort"), nil)
|
||
|
}
|
||
|
|
||
|
func makeWebhookHandler(hook webhook) func(http.ResponseWriter, *http.Request) {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
// authentication check
|
||
|
keys, ok := r.URL.Query()["key"]
|
||
|
if !ok || len(keys) == 0 {
|
||
|
log.Printf("No key found for webhook: %s", hook.Name)
|
||
|
http.NotFound(w, r)
|
||
|
return
|
||
|
}
|
||
|
providedKey := keys[0]
|
||
|
|
||
|
if providedKey == "" || providedKey != hook.SecretKey {
|
||
|
log.Printf("Failed to authenticate request for webhook: %s", hook.Name)
|
||
|
debug("Got key '%s', expected key '%s'", providedKey, hook.SecretKey)
|
||
|
http.NotFound(w, r)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// now actually handle the request
|
||
|
if r.Method == "GET" {
|
||
|
webhookRead(hook, w, r)
|
||
|
|
||
|
} else if r.Method == "POST" {
|
||
|
webhookWrite(hook, w, r)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func webhookRead(hook webhook, w http.ResponseWriter, r *http.Request) {
|
||
|
err := json.NewEncoder(w).Encode(hook.SwitchStates)
|
||
|
|
||
|
if err != nil {
|
||
|
log.Printf("Failed to send response: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// momentary switches have been read, so make sure they're 0
|
||
|
// next time
|
||
|
for _, i := range hook.MomentarySwitches {
|
||
|
hook.SwitchStates[i] = 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func webhookWrite(hook webhook, w http.ResponseWriter, r *http.Request) {
|
||
|
err := json.NewDecoder(r.Body).Decode(&hook.SwitchStates)
|
||
|
if err != nil {
|
||
|
log.Printf("Failed to parse json for '%s': %v", hook.Name, err)
|
||
|
http.Error(w, "Failed to parse json", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
// read environment config
|
||
|
viper.BindEnv("Debug", "DEBUG")
|
||
|
viper.BindEnv("WebhookFile", "WEBHOOK_CONFIG_FILE")
|
||
|
viper.BindEnv("ListenPort", "PORT")
|
||
|
|
||
|
viper.SetDefault("ListenPort", "7200")
|
||
|
viper.SetDefault("WebhookFile", "./webhooks.yaml")
|
||
|
viper.SetDefault("Debug", false)
|
||
|
|
||
|
// read webhook config
|
||
|
initWebhooks(viper.GetString("WebhookFile"))
|
||
|
|
||
|
// serve app
|
||
|
runServer()
|
||
|
}
|