LoginSignup
3
2

More than 3 years have passed since last update.

Alexaを育てる!配列からデータベースへバージョンアップ

Posted at

まずは動かしてみた

前と何が変わったの?

実は前とAlexaが話している内容は変わりがない。
↓前回の記事
https://qiita.com/heihei15408697/items/6ce26775b26daed044e4

変えたのは作りの部分。前回の構成は↓
image.png

今回の構成は↓
image.png

Node.js上で配列にしていたものをデータベース使うように変えた!!

実際に苦労した点は・・・

下記のQiita記事にしてるのでそちら参照!
「Alexa、Lambdaと連携する方法を教えて」と聞いても、答えがよく分からなかったので自分でやったことメモ
LambdaからDynamoDBを使うときの権限エラー解消メモ
Lambda→DynamoDBが非同期処理と知らないやつがとった行動

これをやってみての感想

データベースを導入することで一気にシステムっぽくなる。
ちょっと動かすだけなら、前回までの配列でいいと思うけど、メンテナンスを考えると、やっぱりデータベースがいいんだろうな。
あと、僕個人としてはLambdaもDynamoDBも使ったことなかったので、すごく勉強になったし、楽しかった!!

Lambdaで動かしているNode.jsのソース

※Lambdaに取り込む際にnpmでインストールしたask-sdk-coreとi18nextをすべてzipにして取り込んでます。


// 使用するモジュールを定義
const Alexa = require('ask-sdk-core');
const i18n = require('i18next');

const {
  getRequestType,
  getIntentName,
  getSlotValue,
  getDialogState,
} = require('ask-sdk-core');

const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient()


const GetNewFactHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'LaunchRequest'
      || (request.type === 'IntentRequest'
        && request.intent.name === 'GetNewFactIntent');
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    const randomFact = requestAttributes.t('FACTS');
    const speakOutput = requestAttributes.t('GET_FACT_MESSAGE') + randomFact;

    return handlerInput.responseBuilder
      .speak(speakOutput)
      .withSimpleCard(requestAttributes.t('SKILL_NAME'), randomFact)
      .getResponse();
  },
};

const HelpHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('HELP_MESSAGE'))
      .reprompt(requestAttributes.t('HELP_REPROMPT'))
      .getResponse();
  },
};

const FallbackHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.FallbackIntent';
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('FALLBACK_MESSAGE'))
      .reprompt(requestAttributes.t('FALLBACK_REPROMPT'))
      .getResponse();
  },
};

const ExitHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && (request.intent.name === 'AMAZON.CancelIntent'
        || request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('STOP_MESSAGE'))
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
    return handlerInput.responseBuilder.getResponse();
  },
};

const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);
    console.log(`Error stack: ${error.stack}`);
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('ERROR_MESSAGE'))
      .reprompt(requestAttributes.t('ERROR_MESSAGE'))
      .getResponse();
  },
};

const LocalizationInterceptor = {
  process(handlerInput) {
    const localizationClient = i18n.init({
      lng: handlerInput.requestEnvelope.request.locale,
      resources: languageStrings,
      returnObjects: true
    });
    localizationClient.localize = function localize() {
      const args = arguments;
      const value = i18n.t(...args);
      if (Array.isArray(value)) {
        return value[Math.floor(Math.random() * value.length)];
      }
      return value;
    };
    const attributes = handlerInput.attributesManager.getRequestAttributes();
    attributes.t = function translate(...args) {
      return localizationClient.localize(...args);
    }
  }
};

const HelloWorldIntentHandler = {
  canHandle(handlerInput) {
    console.log('called HelloWorldIntentHandler.canHandle');
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
           && request.intent.name === 'HelloWorldIntent';
  },
  handle(handlerInput) {
    console.log('called HelloWorldIntentHandler.handle');
    return handlerInput.responseBuilder
            .speak("こんにちはインテントが呼ばれました。")
            .getResponse();
  }
};

// 今回の処理
const SearchSkillHandler = {
  canHandle(handlerInput) {
    console.log('called SearchSkillHandler.canHandle');
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
           && request.intent.name === 'SearchSkillIntent';
  },
  // DynamoDBからのデータ取得処理は非同期だったので、関数にasyncを設定
  async handle(handlerInput) {
    const skillsNameValue = getSlotValue(handlerInput.requestEnvelope, 'skills');
    console.log('called SearchSkillHandler.handle');

    const params = {
        TableName: 'skillmap',
        KeyConditionExpression: 'skill = :skill',
        ExpressionAttributeValues: {
            ':skill': `${skillsNameValue}`
        }
    };

    let skillsPersonName = '';
    // 実際にDynamoDBからデータ取得するところについては、await設定+promiseを設定
    await documentClient.query(params, (err, data) => {
        if (err) {
            console.log(JSON.stringify(err, null, 2));
        } else {
            console.log(JSON.stringify(data, null, 2));
            console.log('取得したもの name : ' + data.Items[0].name);
            console.log('取得したもの skill : ' + data.Items[0].skill);
            skillsPersonName = data.Items[0].name;
            console.log('取得したもの skillsPersonName1 : ' + skillsPersonName);
        }
    }).promise();

    console.log('取得したもの skillsPersonName2 : ' + skillsPersonName);

    return handlerInput.responseBuilder
            .speak(`${skillsNameValue}` + "" + `${skillsPersonName}` + "さんが詳しいです。")
            .getResponse();
  }
};

const skillBuilder = Alexa.SkillBuilders.custom();

// 使用するハンドラーをここで定義
exports.handler = skillBuilder
  .addRequestHandlers(
    GetNewFactHandler,
    SearchSkillHandler,
    HelloWorldIntentHandler,
    HelpHandler,
    ExitHandler,
    FallbackHandler,
    SessionEndedRequestHandler,
  )
  .addRequestInterceptors(LocalizationInterceptor)
  .addErrorHandlers(ErrorHandler)
  .withCustomUserAgent('sample/basic-fact/v2')
  .lambda();

const jpData = {
  translation: {
    SKILL_NAME: 'テスト',
  },
};

const languageStrings = {
  'ja': jpData,
};
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2