package processor import ( "bytes" "context" "encoding/hex" "errors" "fmt" "strconv" "text/template" "github.com/google/jsonschema-go/jsonschema" "github.com/jwetzell/osc-go" "github.com/jwetzell/showbridge-go/config" "github.com/jwetzell/showbridge-go/internal/common" ) func init() { RegisterProcessor(ProcessorRegistration{ Type: "osc.message.create", Title: "Create OSC Message", ParamsSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "address": { Title: "Address", Description: "OSC address for the message", Type: "string", }, "args": { Title: "Arguments", Description: "arguments for the OSC message", Type: "array", Items: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "value": { Title: "Value", Description: "Value of the argument", Type: "string", }, "type": { Title: "Type", Description: "OSC type of the argument", Type: "string", }, }, Required: []string{"value", "type"}, AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, }, }, }, Required: []string{"address"}, AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, }, New: func(processorConfig config.ProcessorConfig) (Processor, error) { params := processorConfig.Params addressString, err := params.GetString("address") if err != nil { return nil, fmt.Errorf("osc.message.create address error: %w", err) } addressTemplate, err := template.New("address").Parse(addressString) if err != nil { return nil, err } argObjects, err := params.GetObjectSlice("args") if err != nil { if errors.Is(err, config.ErrParamNotFound) { return &MessageCreate{config: processorConfig, Address: addressTemplate}, nil } else { return nil, fmt.Errorf("osc.message.create args error: %w", err) } } types := []string{} for _, argObject := range argObjects { argType, ok := argObject["type"] if !ok { return nil, errors.New("osc.message.create arg type error: not found") } argTypeStr, ok := argType.(string) if !ok { return nil, errors.New("osc.message.create arg type error: not a string") } types = append(types, argTypeStr) } argTemplates := []*template.Template{} for _, argObject := range argObjects { argValue, ok := argObject["value"] if !ok { return nil, errors.New("osc.message.create arg value error: not found") } argValueStr, ok := argValue.(string) if !ok { return nil, errors.New("osc.message.create arg value error: not a string") } argTemplate, err := template.New("arg").Parse(argValueStr) if err != nil { return nil, err } argTemplates = append(argTemplates, argTemplate) } return &MessageCreate{config: processorConfig, Address: addressTemplate, Args: argTemplates, Types: types}, nil }, }) } type MessageCreate struct { config config.ProcessorConfig Address *template.Template Args []*template.Template Types []string } func (omc *MessageCreate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) { templateData := wrappedPayload var addressBuffer bytes.Buffer err := omc.Address.Execute(&addressBuffer, templateData) if err != nil { wrappedPayload.End = true return wrappedPayload, err } addressString := addressBuffer.String() if len(addressString) == 0 { wrappedPayload.End = true return wrappedPayload, errors.New("osc.message.create address must not be empty") } if addressString[0] != '/' { wrappedPayload.End = true return wrappedPayload, errors.New("osc.message.create address must start with '/'") } payloadMessage := &osc.Message{ Address: addressString, } args := []osc.Arg{} for argIndex, argTemplate := range omc.Args { var argBuffer bytes.Buffer err := argTemplate.Execute(&argBuffer, templateData) if err != nil { wrappedPayload.End = true return wrappedPayload, err } argString := argBuffer.String() typedArg, err := argToTypedArg(argString, omc.Types[argIndex]) if err != nil { wrappedPayload.End = true return wrappedPayload, err } args = append(args, typedArg) } if len(args) > 0 { payloadMessage.Args = args } wrappedPayload.Payload = payloadMessage return wrappedPayload, nil } func (omc *MessageCreate) Id() string { return omc.config.Id } func (omc *MessageCreate) Type() string { return omc.config.Type } func argToTypedArg(rawArg string, oscType string) (osc.Arg, error) { switch oscType { case "s": return osc.Arg{ Value: rawArg, Type: "s", }, nil case "i": number, err := strconv.ParseInt(rawArg, 10, 32) if err != nil { return osc.Arg{}, err } return osc.Arg{ Value: int32(number), Type: "i", }, nil case "f": number, err := strconv.ParseFloat(rawArg, 32) if err != nil { return osc.Arg{}, err } return osc.Arg{ Value: float32(number), Type: "f", }, nil case "b": data, err := hex.DecodeString(rawArg) if err != nil { return osc.Arg{}, err } return osc.Arg{ Value: data, Type: "b", }, nil case "h": number, err := strconv.ParseInt(rawArg, 10, 64) if err != nil { return osc.Arg{}, err } return osc.Arg{ Value: int64(number), Type: "h", }, nil case "d": number, err := strconv.ParseFloat(rawArg, 64) if err != nil { return osc.Arg{}, err } return osc.Arg{ Value: float64(number), Type: "d", }, nil case "T": return osc.Arg{ Value: true, Type: "T", }, nil case "F": return osc.Arg{ Value: false, Type: "F", }, nil case "N": return osc.Arg{ Value: nil, Type: "N", }, nil default: return osc.Arg{}, fmt.Errorf("osc.message.create unhandled osc type: %s", oscType) } }