diff --git a/main.go b/main.go
index 40365d1..bfc06d1 100644
--- a/main.go
+++ b/main.go
@@ -4,9 +4,11 @@ import (
"encoding/json"
"fmt"
"log"
+ "net"
"net/http"
"os"
"path/filepath"
+ "regexp"
"strconv"
"strings"
"time"
@@ -36,7 +38,8 @@ type Config struct {
const (
dbPath = "./.data/data.json"
- defaultClosingTimeout = 5 * time.Minute
+ defaultClosingTimeout = 1 * time.Minute
+ espMaxBlinkDuration = 5 * time.Minute
)
var (
@@ -53,8 +56,10 @@ var (
// formerly https://www.flaticon.com/free-icon/closed_1234190, maybe try https://flaticons.net/customize.php?dir=Miscellaneous&icon=Closed.png without attribution
false: ``,
}
- db *os.File
- matrix *gomatrix.Client
+ db *os.File
+ matrix *gomatrix.Client
+ espIP string
+ espBlinkDeadline = time.Now().Add(-espMaxBlinkDuration)
)
func init() {
@@ -208,6 +213,21 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Authentication required", 401)
return
}
+
+ // handle being behind proxy https://stackoverflow.com/a/33301173
+ espIP, _, _ = net.SplitHostPort(r.RemoteAddr)
+ for _, ifaceIP := range ifacesIPs() {
+ if espIP == ifaceIP {
+ for _, header := range []string{"X-Real-Ip", "X-Forwarded-For"} {
+ if r.Header.Get(header) != "" {
+ espIP = strings.Split(r.Header.Get(header), ", ")[0]
+ }
+ }
+ }
+ }
+ fmt.Printf("espIP %s ", espIP)
+
+
q := r.URL.Query()
fuzIsOpen := q.Get("fuzisopen") == "1"
fmt.Printf("button pushed: %v\n", fuzIsOpen)
@@ -239,11 +259,62 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
}
}
+// https://github.com/qbit/mcchunkie/blob/53eb25ae3394f326e24782f273584e3e6a4c0e8c/plugins/plugins.go#L52-L66
+// NameRE matches the "friendly" name. This is typically used in tab
+// completion.
+var nameRE = regexp.MustCompile(`@(.+):.+$`)
+
+// ToMe returns true of the message pertains to the bot
+func toMe(user, message string) bool {
+ u := nameRE.ReplaceAllString(user, "$1")
+ return strings.Contains(message, u)
+}
+
+// RemoveName removes the friendly name from a given message
+func removeName(user, message string) string {
+ n := nameRE.ReplaceAllString(user, "$1")
+ return strings.ReplaceAll(message, n+": ", "")
+}
+
func syncMatrix() {
syncer := matrix.Syncer.(*gomatrix.DefaultSyncer)
syncer.OnEventType("m.room.message", func(ev *gomatrix.Event) {
if ev.Sender != matrix.UserID {
matrix.MarkRead(ev.RoomID, ev.ID)
+ if body, ok := ev.Body(); ok && toMe(config.MATRIXUSERNAME, body) {
+ cmd := strings.Fields(removeName(config.MATRIXUSERNAME, body))
+ if len(cmd) >= 2 {
+ switch cmd[0] {
+ case "blink":
+ switch cmd[1] {
+ case "on":
+ if status.FuzIsOpen {
+ matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: let's make it **blink**! (%s max)", ev.Sender, espMaxBlinkDuration), fmt.Sprintf("%s: let's make it blink! (%s max)", ev.Sender, espMaxBlinkDuration))
+ espBlinkDeadline = time.Now().Add(espMaxBlinkDuration)
+ } else {
+ matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: can't do that, space is closed", ev.Sender))
+ }
+ case "off":
+ if status.FuzIsOpen {
+ matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: OK it's time to **STOP**!", ev.Sender), fmt.Sprintf("%s: OK it's time to STOP!", ev.Sender))
+ espBlinkDeadline = time.Now()
+ } else {
+ matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: can't do that, space is closed", ev.Sender))
+ }
+ case "status":
+ if time.Now().Before(espBlinkDeadline) {
+ matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: yes it's still blinking!", ev.Sender))
+ } else {
+ matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: it's **not** blinking", ev.Sender), fmt.Sprintf("%s: it's not blinking", ev.Sender))
+ }
+ default:
+ matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: command is `blink (status|on|off)`", ev.Sender), fmt.Sprintf("%s: command is blink (status|on|off)
", ev.Sender))
+ }
+ return
+ }
+ }
+ matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: huh?", ev.Sender))
+ }
}
})
@@ -263,10 +334,83 @@ func syncMatrix() {
}
}
+func espBlinkLoop() {
+ client := &http.Client{}
+ for {
+ time.Sleep(time.Second)
+ if espIP == "" {
+ continue
+ }
+ if time.Now().Before(espBlinkDeadline) {
+ // http.get request to espIP to have it light up
+ req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/admin?enablerotatinglight", espIP), nil)
+ if err != nil {
+ fmt.Println("error creating request in espBlinkLoop:", err)
+ continue
+ }
+ req.SetBasicAuth(config.ESPUSERNAME, config.ESPPASSWORD)
+ fmt.Print("admin?enablerotatinglight ")
+ fmt.Println(client.Do(req))
+ time.Sleep(7 * time.Second)
+
+ // http.get request to espIP to shut down light
+ req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/admin?disablerotatinglight", espIP), nil)
+ if err != nil {
+ fmt.Println("error creating request in espBlinkLoop:", err)
+ continue
+ }
+ req.SetBasicAuth(config.ESPUSERNAME, config.ESPPASSWORD)
+ fmt.Print("admin?disablerotatinglight ")
+ fmt.Println(client.Do(req))
+ time.Sleep(3 * time.Second)
+ }
+ }
+}
+
+func ifacesIPs() []string { // from https://stackoverflow.com/a/23558495 linked playground
+ var ips []string
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return ips
+ }
+ for _, iface := range ifaces {
+ if iface.Flags&net.FlagUp == 0 {
+ continue // interface down
+ }
+ /*if iface.Flags&net.FlagLoopback != 0 {
+ continue // loopback interface
+ }*/
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return ips
+ }
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ }
+ if ip == nil || ip.IsLoopback() {
+ continue
+ }
+ ip = ip.To4()
+ if ip == nil {
+ continue // not an ipv4 address
+ }
+ ips = append(ips, ip.String())
+ }
+ }
+ return ips
+}
+
func main() {
+ fmt.Println(ifacesIPs())
go updateUptime()
go checkClosure()
go syncMatrix()
+ go espBlinkLoop()
http.HandleFunc("/", rootHandler)
http.HandleFunc("/api", apiHandler)
http.HandleFunc("/img", imgHandler)