mirror of
https://github.com/Lomanic/presence-button-web
synced 2024-12-22 19:46:59 +00:00
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:
parent
b5fa938a85
commit
11e9007d86
150
main.go
150
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: `<?xml version="1.0" ?><svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#f8991d;}.cls-2{fill:#fd0;}.cls-3{fill:#314967;}</style></defs><title/><g data-name="18 Closed Sign" id="_18_Closed_Sign"><rect class="cls-1" height="36" rx="6" ry="6" width="96" x="16" y="64"/><circle class="cls-2" cx="64" cy="28" r="6"/><path class="cls-3" d="M71.85,77.83a4.37,4.37,0,0,1,2.34,1,1.93,1.93,0,1,0,2.08-3.25A7.94,7.94,0,0,0,71.85,74c-3.13,0-5.55,2.06-5.55,4.58,0,2.74,2.64,4.21,5.35,4.58.55.11,2,.45,2.26,1,0,.57-1.38,1-2,1a5.25,5.25,0,0,1-2.79-1.16,1.93,1.93,0,1,0-2.42,3,8.73,8.73,0,0,0,5.23,2c3.12,0,5.88-2,5.88-4.82s-2.88-4.4-5.66-4.78c-1.6-.31-2-.77-2-.77C70.15,78.39,70.8,77.83,71.85,77.83Z"/><path class="cls-3" d="M40,76.34V87a2,2,0,0,0,2,2h5.83a2,2,0,0,0,0-4H44V76.34A2,2,0,0,0,40,76.34Z"/><path class="cls-3" d="M50,81.48A7.16,7.16,0,1,0,57.23,74,7.32,7.32,0,0,0,50,81.48Zm10.26,0c0,2.89-3.21,4.64-5.27,2.43-1.94-2-.71-5.86,2.2-5.86A3.29,3.29,0,0,1,60.3,81.48Z"/><path class="cls-3" d="M34.44,78.82a2,2,0,0,0,2.49-3.21A7.7,7.7,0,0,0,32.15,74a7.6,7.6,0,0,0-7.6,7.48h0c0,6.3,7.44,9.7,12.39,5.86a2,2,0,0,0-2.51-3.2,3.52,3.52,0,1,1,0-5.31Z"/><path class="cls-3" d="M87.69,78.35a2,2,0,0,0,0-4H81.8a2,2,0,0,0-2,2V87a2,2,0,0,0,1,1.75V89h6.83a2,2,0,0,0,0-4H83.81V83.65H87a2,2,0,0,0,0-4h-3.2V78.35Z"/><path class="cls-3" d="M103.09,81.64a7.29,7.29,0,0,0-7.28-7.28H93.69a2,2,0,0,0-2,2V87a2,2,0,0,0,2,2h2.13A7.31,7.31,0,0,0,103.09,81.64ZM95.81,85h-.12V78.35h.12C100.14,78.35,100.18,84.93,95.81,85Z"/><path class="cls-3" d="M92.73,98H22a4,4,0,0,1-4-4V70a4,4,0,0,1,4-4H42.55a2,2,0,0,0,0-4H32.83L59.93,34.89a8,8,0,0,0,8.13,0L81.71,48.54a2,2,0,0,0,2.83-2.83L70.88,32.06A8,8,0,0,0,64,20a2,2,0,0,0,0,4,4,4,0,0,1,2.79,6.86C63.58,34,58.24,30.08,60.51,26a2,2,0,0,0-3.49-2,8,8,0,0,0,.09,8L27.17,62H22a8,8,0,0,0-8,8V94a8,8,0,0,0,8,8H92.73A2,2,0,0,0,92.73,98Z"/><path class="cls-3" d="M106,62h-5.17L88.09,49.26a2,2,0,0,0-2.83,2.83L95.17,62H76a2,2,0,0,0,0,4h30a4,4,0,0,1,4,4V94a4,4,0,0,1-4,4H98.51a2,2,0,0,0,0,4H106a8,8,0,0,0,8-8V70A8,8,0,0,0,106,62Z"/><path class="cls-3" d="M70,62H49.22a2,2,0,0,0,0,4H70A2,2,0,0,0,70,62Z"/></g></svg>`,
|
||||
}
|
||||
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 <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)
|
||||
|
Loading…
Reference in New Issue
Block a user