ごきげんよう
この記事は
「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」
の8日目の記事です。
今回は技術書典5にむけて
「スマートスピーカーを遊びたおす本」
を執筆・編集し、頒布後に反響があった話について記載します。
・この本がどうやって生まれたか
・複数人でどのように執筆していったか
・当日どうしたか
などは技術的な話ではないのでQiita以外に記載しようと思います。
#■その前にQiita運営に問い合わせた
Qiitaは特性上、技術的な話を記載する場です。
なので、技術書典と言えども、ただの参加記事ではだめだろう。
じゃあ、Qiitaさんとしてはどんな記事なら良いの?
というわけで問い合わせをすると、爆速で回答がきました。
すごすぎるぞQiitaさん。
Qiitaさんの回答
ご認識の通り、Qiitaの記事としてご投稿いただく場合には技術的な記事であるかどうかにご留意ください。
例えば(略)「技術書典での本の執筆や当日の状況の記事」では「本の内容に関連した記事内容」ではないのであれば、外部のブログサービスなどに記載いただく方が向いているかもしれません。
なおQiitaのAdvent Calendarは、Qiita以外の外部サービスのURLをカレンダーへの投稿として登録することが可能となっています。
そちらの機能と併用いただくと良いかもしれません。
とのことで、本の内容に関する投稿を含めれば概ねOKなようです。
#■当日、僕が頒布した本「スマートスピーカーを遊びたおす本」
この本の概要は以下に記載のクラスメソッド社のDevelopersIOに掲載されているので、そちらをご確認ください。
というわけで、僕が記載した章の技術的な話。
##本の内容の技術的な話
僕の担当箇所はたまたま1章になりましたが、Echo Spot対応のスキル「ヒロインの告白」の制作プロセスの話です。
本の内容をすべてブログに載せてしまうと、お金を払ってくれた方に申し訳ないので一部のみ記載します。
##DynamoDBにputする関数は事前に作成してしまう
僕はDynamoDBにデータを格納する際の形式は自分で決めたい派です。
このアドベントカレンダーの3日目
https://qiita.com/surumegohan/items/085024b50d87dd1906e8
でも取り上げましたが、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さんの扱いがすごすぎまして、秋葉原店の技術書典コーナーにないので店員さんに確認したら、まさかの入り口から入ってド正面のド真ん中に平積みしてもらっていました。
逆に気づかなかったという。。
##2.クラスメソッド社のせーのさんが記事にしてくれた
日ごろ、非常に、大変、ものすごく、お世話になっているクラスメソッド社の清野さん(せーのさん)にDevelopersIOの記事として掲載いただけました。
非常にありがたいことです。
■VUIをトータルに勉強できる本「スマートスピーカーを遊びたおす本」を読んでみた。 #Alexa
https://dev.classmethod.jp/voice-assistant/review-the-book-try-smartspeaker-throughly/
##3.この本をきっかけに商業誌の執筆依頼が出版社から頂く
この本をきっかけに、某スマートスピーカーおよび関連技術に関する商業誌の執筆依頼がきて、契約しました。
##4.イベントでめっちゃ感謝された
先日の某イベントに参加していたら、この本と千代田まどか様(ちょまど)のファンの方から熱烈にお礼を伝えていただきました。
その人はGoogle Homeユーザーのようですが、著者としてもそういった声をいただけるのはうれしい限りです。
#■アウトプットはいいぞ
というわけで、アウトプットをし続けていると、いろんな人が自分を観てくれます。
もちろんたまにはディスられます。
それでも、アウトプットをし続けることで、多くの人が応援してくれます。
みなさんもQiitaやブログなどでアウトプットをしてみてはいかがでしょうか。
以上です。