N@i.jp  昨日:2511
 今日:2174
 総計:01428309
keywords
管理者専用
  Post   Add link   Control Panel 






























新しいトピック
最新:07/20 18:08


新しいコメント
最新:06/04 19:41





Alexaの緊急連絡スキルを作る(3)

"MyEmergency"関数のプログラム本体です


6. "index.js"を置き換える

 この後、"MyEmergency"関数をAWSにデプロイ(アップロード)するわけですが、ついでですからプログラム本体も一緒にデプロイできるようにしてしまいましょう。"index.js"を以下のプログラムに書き換えます。

 なお、まだ色々と試行錯誤、テスト中であり、正常動作を保証するものではありませんので、その点は御容赦です。

'use strict';

const Alexa = require('alexa-sdk');
const APP_ID = process.env.Emergency_APPID;  // 緊急連絡 app ID

//環境変数の取り込み
const smtp_host         = process.env.SMTP_HOST;            // SMTPサーバー
const smtp_user         = process.env.SMTP_USER_NAME;
const smtp_password     = process.env.SMTP_PASSWORD;
const emergency_to      = process.env.EMERGENCY_SENDTO;     // 緊急連絡先メールアドレス
const twilio_account    = process.env.TWILIO_ACCOUNT_SID;
const twilio_authtoken  = process.env.TWILIO_AUTH_TOKEN;
const twilio_call_to    = process.env.EMERGENCY_CALL_TO_NUMBER; // 緊急連絡先電話番号
const twilio_call_from  = process.env.EMERGENCY_CALL_FROM_NUMBER;

var nodemailer = require('nodemailer');
//SMTPの設定
var SSLSmtpConfig = {
    host: smtp_host,
    port: 465,      // SSLを使用する場合のポート番号
    secure: true,   // SSLの有効化
    auth: {
        user: smtp_user,
        pass: smtp_password,
        tls: {
            rejectUnauthorized: false   // do not fail on invalid certs
        }
    }
};
var GeneralSmtpConfig = {
    host: smtp_host,
    port: 25,
    auth: {
        user: smtp_user,
        pass: smtp_password,
        use_authentication: false,      // 認証しない
   }
};

