378 lines
8.8 KiB
Go
378 lines
8.8 KiB
Go
package v01
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/rand"
|
|
"mayhem-party/src/device/input/wrap"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
func (v01 *V01) listen() {
|
|
if v01.cfg.Feedback.Addr == "" {
|
|
return
|
|
}
|
|
v01._listen()
|
|
}
|
|
|
|
func (v01 *V01) _listen() {
|
|
mutex := &sync.Mutex{}
|
|
s := &http.Server{
|
|
Addr: v01.cfg.Feedback.Addr,
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
defer func() { log.Printf("%vms | %s %s", time.Since(start).Milliseconds(), r.Method, r.URL) }()
|
|
v01.cfg.lock.Lock()
|
|
defer v01.cfg.lock.Unlock()
|
|
if r.Method == http.MethodGet {
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
} else {
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
}
|
|
v01.ServeHTTP(w, r)
|
|
v01.stashConfig() // TODO
|
|
}),
|
|
}
|
|
go func() {
|
|
<-v01.ctx.Done()
|
|
log.Println("closing v01 server")
|
|
s.Close()
|
|
}()
|
|
log.Println("starting v01 server")
|
|
if err := s.ListenAndServe(); err != nil && v01.ctx.Err() == nil {
|
|
log.Println("err with v01 server", err)
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
r = r.WithContext(v01.ctx)
|
|
v01.serveHTTP(w, r)
|
|
v01.serveGlobalQueries(r)
|
|
}
|
|
|
|
func (v01 *V01) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
|
switch strings.Split(r.URL.Path[1:], "/")[0] {
|
|
case "":
|
|
v01.getUserFeedback(w, r)
|
|
case "broadcast":
|
|
v01.servePutBroadcast(w, r)
|
|
case "config":
|
|
v01.serveConfig(w, r)
|
|
case "gm":
|
|
v01.serveGM(w, r)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) getUserFeedback(w http.ResponseWriter, r *http.Request) {
|
|
user := v01.cfg.Users[r.URL.Query().Get("user")]
|
|
|
|
msg := user.State.Message
|
|
if msg == "" {
|
|
msg = v01.cfg.Broadcast.Message
|
|
}
|
|
|
|
alias := user.State.GM.Alias
|
|
if alias == "" {
|
|
alias = user.State.GM.LastAlias
|
|
}
|
|
if alias != "" {
|
|
msg = fmt.Sprintf("%s (Your secret word is '%s'. Make **someone else** say it!)", msg, alias)
|
|
}
|
|
|
|
w.Write([]byte(msg + "\n\n"))
|
|
v01.serveGMStatus(w)
|
|
|
|
if v01.cfg.Quiet {
|
|
w.Write([]byte("\n\n"))
|
|
v01.serveGMVoteRead(w)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) servePutBroadcast(w http.ResponseWriter, r *http.Request) {
|
|
b, _ := io.ReadAll(r.Body)
|
|
v01.servePutBroadcastValue(string(b))
|
|
}
|
|
|
|
func (v01 *V01) servePutBroadcastValue(v string) {
|
|
v01.cfg.Broadcast.Message = v
|
|
}
|
|
|
|
func (v01 *V01) serveConfig(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
v01.serveGetConfig(w, r)
|
|
} else {
|
|
v01.servePatchConfig(w, r)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) serveGetConfig(w http.ResponseWriter, r *http.Request) {
|
|
b, _ := json.Marshal(v01.cfg)
|
|
w.Write(b)
|
|
}
|
|
|
|
func (v01 *V01) servePatchConfig(w http.ResponseWriter, r *http.Request) {
|
|
b, _ := io.ReadAll(r.Body)
|
|
var v []interface{}
|
|
if err := json.Unmarshal(b, &v); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
v01.cfg = v01.cfg.WithPatch(v)
|
|
if err := v01.stashConfig(); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) stashConfig() error {
|
|
if b, err := yaml.Marshal(v01.cfg); err == nil && FlagParseV01Config != "" {
|
|
if err := os.WriteFile(FlagParseV01Config, b, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v01 *V01) serveGlobalQueries(r *http.Request) {
|
|
v01.serveGlobalQuerySay(r)
|
|
v01.serveGlobalQueryRefresh(r)
|
|
}
|
|
|
|
func (v01 *V01) serveGlobalQuerySay(r *http.Request) {
|
|
text := r.URL.Query().Get("say")
|
|
if text == "" {
|
|
text = r.Header.Get("say")
|
|
}
|
|
if text == "" {
|
|
return
|
|
}
|
|
go v01.tts(text)
|
|
}
|
|
|
|
func (v01 *V01) serveGlobalQueryRefresh(r *http.Request) {
|
|
if _, ok := r.URL.Query()["refresh"]; !ok {
|
|
return
|
|
}
|
|
select {
|
|
case wrap.ChSigUsr1 <- syscall.SIGUSR1:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) serveGM(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/gm/rpc/status":
|
|
v01.serveGMStatus(w)
|
|
case "/gm/rpc/broadcastSomeoneSaidAlias":
|
|
v01.serveGMSomeoneSaidAlias(w, r)
|
|
case "/gm/rpc/fillNonPlayerAliases":
|
|
v01.serveGMFillNonPlayerAliases(w, r)
|
|
case "/gm/rpc/vote":
|
|
v01.serveGMVote(w, r)
|
|
case "/gm/rpc/elect":
|
|
v01.serveGMElect(w, r)
|
|
case "/gm/rpc/shuffle":
|
|
v01.serveGMShuffle(r)
|
|
case "/gm/rpc/swap":
|
|
if errCode, err := v01.serveGMSwap(r.URL.Query().Get("a"), r.URL.Query().Get("b")); err != nil {
|
|
http.Error(w, err.Error(), errCode)
|
|
return
|
|
}
|
|
default:
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) serveGMStatus(w io.Writer) {
|
|
users := map[string]struct {
|
|
Lag time.Duration `yaml:"lag,omitempty"`
|
|
Player int `yaml:"player,omitempty"`
|
|
IdleFor time.Duration `yaml:"idle_for,omitempty"`
|
|
}{}
|
|
for k, v := range v01.cfg.Users {
|
|
v2 := users[k]
|
|
v2.Lag = time.Duration(v.Meta.LastLag) * time.Millisecond
|
|
v2.Player = v.State.Player
|
|
if v.Meta.LastTSMS > 0 {
|
|
v2.IdleFor = time.Since(time.Unix(0, v.Meta.LastTSMS*int64(time.Millisecond)))
|
|
}
|
|
users[k] = v2
|
|
}
|
|
yaml.NewEncoder(w).Encode(map[string]interface{}{
|
|
"Players": len(v01.cfg.Players),
|
|
"Users": users,
|
|
})
|
|
}
|
|
|
|
func (v01 *V01) serveGMSomeoneSaidAlias(w http.ResponseWriter, r *http.Request) {
|
|
v01.cfg.Quiet = true
|
|
for k, v := range v01.cfg.Users {
|
|
v.State.GM.LastAlias = v.State.GM.Alias
|
|
v.State.GM.Alias = ""
|
|
v01.cfg.Users[k] = v
|
|
}
|
|
v01.servePutBroadcastValue(fmt.Sprintf("<<SOMEONE SAID %q>>", strings.ToUpper(r.URL.Query().Get("message"))))
|
|
}
|
|
|
|
func (v01 *V01) serveGMFillNonPlayerAliases(w http.ResponseWriter, r *http.Request) {
|
|
b, _ := io.ReadAll(r.Body)
|
|
var pool []string
|
|
yaml.Unmarshal(b, &pool)
|
|
n := 0
|
|
for _, v := range v01.cfg.Users {
|
|
if v.State.Player == 0 {
|
|
n += 1
|
|
}
|
|
}
|
|
if n < 1 {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
if len(pool) < n {
|
|
http.Error(w, fmt.Sprintf("request body must contain a list of %v options", n), http.StatusBadRequest)
|
|
return
|
|
}
|
|
for i := 0; i < 100; i++ {
|
|
a, b := rand.Int()%len(pool), rand.Int()%len(pool)
|
|
pool[a], pool[b] = pool[b], pool[a]
|
|
}
|
|
i := 0
|
|
for k, v := range v01.cfg.Users {
|
|
if v.State.Player == 0 {
|
|
v.State.GM.Alias = pool[i]
|
|
v01.cfg.Users[k] = v
|
|
i += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) serveGMElect(w http.ResponseWriter, r *http.Request) {
|
|
alias := r.URL.Query().Get("alias")
|
|
aliasWinner := ""
|
|
votes := map[string]int{}
|
|
for k, v := range v01.cfg.Users {
|
|
votes[v.State.GM.Vote] = votes[v.State.GM.Vote] + 1
|
|
if v.State.GM.LastAlias == alias {
|
|
aliasWinner = k
|
|
}
|
|
}
|
|
if aliasWinner == "" {
|
|
http.Error(w, "who is "+alias+"?", http.StatusBadRequest)
|
|
return
|
|
}
|
|
threshold := 0.1 + float64(len(votes))/2.0
|
|
winner := ""
|
|
for k, v := range votes {
|
|
if float64(v) > threshold {
|
|
winner = k
|
|
}
|
|
}
|
|
if winner == "" {
|
|
v01.serveGMShuffle(r)
|
|
} else if _, err := v01.serveGMSwap(winner, aliasWinner); err != nil {
|
|
v01.serveGMShuffle(r)
|
|
}
|
|
for k, v := range v01.cfg.Users {
|
|
v.State.GM.Vote = ""
|
|
v01.cfg.Users[k] = v
|
|
}
|
|
yaml.NewEncoder(w).Encode(votes)
|
|
}
|
|
|
|
func (v01 *V01) serveGMVote(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Query().Get("payload") {
|
|
case "":
|
|
v01.serveGMVoteRead(w)
|
|
default:
|
|
v01.serveGMVoteWrite(w, r)
|
|
}
|
|
}
|
|
|
|
func (v01 *V01) serveGMVoteRead(w io.Writer) {
|
|
counts := map[string]string{}
|
|
for k, v := range v01.cfg.Users {
|
|
if v.State.GM.Vote != "" {
|
|
counts[k] = "voted"
|
|
} else {
|
|
counts[k] = "voting"
|
|
}
|
|
}
|
|
yaml.NewEncoder(w).Encode(counts)
|
|
}
|
|
|
|
func (v01 *V01) serveGMVoteWrite(w http.ResponseWriter, r *http.Request) {
|
|
voter := r.URL.Query().Get("user")
|
|
candidate := r.URL.Query().Get("payload")
|
|
v, ok := v01.cfg.Users[voter]
|
|
if _, ok2 := v01.cfg.Users[candidate]; !ok || !ok2 {
|
|
http.Error(w, "bad voter/candidate", http.StatusBadRequest)
|
|
return
|
|
}
|
|
v.State.GM.Vote = candidate
|
|
v01.cfg.Users[voter] = v
|
|
}
|
|
|
|
func (v01 *V01) serveGMShuffle(r *http.Request) {
|
|
poolSize := len(v01.cfg.Users)
|
|
if altSize := len(v01.cfg.Players); altSize > poolSize {
|
|
poolSize = altSize
|
|
}
|
|
pool := make([]int, poolSize)
|
|
if poolSize > 0 {
|
|
for i := range v01.cfg.Players {
|
|
pool[i] = i + 1
|
|
}
|
|
for i := 0; i < 30; i++ {
|
|
l := rand.Int() % poolSize
|
|
r := rand.Int() % poolSize
|
|
pool[l], pool[r] = pool[r], pool[l]
|
|
}
|
|
}
|
|
i := 0
|
|
msg := []string{}
|
|
for k, v := range v01.cfg.Users {
|
|
v.State.Player = pool[i]
|
|
v01.cfg.Users[k] = v
|
|
if pool[i] > 0 {
|
|
msg = append(msg, fmt.Sprintf("%s is now player %v", k, v.State.Player))
|
|
}
|
|
i += 1
|
|
}
|
|
v01.servePutBroadcastValue(strings.Join(msg, ", "))
|
|
v01.cfg.Quiet = false
|
|
}
|
|
|
|
func (v01 *V01) serveGMSwap(userA, userB string) (int, error) {
|
|
if userA == userB {
|
|
return http.StatusConflict, errors.New("/spiderman-pointing")
|
|
}
|
|
_, okA := v01.cfg.Users[userA]
|
|
_, okB := v01.cfg.Users[userB]
|
|
if !okA || !okB {
|
|
return http.StatusBadRequest, errors.New("who dat?")
|
|
}
|
|
a := v01.cfg.Users[userA]
|
|
b := v01.cfg.Users[userB]
|
|
a.State.Player, b.State.Player = b.State.Player, a.State.Player
|
|
v01.cfg.Users[userA] = a
|
|
v01.cfg.Users[userB] = b
|
|
v01.cfg.Quiet = false
|
|
v01.servePutBroadcastValue(fmt.Sprintf(`%s is now player %v and %s is now player %v`, userA, a.State.Player, userB, b.State.Player))
|
|
return http.StatusOK, nil
|
|
}
|