https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-birthday-congratulations-time-capsule-with-go/go_birthday_voiceapi_1200x600.png

囲碁で誕生日を祝うタイムカプセルを作ろう

最終更新日 June 10, 2021

所要時間:5 分

イントロ

パンデミックによって、私たちは家族や友人とのバーチャルな交流を余儀なくされることもあった。しかし、パンデミックが進行していても、私たちの生活は続いている。人々はまだ結婚している。

だから私の誕生日が近づくと、祖母が毎年してくれていたことを思い出す。祖母は朝一番に私に電話をかけ、電話口でハッピーバースデーを歌ってくれたのだ。

この記憶をきっかけに、誕生日のタイムカプセルを作ることを思いついた。友人や家族から電話がかかってきたら、「誕生日を祝ってください」という言葉をVoice録音として残すことができる。そして、あらかじめ決められた日時に電話を受け、すべてのお祝いの言葉を聞くことができる。

前提条件

このチュートリアルを完成させるために必要なもの

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

ングロク・トンネルを作る

Voice コールを発信または受信する際、Vonage は事前に設定された Webhook URL に HTTP リクエストを送信します。これを受信するには、アプリケーションがインターネットにアクセスできる必要があります。 を使用することをお勧めします。.

以下のコマンドでngrokを起動する:

ngrok http 8080 # Creates an http tunnel to the Internet from your computer on port 8080

後でプロジェクトを設定するときに必要になるので、ngrok HTTPS URLをコピーしておいてください。

WebhooksでVonageアプリケーションを作成する

このプロジェクトは、Vonage APIによって作られたインバウンドのウェブフック・リクエストをリッスンすることに依存しているので、新しいアプリケーションを作成する必要がある。新しい アプリケーションを作成する:

  • 名前 - お好きな名前にしてください。

  • 能力

    • Voice

      • アンダー Answer URLを加える: <your ngrok url>/webhooks/answer

      • アンダー Event URLを加える: <your ngrok url>/webhooks/event

    • RTC (In-App Voice & Messaging)

      • アンダー Event URLを加える: <your ngrok url>/webhooks/event

  • 公開鍵と秘密鍵を生成」をクリックし、ファイルをプロジェクト・ディレクトリに移動する。 private.keyファイルをプロジェクトディレクトリに移動します。

  • 変更を保存」をクリックする

これでアプリケーションは、定義済みのウェブフックを送信する準備が整いました!

注意事項アカウントなしで ngrok を使用している場合、ngrok を実行するたびに、アカウントは異なります、 <your ngrok url>は ngrok を実行するたびに異なります。コマンドを実行するたびに Webhook URL を更新することを忘れないでください。または、無料アカウントにサインアップして URL を永続化します。

録音した音声を集める

このプロジェクトの前半は、井戸端会議からVoiceを受け取ることである。

必要なパッケージのインストール

このプロジェクトを成功させるには、サードパーティ製のGoライブラリーがいくつか必要になる。これらには以下が含まれる:

  • joho/godotenv- Vonageの認証情報を安全に保存する

  • vonage/vonage-go-sdk- Vonage APIリクエスト

  • gormそして sqlite音声メッセージのファイル名と再生されたかどうかをSQLiteデータベースに保存する。

これらのサードパーティ・ライブラリをインストールするには、以下の4つのコマンドを実行する:

go 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パッケージを利用し、クレデンシャルをファイルに保存するには、プロジェクト・ディレクトリに .envファイルを作成し、以下の変数を追加する:

VONAGE_APPLICATION_ID= VONAGE_PRIVATE_KEY_PATH=private.key VONAGE_NUMBER= TO_NUMBER= PERSON_NAME= NGROK_URL=

これらの変数には、前のステップで収集した正しい値を必ず入力してください。以下は、必要な値をすべて取得する方法のリストです:

  • VONAGE_APPLICATION_ID- アプリケーションIDは、Vonageのダッシュボードでアプリケーションを作成したときのIDです。 ダッシュボード

  • VONAGE_PRIVATE_KEY- プロジェクト・ディレクトリに関連する private.keyプロジェクトのディレクトリに関連する

  • VONAGE_NUMBER- Vonage 番号は、次の手順で購入したバーチャル電話番号です。 Vonageダッシュボード

  • TO_NUMBER- あらかじめ設定した日時に、すべてのVoiceを録音した電話を受けるNumbers

  • PERSON_NAME- お見舞いを受け取る方のお名前

  • NGROK_URL- 前のステップで受信して保存したngrok URL

このチュートリアルでは、Webhook リクエストからのデータをグループ化するために使用します。という名前の新しいファイルを作成し structs.goという名前の新しいファイルを作成し、以下を追加します:

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を作成し、以下のコードを追加しましょう:

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ファイルをプロジェクトにロードし、ポート 8080.

データベースモデルの作成

オーディオファイルのファイル名と再生されたかどうかを保存するには、データベースを作成する必要があります。モデル BirthdayEntryと、データベースへの接続を処理する関数 connectDb()を作りましょう。新しいファイル models.goという名前の新しいファイルを作成し、以下のコードを追加します:

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{})
}

