package main import ( "github.com/valyala/fastjson" "log" "meow.tf/streamdeck/sdk" "strconv" "sync" "time" ) const ( actionReplayToggle = "tf.meow.obsreplay.replay_toggle" actionReplaySave = "tf.meow.obsreplay.replay_save" ) var ( contextMutex sync.RWMutex clientMutex sync.RWMutex stateMutex sync.RWMutex clients = make(map[string]Client) contexts = make(map[string]string) contextActions = make(map[string]string) cachedStates = make(map[string]int) ) func replayToggle(action, context string, payload *fastjson.Value, deviceId string) { c := clientForContext(context) if c == nil { sdk.ShowAlert(context) return } if err := c.ToggleReplay(); err != nil { sdk.ShowAlert(context) return } time.AfterFunc(1*time.Second, func() { contextMutex.RLock() key, exists := contexts[context] contextMutex.RUnlock() if !exists { return } stateMutex.RLock() state, exists := cachedStates[key] stateMutex.RUnlock() if !exists { return } sdk.SetState(context, state) }) } func replaySave(action, context string, payload *fastjson.Value, deviceId string) { c := clientForContext(context) if c == nil { sdk.ShowAlert(context) return } contextMutex.RLock() key, exists := contexts[context] contextMutex.RUnlock() if exists { stateMutex.RLock() state, exists := cachedStates[key] stateMutex.RUnlock() if exists && state == 0 { sdk.ShowAlert(context) return } } if err := c.SaveReplay(); err != nil { sdk.ShowAlert(context) return } sdk.ShowOk(context) } func clientForContext(context string) Client { contextMutex.RLock() key, exists := contexts[context] contextMutex.RUnlock() if !exists { return nil } clientMutex.RLock() c, exists := clients[key] clientMutex.RUnlock() if !exists { return nil } if !c.Connected() { err := c.Connect() if err != nil { return nil } } return c } func onWillAppear(e *sdk.WillAppearEvent) { settings := e.Payload.Get("settings") if settings != nil { host := sdk.JsonStringValue(settings, "host") portStr := sdk.JsonStringValue(settings, "port") password := sdk.JsonStringValue(settings, "password") port, err := strconv.Atoi(portStr) if err != nil { port = 4444 } key := checkClient(host, port, password) if key == "" { return } contextMutex.Lock() contexts[e.Context] = key contextActions[e.Context] = e.Action contextMutex.Unlock() if e.Action == actionReplayToggle { stateMutex.RLock() if state, ok := cachedStates[key]; ok { sdk.SetState(e.Context, state) } stateMutex.RUnlock() } } } func checkClient(host string, port int, password string) string { if host == "" { host = "127.0.0.1" } if port == 0 { port = 4444 } key := host + ":" + strconv.Itoa(port) clientMutex.RLock() client, ok := clients[key] clientMutex.RUnlock() if !ok { client = NewClient(key, host, port, password) defer client.Connect() clientMutex.Lock() clients[key] = client clientMutex.Unlock() } return key } func onWillDisappear(e *sdk.WillDisappearEvent) { contextDiscounnected(e.Context) } func contextDiscounnected(context string) { contextMutex.Lock() defer contextMutex.Unlock() // replayToggleContexts key, ok := contexts[context] delete(contexts, context) delete(contextActions, context) if !ok { return } for _, k := range contexts { if k == key { return } } clientMutex.Lock() clients[key].Disconnect() delete(clients, key) clientMutex.Unlock() } func onSettingsReceived(e *sdk.ReceiveSettingsEvent) { host := sdk.JsonStringValue(e.Settings, "host") portStr := sdk.JsonStringValue(e.Settings, "port") password := sdk.JsonStringValue(e.Settings, "password") if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 4444 } key := checkClient(host, port, password) contextMutex.RLock() previousKey, existing := contexts[e.Context] contextMutex.RUnlock() if existing && previousKey != key { contextDiscounnected(e.Context) } if key == "" { return } contextMutex.Lock() contexts[e.Context] = key contextMutex.Unlock() stateMutex.RLock() if state, ok := cachedStates[key]; ok { sdk.SetState(e.Context, state) } stateMutex.RUnlock() } func main() { sdk.RegisterAction(actionReplayToggle, replayToggle) sdk.RegisterAction(actionReplaySave, replaySave) sdk.AddHandler(onWillAppear) sdk.AddHandler(onWillDisappear) sdk.AddHandler(onSettingsReceived) // Open and connect the SDK err := sdk.Open() if err != nil { log.Fatalln(err) } defer cleanupSockets() // Wait until the socket is closed, or SIGTERM/SIGINT is received sdk.Wait() } func cleanupSockets() { clientMutex.RLock() defer clientMutex.RUnlock() for _, client := range clients { client.Disconnect() } } func loopContextState(key string, state int) { stateMutex.Lock() cachedStates[key] = state stateMutex.Unlock() contextMutex.RLock() defer contextMutex.RUnlock() for ctx, ctxKey := range contexts { if ctxKey == key { action := contextActions[ctx] if action == actionReplayToggle { sdk.SetState(ctx, state) } } } }