WIP: Implement remote control of the button from the server

This will not be developed further as the server and the button are not
on the same local network anymore.
This commit is contained in:
Lomanic 2022-10-16 18:59:57 +02:00
parent b5fa938a85
commit 11e9007d86
1 changed files with 147 additions and 3 deletions

146
main.go
View File

@ -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 (
@ -55,6 +58,8 @@ var (
}
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 <b>blink</b>! (%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 <b>STOP</b>!", 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 <b>not</b> blinking", ev.Sender))
}
default:
matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: command is `blink (status|on|off)`", ev.Sender), fmt.Sprintf("%s: command is <code>blink (status|on|off)</code>", 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)