電話の応対

Voiceメッセージの録音プロセスには複数のステップがあります。最初のステップでは、最初の呼び出しに応答し、次に何をすべきかをVonage APIに指示します。そこで、プロジェクト・ディレクトリに recording.goという名前の新しいファイルを作成し、以下を追加します:

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)
}

上記の機能により、2つのアクションを実行する新しい呼制御オブジェクト(NCCO)が作成される。最初のアクションは "通話 "で、事前に定義されたテキストを音声に変換します。

これらのアクションはJSONオブジェクトに変換され、リクエストに返される。

この機能は現在使われていないので、変更しよう!で戻る main.goに戻って main()関数内に戻り、以下のコードを追加します。 webhooks/answerをリッスンし、トリガーされたら answer()関数を呼び出します:

// First Step - Answer phone call
http.HandleFunc("/webhooks/answer", answer)

通話の録音

音声通話中は RecordActionをトリガーし、マイクが拾ったものすべてを録音し始めます。をトリガーすると、録音が開始されます。 RecordActionをトリガーするときは、通話完了時に録音ファイルの詳細を提供するWebhook URLを定義する必要があります。

録画をトリガーするには、まずウェブサーバーに2つの新しいルートを登録する必要があります。あなたの main.goファイルの answer関数の呼び出しの下に、次の2行を追加します:

// 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ファイルでは、上記のステップで定義した関数の1つが recordUsersMessage()関数は、ユーザーが通話にDTMF応答を入力したとき(たとえば1を押したとき)にトリガーされます。この関数は新しいNCCOを作成し、最初にテキストを音声に変換し、お礼を言い、トーンの後にメッセージを残すように要求します。

番目のアクションは RecordActionで、これはAPIに、トーンの後に言われたことは何でも記録するように指示する。この新しい関数をファイルに追加する:

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)
}

オーディオファイルの保存

音声録音が完了すると、以下の例のように、JSONで /webhooks/recording-fileパスへの呼び出しがトリガーされる:

{
  "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"
}

このJSONの例では recording_urlこのJSONの例では、チュートリアルを動作させるために不可欠なJSONを見ることができます。この録画URLは保護されています。録画ファイルを引き出す際には、JSON Web Token (JWT)を生成し、それを GETリクエストで提供する必要があります。

最初のステップは、このファイルのためにデータベースに新しい行を作成し、ファイル名(Unixタイムスタンプ)を作成し、そして downloadFile()関数を呼び出します。次に recordings.goファイルに以下の関数を追加します:

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()関数がまだ呼び出されていないことにお気づきでしょう。次のステップは、この関数とJWTを生成する別の関数を追加することです。JWTはリクエストのヘッダーとして渡す必要があります。
以下を recordings.goファイルに追加します。このアクションは、Vonageサーバーからオーディオファイルをダウンロードし、それをあらかじめ決められたファイル名で recordingsディレクトリに所定のファイル名で保存します。

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
}

まだJWTトークンを生成していません!そこで、VonageのGo SDKを使って、以下の関数を recordings.go.この関数は VONAGE_APPLICATION_IDVONAGE_PRIVATE_KEY_PATH環境変数を使用して新しい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
}

チュートリアルの後半に進む前に、この半分を最初から最後までテストしたいと思います。

まず、プロジェクトが実行されていることを確認してください。Terminalのプロジェクト・ディレクトリの中で、次のコマンドを実行してください:

go run .

ngrokはまだ動いているはずなので、あなたの電話を使ってVonageのバーチャル番号に電話をかけてみてください。

最初の応答は次のような音声メッセージである。 <insert name here>..メッセージを残したい場合は、1を押してください。それ以外は通話を終了してください。ありがとうございました。

キーパッドで1を押すと、「ありがとうございました。トーンの後にメッセージを残してください。ここで、自分の言葉を録音し、電話を切ってください。

電話が終了してから数秒後、電話帳を確認する。 recordingsディレクトリを確認してください。新しいファイルが作成されているはずです。

誕生日の人のためにシステムの一部を構築する時だ!

誕生日の人を呼ぶ

Cronjobの作成とお祝い

このプロジェクトでは、特定の日時に関数の1つを実行するメソッドが必要です。
cronジョブはUnixオペレーティングシステムのタイムスケジューラです。このプロジェクトでは、特定の関数を実行する特定の日時を定義するためにGo用のcronライブラリを使用します。

ターミナルで以下のコマンドを実行し、このcronライブラリをインストールしてください:

go get github.com/robfig/cron

関数内の main()関数の中で main.goの中で、まだ作成されていない関数を呼び出します、 runCongratulateCron()を呼び出す部分の下にこれを追加する。 connectDb():

runCongratulateCron()

チュートリアルの最初の部分と機能を分けておくために、この部分に必要な機能を別のファイルに追加します。という名前の新しいファイルを作成し congratulate.goという名前の新しいファイルを作成し、以下のコードを追加します:

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})
}

