するめごはんのIT日記

主にITネタを書いていくのさ

技術書典5にて「スマートスピーカーを遊びたおす本」を執筆し、反響があった話

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の8日目の記事です。

今回は技術書典5にむけて 「スマートスピーカーを遊びたおす本」 を執筆・編集し、頒布後に反響があった話について記載します。

f:id:surumegohan:20181208102943j:plain
スマートスピーカーを遊びたおす本

■当日、僕が頒布した本「スマートスピーカーを遊びたおす本」

この本の概要は以下に記載のクラスメソッド社のDevelopersIOに掲載されているので、そちらをご確認ください。

というわけで、僕が記載した章の技術的な話。

本の内容の技術的な話

僕の担当箇所はたまたま1章になりましたが、Echo Spot対応のスキル「ヒロインの告白」の制作プロセスの話です。

本の内容をすべてブログに載せてしまうと、お金を払ってくれた方に申し訳ないので一部のみ記載します。

DynamoDBにputする関数は事前に作成してしまう

僕はDynamoDBにデータを格納する際の形式は自分で決めたい派です。

このアドベントカレンダーの3日目

surumegohan.hatenablog.com

でも取り上げましたが、SDKをいじるくらい自分で操りたい人です。

なので、今ではPut用のfunctionは共通化して作成してしまっています。

//DynamoDBにputする関数
function putDynamo(params) {
    
    console.log("=== putDynamo function ===" + params);
    
    docClient.put(params, function (err, data) {
    
        console.log("=== put ===");
    
        if (err) {
            console.log(err);
        } else {
            console.log(data);
        }
    });
}

2回目以降の起動ユーザーは説明文を省くように実装する

上記のDynamoDBへのPutのfunctionをもちいて、LaunchRequestでの2回目以降の起動ユーザーは、スキルの説明をカットして、告白しても良いかどうかのみを結城琴葉ちゃんが話しかけるようにしています。

これはVUIのデザイン・設計として非常に重要だと僕は考えていて、2回目以降のユーザーは説明文をいちいち聞くのは極めて不快です。

もちろん、スキルの特性にもよりますが、このスキルは告白に対する受け答えをするスキルなので、複雑な操作は不要だと感じています。

また、2回目以降のユーザーでも初回起動時の説明文が流れるように「ヘルプ」では初回起動時のメッセージを流すようにしています。

ちなみに、ここでは余談ですが、僕がスキルを創るときは、ユーザーとVUIアプリケーションの対話の状況によって、「ヘルプ」のメッセージを変更するように実装しています。

// スタート音声
const kotoha_start_01 = '<audio src=\"https://hogehoge/kotoha_start_01.mp3\" />';
const kotoha_start_existuser = '<audio src=\"https://hogehoge/kotoha_start_existuser.mp3\" />';

