package app import ( "fmt" "log" "strings" "time" "vrc_osc_go/internal/osc" ) const discordDebounce = 300 * time.Millisecond func handleDiscordSend(state *discordMuteState, args []osc.Value) error { state.mu.Lock() defer state.mu.Unlock() if len(args) == 0 { return fmt.Errorf("OSC args is empty") } isPressed, err := toBool(args[0]) if err != nil { return err } now := time.Now() if !state.lastAction.IsZero() && now.Sub(state.lastAction) < discordDebounce { return nil } if isPressed && !state.buttonPressed { state.buttonPressed = true if state.muted { log.Printf("ACTION VRChat button pressed -> Discord unmute hotkey") if err := pressDiscordMuteHotkey(); err != nil { return err } state.muted = false SetDiscordMuted(state.muted) state.lastAction = now } else { log.Printf("INFO already unmuted") } return nil } if !isPressed && state.buttonPressed { state.buttonPressed = false if !state.muted { log.Printf("ACTION VRChat button released -> Discord mute hotkey") if err := pressDiscordMuteHotkey(); err != nil { return err } state.muted = true SetDiscordMuted(state.muted) state.lastAction = now } else { log.Printf("INFO already muted") } } return nil } func toBool(v osc.Value) (bool, error) { switch v.Type { case 'T': return true, nil case 'F': return false, nil case 'i': return v.Int != 0, nil case 'f': return v.Float != 0, nil case 's': switch strings.ToLower(strings.TrimSpace(v.Str)) { case "true", "1", "on", "yes": return true, nil case "false", "0", "off", "no": return false, nil default: return false, fmt.Errorf("unsupported OSC value: %q", v.Str) } case 0: return false, fmt.Errorf("unsupported OSC value") default: return false, fmt.Errorf("unsupported OSC type: %q", v.Type) } }