上記のコードには2つの機能がある。
まず runCongratulateCron()関数は新しいcronjobを定義し、誕生日の人が電話を受ける時間を指定します。cronjobで時間を設定する方法がわからない場合は Crontab Guruを参照してください。

2つ目の関数は、1つ目の関数から呼び出され、誕生日の人に音声合成を発信し、InputAction(「Press 1 to continue」)を求める。受信側で通話をアクティブに保つには ConversationActionが必要である。次のステップで、通話中に音声を再生する方法を学ぶが、これはアクティブな 会話の中で行う必要がある。

通話中にオーディオを再生する

通話ができたので、音声通話に音声ファイルを再生するコードを追加する必要があります。これを行うには、UUIDを取得し、最初に再生したいファイルのURLと一緒に PlayAudioStream関数を呼び出すリクエストに渡す必要があります。

注意音声ファイルをキューに入れることはできません。各オーディオファイルの再生をループして呼び出すと、各オーディオファイルが最新のファイルで中断されてしまいます。これを避けるには、ファイルを再生してから、完了時にイベントが来るのを待つ必要があります。次に、データベースから次の未再生のオーディオファイルを見つけ、前のオーディオファイルの完了時にそのファイルを再生します。

そこで congratulate.goに以下のコードを追加する:

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行を探し http.HandleFunc("/webhooks/recording-file", getFileRecording)を見つけ、以下を追加する:

http.HandleFunc("/congratulate", congratulate)
http.HandleFunc("/webhooks/play-audio", congratulatePlayAudio)

次のオーディオファイルを再生するトリガーリクエスト

前に説明したように、前の音声ファイルの再生が完了したら、次の音声ファイルを再生する必要があります。ダッシュボードの: RTC (In-app voice & messaging)の下に定義したWebhook URLを使用して、リクエストに特定のキーが含まれる特定のイベントをリッスンします。リクエストの event.type部分をリッスンすることで、その値が audio:play:doneであるかどうかをチェックし、関数 playAudioを呼び出して、次の未再生のオーディオファイルを探します。

インサイド congratulate.goこの新しい event関数を追加する:

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行の下に http.HandleFunc("/webhooks/play-audio", congratulatePlayAudio)を追加する:

http.HandleFunc("/webhooks/event", event)

以上です!これで、誕生日を祝うタイムカプセルをGoで作成できました! 以下では、機能をテストするためのステップ・バイ・ステップのプロセスを紹介します。

テストしてみよう!

さて、このプロジェクトを立ち上げたので、最初から最後までのプロセスの概略を説明しよう:

  1. あなたのバーチャルVonage番号に親愛なる人々が電話をかける

  2. お誕生日おめでとうホットラインにお電話いただきありがとうございます。 <insert name here>..メッセージを残したい場合は、1を押してください。それ以外の場合は、通話を終了してください。ありがとうございました。

  3. アプリはキーパッドに数字を入力するのを待つ。

  4. 次のウェブフックはリクエストを受信し、音声合成メッセージを送信する。ありがとうございました。

  5. ビープ音が鳴り、通話が録音されます。

  6. 用が済んだら通話を終了する。

しかし、多くの支援者は1~6のステップを繰り返すことができる。

  1. 指定された時刻に runCongratulateCron()関数で定義)、関数 congratulate()が呼び出される。

  2. 誕生日の人へのアプリケーションがアウトバウンドコールをかける。

  3. 電話に出ると、受信者には「お誕生日おめでとうございます!お誕生日おめでとうございます。お聞きになりたい方は1を押してください。"

  4. この通話は、受信者がキーパッドで番号を押すのを待っている状態である。

  5. その後、アプリケーションはデータベースから最初の未再生のオーディオファイルを取得し、音声通話にストリーミングします。

  6. オーディオ・ファイル・ストリームが完了すると、イベントがアプリケーションに送り返される。このイベントを受信すると、アプリケーションは次の未再生のオーディオ・ファイルを見つけ、呼び出しを通じてストリームします。

  7. 未再生のオーディオファイルがない場合、通話は終了します。

VonageのVoice APIを使って、誕生日祝いのタイムカプセルをGoに統合しました。ここで紹介した例は、Voice APIを使用する多くの方法のひとつにすぎません。

このチュートリアルでVoice APIに興味を持たれた方で、Goがお好きな言語でない場合は、様々な言語やサービスの他のチュートリアルをこちらからご覧いただけます。 Vonage ブログにあります:

コミュニティで共有したい質問、アドバイス、アイデアなどがありましたら、お気軽に私たちの コミュニティSlackワークスペースまたは ツイッター.このチュートリアルを実施した方、またあなたのプロジェクトがどのように機能しているか、お返事をお待ちしています。

シェア:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg Holmesヴォネージの卒業生

元Vonage開発者エデュケーター。PHPのバックグラウンドを持つが、一つの言語に縛られることはない。熱心なゲーマーでRaspberry pi愛好家。屋内クライミング施設でボルダリングをしていることが多い。