// 言語リソース
const SLEEP = '<break time="300ms"/>';
const CR = "\n";
const ALEXA_EMERGENCY_TEXT      = "これはアレクサからの緊急連絡です。";
const ASP                       = "「大至急、";
const CONTACT_MESSAGE_TEXT      = "自宅に連絡してください。」";
const REQMAIL_MESSAGE_TEXT      = "メールしてください。」";
const PHONECALL_MESSAGE_TEXT    = "自宅へ電話してください。」";
const BACK2HOME_MEESSAGE_TEXT   = "自宅へ帰って来てください。」";
const REQHELP_MESSAGE_TEXT      = "「自宅で助けを呼んでいます。」";
const languageStrings = {
    "ja": {     // 日本語
        translation: {
            THIS_SKILL_NAME:    "緊急連絡",
            MESSAGE_OF:         "と、",
            WELCOME_MESSAGE:    "さん、緊急連絡メール、緊急電話連絡が使用できます。",
            HELP_MESSAGE:       "緊急連絡のメール送信、または電話ができます。"+
                "メールの場合は、「緊急連絡で、連絡してとメール」、「緊急連絡で、電話してとメール」、などと指示してください。"+
                "電話の場合は、「緊急連絡で、連絡してと電話」、「緊急連絡で、帰ってきてと電話」、などと指示してください。"+
                "送ることができるメッセージは、" + SLEEP +
                "連絡して" + SLEEP +
                "メールして" + SLEEP +
                "電話して" + SLEEP +
                "帰宅して" + SLEEP +
                "助けて" + SLEEP + "です。",
            AUTH_MESSAGE:       "このスキルを使用するには、Amazonへのログインを許可してください。",
            NOPROF_MESSAGE:     "Amazonの認証情報が取得できませんでした。",
            HTTPS_GET_ERROR:    "通信に問題が発生しました。",
            ASK_TEST:           "動作確認のため、"+
                "テストメールを送信する場合は、「緊急連絡で、テストメールを送信」と、"+
                "テスト電話をかける場合は、「緊急連絡で、テスト電話をかけて」と指示してください。",
            TESTMAIL_TEXT:      "からのテストメールです。",
            TESTCALL_TEXT:      "からのテスト電話です。",
            TESTMAIL_SUCCESS:   "さんへ、テストメールを送信しました。",
            TESTCALL_SUCCESS:   "さんへ、テスト電話をかけました。",
            SEND_FAILED:        "メールの送信に失敗しました。",
            PHONECALL_FAILED:   "さんに電話が繋がりませんでした。",
            SEND_TO:            "さんへ、",
            SEND_MAIL:          "メールを送信しました。",
            PHONE_CALL:         "電話をかけました。",
            CONTACT_MESSAGE:    ASP + CONTACT_MESSAGE_TEXT,
            REQMAIL_MESSAGE:    ASP + REQMAIL_MESSAGE_TEXT,
            PHONECALL_MESSAGE:  ASP + PHONECALL_MESSAGE_TEXT,
            BACK2HOME_MEESSAGE: ASP + BACK2HOME_MEESSAGE_TEXT,
            REQHELP_MESSAGE:    REQHELP_MESSAGE_TEXT,
            EMERGENCY_SUBJECT:  ALEXA_EMERGENCY_TEXT,
            EMERGENCY_CONTACT:  ALEXA_EMERGENCY_TEXT + CR + ASP + CONTACT_MESSAGE_TEXT,
            EMERGENCY_REQMAIL:  ALEXA_EMERGENCY_TEXT + CR + ASP + REQMAIL_MESSAGE_TEXT,
            EMERGENCY_CALL:     ALEXA_EMERGENCY_TEXT + CR + ASP + PHONECALL_MESSAGE_TEXT,
            EMERGENCY_COMEBACK: ALEXA_EMERGENCY_TEXT + CR + ASP + BACK2HOME_MEESSAGE_TEXT,
            EMERGENCY_HELP:     ALEXA_EMERGENCY_TEXT + CR + REQHELP_MESSAGE_TEXT,
            WHAT_REQUEST:       "どのような緊急メッセージを送りますか?"+
                "メールを送る場合は、「連絡してとメール」、「電話してとメール」などと。"+
                "電話をかける場合は、「連絡してと電話」、「帰って来てと電話」などと、"+
                "指示してください。",
            WHAT_REQUEST_MAIL:  "どのような緊急メッセージを送りますか?"+
                "「連絡してとメール」、「電話してとメール」、などと指示してください。",
            WHAT_REQUEST_CALL:  "どのような緊急メッセージを送りますか?"+
                "「連絡してと電話」、「帰って来てと電話」、などと指示してください。",
            NAME_IS:            "連絡先氏名:",
            ADDRESS_IS:         "緊急連絡先のメールアドレスと電話番号は、",
            MAIL_ADDRESS_IS:    "緊急連絡先メールアドレス:",
            PHONE_NUMBER_IS:    "緊急連絡先電話番号:",
            SEE_THE_CARD:       "アレクサアプリで緊急連絡のカードを参照してください。",
            CANCEL_MESSAGE:     "緊急連絡を中止します。",
            STOP_MESSAGE:       "緊急連絡を終了します。",
            UNKNOWN_MESSAGE:    "すみません、分かりません。",
            RETRY_MESSAGE:      "すみません、もう一度お願いします。",
        },
    },
};

