start messing with controlling response to SIP calls

This commit is contained in:
Joel Wetzell
2025-12-28 19:29:38 -06:00
parent bd2a68ff6e
commit bb33974e1c
2 changed files with 169 additions and 27 deletions

View File

@@ -3,9 +3,9 @@ package module
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"log/slog" "log/slog"
"os"
"time" "time"
"github.com/emiago/diago" "github.com/emiago/diago"
@@ -13,6 +13,7 @@ import (
"github.com/emiago/sipgo" "github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip" "github.com/emiago/sipgo/sip"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route" "github.com/jwetzell/showbridge-go/internal/route"
) )
@@ -32,6 +33,8 @@ type SIPCallMessage struct {
To string To string
} }
type sipCallContextKey string
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "sip.call.server", Type: "sip.call.server",
@@ -143,51 +146,93 @@ func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
inDialog.Trying() inDialog.Trying()
inDialog.Ringing() inDialog.Ringing()
inDialog.Answer() inDialog.Answer()
scs.router.HandleInput(scs.ctx, scs.Id(), SIPCallMessage{
dialogContext := context.WithValue(scs.ctx, sipCallContextKey("inDialog"), inDialog)
scs.router.HandleInput(dialogContext, scs.Id(), SIPCallMessage{
To: inDialog.ToUser(), To: inDialog.ToUser(),
}) })
<-inDialog.Context().Done() inDialog.Hangup(dialogContext)
} }
func (scs *SIPCallServer) Output(ctx context.Context, payload any) error { func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
payloadMsg, ok := payload.(string) inDialog, ok := ctx.Value(sipCallContextKey("inDialog")).(*diago.DialogServerSession)
if !ok { if !ok {
return errors.New("sip.call.server output payload must be of type string") return errors.New("sip.call.server output must originate from sip.call.server input")
} }
if scs.dg == nil { // _, ok := payload.([]byte)
return errors.New("sip.call.server diago is not initialized")
// if !ok {
// return errors.New("sip.call.server is only able to output bytes")
// }
payloadResponse, ok := payload.(processor.SipAudioFileResponse)
if !ok {
return errors.New("sip.call.server is only able to handle SipCallResponse")
} }
var uri sip.Uri audioFile, err := os.Open(payloadResponse.AudioFile)
err := sip.ParseUri(payloadMsg, &uri)
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %s", err) return err
} }
outDialog, err := scs.dg.NewDialog(uri, diago.NewDialogOptions{ defer audioFile.Close()
Transport: scs.Transport,
}) playback, err := inDialog.PlaybackCreate()
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to create new dialog: %s", err) return err
} }
err = outDialog.Invite(scs.ctx, diago.InviteClientOptions{}) time.Sleep(time.Millisecond * time.Duration(payloadResponse.PreWait))
_, err = playback.Play(audioFile, "audio/wav")
time.Sleep(time.Millisecond * time.Duration(payloadResponse.PostWait))
if err != nil { if err != nil {
return fmt.Errorf("sip.call.server failed to send invite: %s", err) return err
} }
err = outDialog.Ack(scs.ctx) // payloadMsg, ok := payload.(string)
if err != nil { // if !ok {
return fmt.Errorf("sip.call.server failed to send ack: %s", err) // return errors.New("sip.call.server output payload must be of type string")
} // }
// TODO(jwetzell): make this configurable
// NOTE(jwetzell): wait 5 seconds before hanging up the call // if scs.dg == nil {
time.Sleep(5 * time.Second) // return errors.New("sip.call.server diago is not initialized")
err = outDialog.Hangup(scs.ctx) // }
if err != nil {
return fmt.Errorf("sip.call.server failed to hangup call: %s", err) // var uri sip.Uri
} // err := sip.ParseUri(payloadMsg, &uri)
// if err != nil {
// return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %s", err)
// }
// outDialog, err := scs.dg.NewDialog(uri, diago.NewDialogOptions{
// Transport: scs.Transport,
// })
// if err != nil {
// return fmt.Errorf("sip.call.server failed to create new dialog: %s", err)
// }
// err = outDialog.Invite(scs.ctx, diago.InviteClientOptions{})
// if err != nil {
// return fmt.Errorf("sip.call.server failed to send invite: %s", err)
// }
// err = outDialog.Ack(scs.ctx)
// if err != nil {
// return fmt.Errorf("sip.call.server failed to send ack: %s", err)
// }
// // TODO(jwetzell): make this configurable
// // NOTE(jwetzell): wait 5 seconds before hanging up the call
// time.Sleep(5 * time.Second)
// err = outDialog.Hangup(scs.ctx)
// if err != nil {
// return fmt.Errorf("sip.call.server failed to hangup call: %s", err)
// }
return nil return nil
} }

View File

@@ -0,0 +1,97 @@
package processor
import (
"bytes"
"context"
"errors"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
)
type SipResponseCreate struct {
config config.ProcessorConfig
PreWait int
PostWait int
AudioFile *template.Template
}
type SipAudioFileResponse struct {
PreWait int
PostWait int
AudioFile string
}
func (scc *SipResponseCreate) Process(ctx context.Context, payload any) (any, error) {
var audioFileBuffer bytes.Buffer
err := scc.AudioFile.Execute(&audioFileBuffer, payload)
if err != nil {
return nil, err
}
audioFileString := audioFileBuffer.String()
return SipAudioFileResponse{
PreWait: scc.PreWait,
PostWait: scc.PostWait,
AudioFile: audioFileString,
}, nil
}
func (scc *SipResponseCreate) Type() string {
return scc.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "sip.response.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
preWait, ok := params["preWait"]
if !ok {
return nil, errors.New("sip.response.create requires a preWait parameter")
}
preWaitNum, ok := preWait.(float64)
if !ok {
return nil, errors.New("sip.response.create preWait must be a number")
}
postWait, ok := params["postWait"]
if !ok {
return nil, errors.New("sip.response.create requires a postWait parameter")
}
postWaitNum, ok := postWait.(float64)
if !ok {
return nil, errors.New("sip.response.create postWait must be a number")
}
audioFile, ok := params["audioFile"]
if !ok {
return nil, errors.New("sip.response.create requires a audioFile parameter")
}
audioFileString, ok := audioFile.(string)
if !ok {
return nil, errors.New("sip.response.create audioFile must be a string")
}
audioFileTemplate, err := template.New("audioFile").Parse(audioFileString)
if err != nil {
return nil, err
}
return &SipResponseCreate{config: config, AudioFile: audioFileTemplate, PreWait: int(preWaitNum), PostWait: int(postWaitNum)}, nil
},
})
}