29
loading...
This website collects cookies to deliver better user experience
ngrok http 8080 # Creates an http tunnel to the Internet from your computer on port 8080
Capabilities
Answer URL
add: <your ngrok url>/webhooks/answer
Event URL
add: <your ngrok url>/webhooks/event
Event URL
add: <your ngrok url>/webhooks/event
Click "Generate public & private key" and move the private.key
file into your project directory.
Click "Save changes"
Note If you're using ngrok without an account, <your ngrok url>
will be different every time you run ngrok. Remember to update your webhook URLs every time you run the command. Alternatively, sign up for a free account to make the URL persist.
joho/godotenv
- to securely store our Vonage credentialsvonage/vonage-go-sdk
- to make our API requests at Vonagegorm
and sqlite
to store the voice message file names and whether they've been played into an SQLite databasego get github.com/joho/godotenv
go get github.com/vonage/vonage-go-sdk
go get gorm.io/gorm
go get gorm.io/driver/sqlite
joho/gotdotenv
package, and start storing your credentials in a file, create your .env
file in your project directory and add the following variables:VONAGE_APPLICATION_ID=
VONAGE_PRIVATE_KEY_PATH=private.key
VONAGE_NUMBER=
TO_NUMBER=
PERSON_NAME=
NGROK_URL=
VONAGE_APPLICATION_ID
- Your application ID is the ID given when you created an application in Vonage's dashboard
VONAGE_PRIVATE_KEY
- The location of the private.key
file relevant to the project directoryVONAGE_NUMBER
- Your Vonage number is the virtual phone number you purchased in the Vonage Dashboard
TO_NUMBER
- The number that will be receiving the call with all the voice recordings at your predetermined date and timePERSON_NAME
- The name of the person who will be receiving these well wishesNGROK_URL
- The ngrok URL you received and stored in a previous stepstructs.go
and add the following:package main
type Dtmf struct {
Digits string
Timed_out bool
}
type EventResponse struct {
Conversation_id string
Type string
Body EventBodyResponse
}
type EventBodyResponse struct {
Channel EventBodyChannelResponse
}
type EventBodyChannelResponse struct {
Id string
Type string
}
type Recording struct {
Start_time string
Recording_url string
Size int
Recording_uuid string
End_time string
Conversation_uuid string
Timestamp string
}
type Response struct {
Speech []string
Dtmf Dtmf
From string
To string
Uuid string
Conversation_uuid string
Timestamp string
}
main.go
, in your project directory and add the following code to it:package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/joho/godotenv"
"github.com/vonage/vonage-go-sdk/jwt"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
connectDb()
http.ListenAndServe(":8080", nil)
}
.env
file into the project and creates a web server listening on port 8080
.BirthdayEntry
and a function connectDb()
to handle connecting to our database. Create a new file called models.go
and add the following code:package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var db *gorm.DB
var err error
type BirthdayEntry struct {
gorm.Model
FileName string
Played bool
}
func connectDb() {
db, err = gorm.Open(sqlite.Open("voiceRecordings.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&BirthdayEntry{})
}
recording.go
and add the following:package main
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"time"
"github.com/vonage/vonage-go-sdk"
"github.com/vonage/vonage-go-sdk/ncco"
"github.com/vonage/vonage-go-sdk/jwt"
)
func answer(w http.ResponseWriter, req *http.Request) {
MyNcco := ncco.Ncco{}
talk := ncco.TalkAction{Text: "Thank you for calling the birthday congratulations hotline for " + os.Getenv("PERSON_NAME") + ".. If you would like to leave a message, please press 1. Otherwise end the call. Thank you"}
MyNcco.AddAction(talk)
inputAction := ncco.InputAction{EventUrl: []string{"https://" + req.Host + "/webhooks/record"}, Dtmf: &ncco.DtmfInput{MaxDigits: 1}}
MyNcco.AddAction(inputAction)
data, _ := json.Marshal(MyNcco)
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
main.go
within the main()
function, add the following line of code, which tells the webserver to listen for the URL webhooks/answer
, and when triggered, call the answer()
function:// First Step - Answer phone call
http.HandleFunc("/webhooks/answer", answer)
RecordAction
in the NCCO is triggered and starts recording anything your microphone will pick up. When you trigger the RecordAction
, you need to define the webhook URL to provide you with the details of the recorded file upon completion of the call. main.go
file below your call to the answer
function, add the following two lines:// Second Step - Take Voice Recording
http.HandleFunc("/webhooks/record", recordUsersMessage)
// Third Step - Receive Voice Recording confirmation + Download the file
http.HandleFunc("/webhooks/recording-file", getFileRecording)
recording.go
file, one of the functions you defined in the step above is the recordUsersMessage()
function, triggered when the user inputs their DTMF response into the call (Pressing 1, for example). This function will create a new NCCO, which will first convert some text to speech, thanking them, then requesting they leave a message after the tone. RecordAction
, which tells the API to record whatever is said after the tone. Add this new function to your file:func recordUsersMessage(w http.ResponseWriter, req *http.Request) {
data, _ := ioutil.ReadAll(req.Body)
var response Response
json.Unmarshal(data, &response)
MyNcco := ncco.Ncco{}
talk := ncco.TalkAction{Text: "Thank you. Please leave a message after the tone."}
MyNcco.AddAction(talk)
recordAction := ncco.RecordAction{EventUrl: []string{"https://" + req.Host + "/webhooks/recording-file"}, Format: "mp3", BeepStart: true, EndOnSilence: 10}
MyNcco.AddAction(recordAction)
responseData, _ := json.Marshal(MyNcco)
w.Header().Set("Content-Type", "application/json")
w.Write(responseData)
}
/webhooks/recording-file
path is triggered with JSON, similar to the example below:{
"start_time": "2020-01-01T12:00:00Z",
"recording_url": "https://api.nexmo.com/v1/files/aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
"size": 12345,
"recording_uuid": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
"end_time": "2020-01-01T12:01:00Z",
"conversation_uuid": "bbbbbbbb-cccc-dddd-eeee-0123456789ab",
"timestamp": "2020-01-01T14:00:00.000Z"
}
recording_url
, which is vital for our tutorial to work. This recording URL is protected; you need to generate a JSON Web Token (JWT) and provide it with the GET
request when pulling that recording file. downloadFile()
function. Then, in your recordings.go
file, add the following function:func getFileRecording(w http.ResponseWriter, req *http.Request) {
data, _ := ioutil.ReadAll(req.Body)
var recording Recording
json.Unmarshal(data, &recording)
responseData, _ := json.Marshal(data)
fileName := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) + ".mp3"
err := downloadFile(recording.Recording_url, fileName)
if err != nil {
log.Fatal(err)
}
birthdayEntry := BirthdayEntry{FileName: fileName, Played: false}
_ = db.Create(&birthdayEntry)
w.Header().Set("Content-Type", "application/json")
w.Write(responseData)
}
downloadFile()
function called in the example above. Our next step is to add this as well as another function to generate our JWT. The JWT needs passing as a header in the request.recordings.go
file. This action will download the audio file from Vonage servers and save it as a file in the recordings
directory with the predetermined file name.func downloadFile(audioUrl string, fileName string) error {
//Get the response bytes from the url
reqUrl, _ := url.Parse(audioUrl)
token := generateJWT()
request := &http.Request{
Method: "GET",
URL: reqUrl,
Header: map[string][]string{
"Authorization": {"Bearer " + token},
},
}
response, err := http.DefaultClient.Do(request)
if err != nil {
log.Fatal("Error:", err)
}
defer response.Body.Close()
if response.StatusCode != 200 {
return errors.New("received non 200 response code")
}
file, err := os.Create("./recordings/" + fileName)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, response.Body)
if err != nil {
return err
}
return nil
}
recordings.go
. This function uses your VONAGE_APPLICATION_ID
and your VONAGE_PRIVATE_KEY_PATH
environment variables to generate a new JWT.func generateJWT() string {
applicationId := os.Getenv("VONAGE_APPLICATION_ID")
privateKey, _ := ioutil.ReadFile(os.Getenv("VONAGE_PRIVATE_KEY_PATH"))
g := jwt.NewGenerator(applicationId, privateKey)
token, _ := g.GenerateToken()
return token
}
go run .
recordings
directory. You'll see a new file created.go get github.com/robfig/cron
main()
function within your main.go
we're going to call a function yet to be created, runCongratulateCron()
, so add this below the part where you call connectDb()
:runCongratulateCron()
congratulate.go
and add the following code:package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/robfig/cron"
"github.com/vonage/vonage-go-sdk"
"github.com/vonage/vonage-go-sdk/ncco"
)
func runCongratulateCron() {
c := cron.New()
// This would be triggered at midnight on 1st Jan
c.AddFunc("0 0 0 1 1 *", func() {
congratulate()
})
c.Start()
}
func congratulate(w http.ResponseWriter, req *http.Request) {
privateKey, _ := ioutil.ReadFile(os.Getenv("VONAGE_PRIVATE_KEY_PATH"))
auth, _ := vonage.CreateAuthFromAppPrivateKey(os.Getenv("VONAGE_APPLICATION_ID"), privateKey)
client := vonage.NewVoiceClient(auth)
from := vonage.CallFrom{Type: "phone", Number: os.Getenv("VONAGE_NUMBER")}
to := vonage.CallTo{Type: "phone", Number: os.Getenv("TO_NUMBER")}
MyNcco := ncco.Ncco{}
talkAction := ncco.TalkAction{Text: "Happy Birthday! I have collected a number of recordings from your friends and family wishing you a happy birthday. If you would like to listen to this, please press 1."}
MyNcco.AddAction(talkAction)
inputAction := ncco.InputAction{EventUrl: []string{"https://" + os.Getenv("NGROK_URL") + "/webhooks/play-audio"}, Dtmf: &ncco.DtmfInput{MaxDigits: 1}}
MyNcco.AddAction(inputAction)
conversationAction := ncco.ConversationAction{Name: os.Getenv("TO_NUMBER"), StartOnEnter: "false"}
MyNcco.AddAction(conversationAction)
client.CreateCall(vonage.CreateCallOpts{From: from, To: to, Ncco: MyNcco})
}
runCongratulateCron()
function defines a new cronjob and adds the specified time for the birthday person to receive their phone call. If you're unsure how to set up the times with a cronjob, please check the Crontab Guru to build your custom time set.ConversationAction
is needed. We'll learn how to play the audio into the call in the next step, but this needs to be done in an active conversation.PlayAudioStream
function, alongside the URL of the file you wish to play first.Note you cannot queue the audio files. If you loop through playing each audio file into the call, it will interrupt each audio file with the latest one. To avoid this, we need to play the file and then wait for an event to come in on completion. We then find the next unplayed one in the database and play that one on completion of the previous audio file.
congratulate.go
add the following code:func congratulatePlayAudio(w http.ResponseWriter, req *http.Request) {
data, _ := ioutil.ReadAll(req.Body)
var response Response
json.Unmarshal(data, &response)
playAudio(response.Uuid, req.Host)
}
func playAudio(uuid string, host string) {
var birthdayEntry BirthdayEntry
privateKey, _ := ioutil.ReadFile(os.Getenv("VONAGE_PRIVATE_KEY_PATH"))
auth, _ := vonage.CreateAuthFromAppPrivateKey(os.Getenv("VONAGE_APPLICATION_ID"), privateKey)
client := vonage.NewVoiceClient(auth)
if err := db.First(&birthdayEntry, "played = ?", false).Error; err != nil {
client.PlayTts(uuid, "This is the end of your birthday wishes, you may now hang up.", vonage.PlayTtsOpts{})
return
}
fmt.Println("https://" + host + "/" + birthdayEntry.FileName)
result, _, _ := client.PlayAudioStream(uuid,
"https://"+host+"/"+birthdayEntry.FileName,
vonage.PlayAudioOpts{},
)
birthdayEntry.Played = true
db.Save(&birthdayEntry)
fmt.Println("Update message: " + result.Message)
}
main.go
find the line http.HandleFunc("/webhooks/recording-file", getFileRecording)
and add the following:http.HandleFunc("/congratulate", congratulate)
http.HandleFunc("/webhooks/play-audio", congratulatePlayAudio)
RTC (In-app voice & messaging)
in the dashboard, we'll listen for a specific event that contains a particular key in the request. By listening to the event.type
part of the request, we'll be able to check if the value is: audio:play:done
, and then call the function playAudio
to find the following unplayed audio file.congratulate.go
add this new event
function:func event(w http.ResponseWriter, req *http.Request) {
var event EventResponse
err := json.NewDecoder(req.Body).Decode(&event)
if err != nil {
return
}
if event.Type == "audio:play:done" {
playAudio(event.Body.Channel.Id, req.Host)
}
}
main.go
, under the line http.HandleFunc("/webhooks/play-audio", congratulatePlayAudio)
add:http.HandleFunc("/webhooks/event", event)
runCongratulateCron()
function), the function congratulate()
is called.