//中略

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {


    // Amazonから提供されているTemplate 6を使用
    if (supportsDisplay(handlerInput)){
      const myImage1 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg1.url) // 結城琴葉の画像
        .getImage();

      const myImage2 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg2.url) // 今回はDisplayImg1.urlと同じ
        .getImage();

      const primaryText = new Alexa.RichTextContentHelper()
        .withPrimaryText('')  // 文字を出力させたくなかったのでこの記載
        .getTextContent();

        handlerInput.responseBuilder.addRenderTemplateDirective({
        type: 'BodyTemplate6',
        token: 'string',
        backButton: 'HIDDEN',
        backgroundImage: myImage2,
        image: myImage1,
        title: "",
        textContent: primaryText
      });
    }
               
    // 起動時メッセージを一旦、2回目以降の起動ユーザー用に代入
    // 初回起動ユーザーで初期化するか迷いましたが、2回目以降のユーザー用に代入
    let kotoha_start = kotoha_start_existuser;

    try{
        
        // DynamoDBにあるテーブルに対して、既に存在するuserIdかどうか検索を実行
        const queryItems = await docClient.query({
          TableName: "kotohaTable", 
          KeyConditionExpression: "#userId = :userId",
          ExpressionAttributeNames: {"#userId": "userId"},
          ExpressionAttributeValues: {":userId": json_userId} // JSONからuserIdを事前取得しておいた値
        }).promise();
                
        try{
            
            // 検索した結果、Items[0]にuserIdが入っているかログ出力を兼ねて確認
            console.log("queryItems.Items[0].userId: " + queryItems.Items[0].userId);
        
        } catch (err){
            
            // 検索した結果がゼロ件ならばログを出力できず、ここのcatchに分岐
            // ここに流れてきた場合は、再生するmp3へのリンクを初回起動のユーザーとして再度代入
            kotoha_start = kotoha_start_01;
            
            console.log("user Nothing");

            // DynamoDBに存在しないuserIdだったため、格納する処理をここから記載
            var item = {
                // JSONから取得しているuserIdをDynamoDBのuserIdとして格納する            
                userId: json_userId
            };

            // DynamoDBに格納する情報をparamsとしてまとめる
            var params = {
                TableName: kotohaTable,
                Item: item
            };

            // DynamoDBに格納する
            // 本来はここでもエラー処理を入れているが割愛
            await putDynamo(params);

        }
        
    } catch(err){
        
        // エラー時の処理を入れているが割愛

    }

    // sessionAttributesに起動後状態に移ったことを示す

    var sessionAttribute = '';
    
    sessionAttribute = {
    "STATE": "after_start"
    };
    
    handlerInput.attributesManager.setSessionAttributes(sessionAttribute); 

    // 起動時の「スマートマキアート」の後に0.7秒の間を開けてから、起動メッセージを流す
    speechText = kotoha_smartmacchiato + '<break time="0.7s"/>' + kotoha_start;

    // 再生する内容はmp3のみであるため、speakもrepromtもspeakタグでくくっておく
    return handlerInput.responseBuilder
      .speak('<speak>' + speechText + '</speak>')
      .reprompt('<speak>' + speechText + '</speak>')
      .withShouldEndSession(false) //セッションを切らないことを明示する
      .getResponse();
  }
};

■各方面での反響

1.委託先のCOMIC ZIN 秋葉原店でド正面に平積み

BOOTHさん、とらのあなさん、ZINさんに売れ残った分を委託しておりますが、特にZINさんの扱いがすごすぎまして、秋葉原店の技術書典コーナーにないので店員さんに確認したら、まさかの入り口から入ってド正面のド真ん中に平積みしてもらっていました。

逆に気づかなかったという。。

f:id:surumegohan:20181208103050p:plain
COMIC ZINさん

2.クラスメソッド社のせーのさんが記事にしてくれた

日ごろ、非常に、大変、ものすごく、お世話になっているクラスメソッド社の清野さん(せーのさん)にDevelopersIOの記事として掲載いただけました。

非常にありがたいことです。

■VUIをトータルに勉強できる本「スマートスピーカーを遊びたおす本」を読んでみた。 #Alexa

dev.classmethod.jp

f:id:surumegohan:20181208103118p:plain
みんな大好きDevelopersIO

3.この本をきっかけに商業誌の執筆依頼が出版社から頂く

この本をきっかけに、某スマートスピーカーおよび関連技術に関する商業誌の執筆依頼がきて、契約しました。

4.イベントでめっちゃ感謝された

先日の某イベントに参加していたら、この本と千代田まどか様(ちょまど)のファンの方から熱烈にお礼を伝えていただきました。

その人はGoogle Homeユーザーのようですが、著者としてもそういった声をいただけるのはうれしい限りです。

■アウトプットはいいぞ

というわけで、アウトプットをし続けていると、いろんな人が自分を観てくれます。 もちろんたまにはディスられます。

それでも、アウトプットをし続けることで、多くの人が応援してくれます。

みなさんもブログやイベントの登壇などでアウトプットをしてみてはいかがでしょうか。

以上です。