const handlers = {
    // 「緊急連絡を開く」で呼び出された場合
    'LaunchRequest': function() {
        var name;
        var email;
        console.log("LaunchRequest: start");
        try {
            name = this.attributes['name'];
            email = this.attributes['email'];
        } catch (e) {
            this.emit("WelcomeIntent");
        }
        this.attributes['stat'] = undefined;
        if (name === undefined || email === undefined) {
            this.emit("WelcomeIntent");
        }
        else {
            console.log("LaunchRequest: Ask your request");
            this.emit(':ask', this.t("WHAT_REQUEST"));
        }
    },

    // 「緊急連絡で○○をメール」で呼び出された場合
    'SendEmergencyMail': function (callback) {
        var session = this.event.session;		// セッション
        var intent = this.event.request.intent;	// インテント
        var req = intent.slots.Message.value;   // スロット{Message}の値
        var name = this.attributes['name'];     // DynamoDBから取り出す
        var email = this.attributes['email'];   // DynamoDBから取り出す
        var sendTo = emergency_to;
        var msg_text;
        var req_text;
        var self = this;

        if (!req) {
            let dialogState = this.event.request.dialogState;
            if (dialogState === "STARTED") {
                let updatedIntent = intent;
                //updatedIntent.slots.Message.value = undefined;
                console.log("SendEmergencyMail: dialogState is STARTED.");
                this.emit(':delegate');
                //this.emit(':delegate', updatedIntent);    //初期値を設定したい場合
            }
            else if (dialogState !== "COMPLETED") {
                console.log("SendEmergencyMail: dialogState is " + dialogState);
                this.emit(':delegate');
                //スロット{Message}について尋ねる
                //this.emit(':elicitSlot', "Message", this.t("WHAT_REQUEST_MAIL"));
            }
        }
        this.attributes['stat'] = "SendEmergencyMail";
        console.log("SendEmergencyMail: name="+name+", email="+email);
        console.log("SendEmergencyMail: req="+req);
        switch (req) {
            case "連絡":
            case "連絡して":
            case "連絡ください":
                msg_text = this.t("EMERGENCY_CONTACT");
                req_text = this.t("CONTACT_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "メール":
            case "メールして":
            case "メールください":
                msg_text = this.t("EMERGENCY_REQMAIL");
                req_text = this.t("REQMAIL_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "電話":
            case "電話して":
            case "電話ください":
                msg_text = this.t("EMERGENCY_CALL");
                req_text = this.t("PHONECALL_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "帰宅":
            case "帰宅して":
            case "帰って":
            case "帰ってきて":
            case "戻って":
            case "戻ってきて":
                msg_text = this.t("EMERGENCY_COMEBACK");
                req_text = this.t("BACK2HOME_MEESSAGE")+this.t("MESSAGE_OF");
                break;
            case "助け":
            case "助けて":
            case "ヘルプ":
                msg_text = this.t("EMERGENCY_HELP");
                req_text = this.t("REQHELP_MESSAGE")+this.t("MESSAGE_OF");
                break;
            default: {
                // 送信するメッセージ(スロットの値)がない、または不明の場合、
                // どんなメッセージを送りたいのか問い合わせる。
                this.emit(':ask', this.t("WHAT_REQUEST_MAIL"));
                return;
            }
        }
        var message = {
            from: "Alexa <" + email + ">",
            to: sendTo,
            subject: this.t("EMERGENCY_SUBJECT"),
            text: msg_text
        };
        this.emit('SendEmail', message, (err) => {
            self.attributes['stat'] = undefined;
            if (err) {
                self.emit(':tell', self.t("SEND_FAILED"));
            }
            else {
                let res = name + self.t("SEND_TO") + req_text + self.t("SEND_MAIL");
                self.emit(':tellWithCard', res, ALEXA_EMERGENCY_TEXT, res);
            }
        });
    },
    'SendEmail': function (message, callback) {
        //SMTPの接続(SSLを使用する場合。SSLを使用しない場合は"GeneralSmtpConfig"の方を使う)
        let smtp = nodemailer.createTransport(SSLSmtpConfig);
        // verify connection configuration
        smtp.verify((error, success) => {
            if (error) {
                console.log(error);
                callback(error);
            }
            console.log("Server is ready to take our messages", success);
            //メールの送信
            console.log("SendEmail:" + JSON.stringify(message));
            smtp.sendMail(message, (err, info) => {
                if (err) {
                    //送信失敗
                    console.log(err);
                    callback(err);
                }
                console.log("Message sent: " + info.messageId);
                //SMTPの切断
                smtp.close();
                callback(null, "success");
            });
        });
    },

    //「緊急連絡で連絡先を教えて」と呼び出された場合
    'SayMailAddress': function () {
        var name = this.attributes['name'];     // DynamoDBから取り出す
        var email = this.attributes['email'];   // DynamoDBから取り出す
        var msg = this.t("ADDRESS_IS") + this.t("SEE_THE_CARD");
        var txt = this.t("NAME_IS") + name + CR +
                  this.t("MAIL_ADDRESS_IS") + emergency_to + CR +
                  this.t("PHONE_NUMBER_IS") + twilio_call_to;
        this.attributes['stat'] = undefined;
        this.emit(':tellWithCard', msg, this.t("THIS_SKILL_NAME"), txt);
    },

    // 「緊急連絡で○○と電話」と呼び出された場合
    'PhoneCall': function (callback) {
        var session = this.event.session;		// セッション
        var intent = this.event.request.intent;	// インテント
        var req = intent.slots.Message.value;   // スロット{Message}の値
        var name = this.attributes['name'];     // DynamoDBから取り出す
        var email = this.attributes['email'];   // DynamoDBから取り出す
        var msg_text;
        var req_text;
        var self = this;

        if (!req) {
            let dialogState = this.event.request.dialogState;
            if (dialogState === "STARTED") {
                let updatedIntent = intent;
                //updatedIntent.slots.Message.value = undefined;
                //console.log("SendEmergencyMail: dialogState is STARTED.");
                this.emit(':delegate');
                //this.emit(':delegate', updatedIntent);    //初期値を設定したい場合
            }
            else if (dialogState !== "COMPLETED") {
                //console.log("SendEmergencyMail: dialogState is " + dialogState);
                this.emit(':delegate');
                //スロット{Message}について尋ねる
                //this.emit(':elicitSlot', "Message", this.t("WHAT_REQUEST_CALL"));
            }
        }
        this.attributes['stat'] = "PhoneCall";
        console.log("PhoneCall: req=",req);
        switch (req) {
            case "連絡":
            case "連絡して":
            case "連絡ください":
                msg_text = this.t("EMERGENCY_CONTACT");
                req_text = this.t("CONTACT_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "メール":
            case "メールして":
            case "メールください":
                msg_text = this.t("EMERGENCY_REQMAIL");
                req_text = this.t("REQMAIL_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "電話":
            case "電話して":
            case "電話ください":
                msg_text = this.t("EMERGENCY_CALL");
                req_text = this.t("PHONECALL_MESSAGE")+this.t("MESSAGE_OF");
                break;
            case "帰宅":
            case "帰宅して":
            case "帰って":
            case "帰ってきて":
            case "戻って":
            case "戻ってきて":
                msg_text = this.t("EMERGENCY_COMEBACK");
                req_text = this.t("BACK2HOME_MEESSAGE")+this.t("MESSAGE_OF");
                break;
            case "助け":
            case "助けて":
            case "ヘルプ":
                msg_text = this.t("EMERGENCY_HELP");
                req_text = this.t("REQHELP_MESSAGE")+this.t("MESSAGE_OF");
                break;
            default: {
                // 電話するメッセージ(スロットの値)がない、または不明の場合、
                // どんなメッセージを送りたいのか問い合わせる。
                this.emit(':ask', this.t("WHAT_REQUEST_CALL"));
                return;
            }
        }
        console.log("PhoneCall: message=" + req);
        console.log("PhoneCall: " + msg_text);
        this.emit('TwilioPhoneCall', msg_text, (err) => {
            self.attributes['stat'] = undefined;
            if (err) {
                self.emit(':tell', name + self.t("PHONECALL_FAILED"));
            }
            else {
                let res = name + self.t("SEND_TO") + req_text + self.t("PHONE_CALL");
                self.emit(':tellWithCard', res, ALEXA_EMERGENCY_TEXT, res);
            }
        });
    },
    'TwilioPhoneCall': function (message, callback) {
        var self = this;
        var https = require("https");
        var key = twilio_account;       // Twilio ACCOUNT SID
        var secret = twilio_authtoken;  // Twilio AUTHTOKEN
        var options = {
            hostname: "api.twilio.com",
            port: 443,
            path: "/2010-04-01/Accounts/" + key + "/Calls",
            method: "POST",
            auth: key + ":" + secret,
            headers : { "Content-Type" : "application/x-www-form-urlencoded" }
        };
        var req = https.request(options, (response) => {
            var res = response.statusCode;
            if (res == 200 || res <= 204)   {
                console.log("Twilio PhoneCall is OK!");
                callback(null, "success");      // 送信成功
            }
            else {
                response.setEncoding("utf8");
                response.on("data", (data) => {
                    console.log("res=" + res);
                    console.log(data);
                    callback(new Error("Invalid response"));
                });
            }
        }).on('error', (error) => {
            console.log(error);
            callback(error);
        });
        var rawXml =
            '<?xml version="1.0" encoding="UTF-8"?>' +
            '<Response>' +
            '  <Say voice="alice" language="ja-JP" loop="3">' + message + '</Say>' +
            '</Response>';
        var urlParam = encodeURIComponent('http://twimlets.com/echo?Twiml=' + encodeURIComponent(rawXml));

        console.log("TwilioPhoneCall:" + urlParam);
        req.write("To=" + twilio_call_to + "&");
        req.write("From=" + twilio_call_from + "&");
        req.write("Method=GET&");
        req.write("Url=" + urlParam);
        req.end();
    },

    'WelcomeIntent': function () {
        console.log("WelcomeIntent: start");
        let accessToken = this.event.session.user.accessToken;
        if (accessToken == undefined) {
            // トークンが空の場合は、ユーザーに許可を促す
            this.emit(':tellWithLinkAccountCard', this.t("AUTH_MESSAGE"));
            return;
        }
        // LWAからプロファイルを取得する
        var self = this;
        var https = require("https");
        var url = "https://api.amazon.com/user/profile?access_token=" + accessToken;
        https.get(url, (response) => {
            if (response.statusCode == 200) {
                // プロファイルから名前とメールアドレスを取り出す
                var body = "";
                response.setEncoding("utf8");
                response.on("data", function(chunk) {
                    // レスポンスボディーを変数に追加
                    body += chunk;
                });
                response.on("end", (res) => {
                    console.log(JSON.stringify(body));
                    let name = JSON.parse(body).name;
                    let email = JSON.parse(body).email;
                    self.attributes['name'] = name;
                    self.attributes['email'] = email;
                    console.log("Name=" + name + ", Email=" + email);
                    self.emit(':tell', name + self.t("WELCOME_MESSAGE") + self.t("ASK_TEST"));
                });
            }
            else {
                console.log("statusCode=" + response.statusCode);
                self.emit(':tell', self.t("NOPROF_MESSAGE"));
            }
        }).on('error', (error) => {
            console.log(error);
            self.emit(':tell', self.t("HTTPS_GET_ERROR"));
        });
    },

    'TestMail': function () {
        // テストメールを送信する
        var self = this;
        var intent = this.event.request.intent;	// インテント
        let name = this.attributes['name'];
        let email = this.attributes['email'];

        console.log("TestMail: name=" + name + ", sendTo=" + emergency_to);
        var message = {
            from: 'Alexa <' + email + '>',
            to: emergency_to,
            subject: "Test Mail",
            text: name + this.t("TESTMAIL_TEXT")
        };
        this.emit('SendEmail', message, (err) => {
            if (err) {
                self.emit(':tell', self.t("SEND_FAILED"));
            }
            else {
                self.emit(':tell', name + self.t("TESTMAIL_SUCCESS"));
            }
        });
    },
    'TestCall': function () {
        // テスト電話を発信する
        var self = this;
        var intent = this.event.request.intent;	// インテント
        let name = this.attributes['name'];

        console.log("TestCall: name=" + name + ", CallTo=" + twilio_call_to);
        this.emit('TwilioPhoneCall', name + this.t("TESTCALL_TEXT"), (err) => {
            if (err) {
                self.emit(':tell', name + self.t("PHONECALL_FAILED"));
            }
            else {
                self.emit(':tell', name + self.t("TESTCALL_SUCCESS"));
            }
        });
    },

    'AMAZON.HelpIntent': function () {
        const speechOutput = this.t("HELP_MESSAGE");
        const reprompt = this.t("HELP_MESSAGE");
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        this.attributes['stat'] = undefined;
        this.emit(':tell', this.t("CANCEL_MESSAGE"));
    },
    'AMAZON.StopIntent': function () {
        this.attributes['stat'] = undefined;
        this.emit(':tell', this.t("STOP_MESSAGE"));
    },
    'Unhandled': function () {
        console.log("Unhandled Intent: start");
        this.attributes['stat'] = undefined;
        this.emit("WelcomeIntent");
    }
};


// Lambda関数ハンドラー
// パラメタ
//  event: イベントデータ
//  context: ランタイム情報
//  callback: 呼び出し元へ情報を返すためのコールバック関数
exports.handler = function (event, context, callback) {
    var session = event.session;                    // セッション
    var apid = session.application.applicationId;   // アプリケーションID
    const alexa = Alexa.handler(event, context, callback);

    if (apid != APP_ID) {
        context.fail("Invalid Application ID"); // 緊急連絡アプリ以外からの呼び出し
        return;
    }
    alexa.appId = APP_ID;
    alexa.dynamoDBTableName = "UserInfoTable";  // session attributeを保存するテーブル
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.resources = languageStrings;  // 言語リソースを設定
    alexa.registerHandlers(handlers);   // ハンドラを設定
    alexa.execute();
};

7. 環境変数を設定する

 プログラムで取り込んでいる環境変数を以下のように設定します。

Emergency_APPID「緊急連絡」スキルのアプリケーションID
SMTP_HOST自分がメール送信に使用しているSMTPサーバーURL
SMTP_USER_NAMESMTPサーバーのログインユーザID
SMTP_PASSWORDSMTPサーバーのログインパスワード
EMERGENCY_SENDTO緊急連絡メールの送信先メールアドレス
TWILIO_ACCOUNT_SIDTwilioのアカウントSID(ライブクレデンシャル)
TWILIO_AUTH_TOKENTwilioのAUTH TOKEN(ライブクレデンシャル)
EMERGENCY_CALL_FROM_NUMBER発信元の電話番号(Twilioで取得した番号。+81で始め、先頭の0は取る)
EMERGENCY_CALL_TO_NUMBER緊急連絡の発信先電話番号(iPhoneの電話番号。+81で始め、先頭の0は取る)

 AWSコンソールの画面上はこんな感じ。

[続く]


< 過去の記事 [ 7月の 全てのカテゴリ リスト ] 新しい記事 >

2018 calendar
7月
1234567
891011121314
15161718192021
22232425262728
293031


掲示板
最新:08/15 17:19



管理人へMAIL

GsBlog was developed by GUSTAV, Copyright(C) 2003, Web Application Factory All Rights Reserved.