Serverless! 使用 AWS 開發 Slack Slash Commands

Serverless! 使用 AWS 開發 Slack Slash Commands

本文介紹如何使用 AWS Lambda & AWS API Gateway 搭建一個不需要伺服器的環境,提供 Slack Slash Commands 查詢豆瓣電影。

Serverless! 使用 AWS 開發 Slack Slash Commands

在 Slack 輸入 /movie 絕地救援,會顯示相關的電影資料。

這篇文章使用到的技術:

  • Slack Slash Commands

  • AWS Lambda

  • AWS API Gateway

  • 豆瓣電影 API

閱讀這篇文章需要具備什麼能力:

  • Node.js 的基本能力

  • Amazon Web Services 的基本操作

接下來我會逐步講解這些東西:

  1. Slack Slash Commands 的運作機制

  2. 建立一個簡單的 AWS Lambda function

  3. 建立一個簡單的 AWS API Gateway 執行 Lambda function

  4. 使用 Lambda 呼叫豆瓣電影 API

  5. 測試 AWS API Gateway

  6. 將 API Gateway endpoint 加入至 Slack Slash Command

Slack Slash Commands 的運作機制

Serverless! 使用 AWS 開發 Slack Slash Commands

當你在 Slack channel 輸入 /movie 權力的遊戲,Slack 會發出一個 content-type Header 設為 application/x-www-form-urlencoded 的 HTTP POST 請求,格式如下:

token=YOUR_SLASH_COMMAND_TOKEN
team_id=YOUR_TEAM_ID
team_domain=YOUR_TEAM_DOMAIN
channel_id=YOUR_CHANNEL_ID
channel_name=YOUR_CHANNEL_NAME
user_id=YOUR_USER_ID
user_name=YOUR_USER_NAME
command=/movie
text=權力的遊戲
response_url=YOUR_HOOK_URL

然後 Slack 需要收到的 JSON 回應格式如下(詳見 Attachments):

{
  "response_type": "in_channel",
  "attachments": [
    {
      "title": "權力的遊戲 第五季",
      "title_link": "http://movie.douban.com/subject/25826612/",
      "text": "Game of Thrones\n2015年",
      "fields": [
        {
          "title": "導演",
          "value": "Michael Slovis\n...",
          "short": true
        }, ...
      ],
      "thumb_url": "https://img1.doubanio.com/view/movie_poster_cover/ipst/public/p2230256732.jpg"
    }
  ]
}

responsetype: inchannel 表示 channel 內的使用者都可以看見,若只想給使用命令的本人看的話可以改成 ephemeral
Slack Slash Commands 有 3000 ms 的 response timeout 限制

建立一個簡單的 AWS Lambda function

Serverless! 使用 AWS 開發 Slack Slash Commands

  1. 前往 AWS Lambda

  2. 點選 Create a Lambda function

  3. 填寫 Configure function,例:slackFunction

  4. Runtime 選擇 Node.js

  5. Code entry type 選擇 Edit code inline,輸入簡單的測試程式碼:

exports.handler = function(event, context) {
  context.succeed("你好世界!");
};
  1. Role 使用 lambdabasicexecution

  2. 點選 Next 完成建立

  3. 點選 Test 測試結果:

你好世界!

建立一個簡單的 AWS API Gateway 執行 Lambda function

Serverless! 使用 AWS 開發 Slack Slash Commands

  1. 前往 AWS API Gateway

  2. 點選 New API

  3. 填寫 API name,例:Slack API

  4. 點選 Create API 完成新增

  5. 點選 Create Resource

  6. 填寫 Resource Name & Resource Path,例:movie

  7. 點選 /movie

  8. 點選 Create Method

  9. 選擇 POST

  10. Integration type 選擇 Lambda Function

11. 選擇 Lambda Region
12. 填寫 Lambda Function,例:slackFunction
13. 點選 Save 完成建立
14. 點選 Test 測試結果:

你好世界!

使用 Lambda 呼叫豆瓣電影 API

Serverless! 使用 AWS 開發 Slack Slash Commands

豆瓣電影搜索 API 的格式如下:

GET https://api.douban.com/v2/movie/search?q={text}

例:https://api.douban.com/v2/movie/search?q=權力的遊戲

{
  "count": 20,
  "start": 0,
  "total": 130,
  "subjects": [
    {
      "rating": {...},
      "genres": [...],
      "collect_count": 47770,
      "casts": [
        {...}, ...
      ],
      "title": "权力的游戏 第五季",
      "original_title": "Game of Thrones",
      "subtype": "tv",
      "directors": [
        {...}, ...
      ],
      "year": "2015",
      "images": {...},
      "alt": "http://movie.douban.com/subject/25826612/",
      "id": "25826612"
    }, ...
  ],
  "title": "搜索 \"權力的遊戲\" 的结果"
}

取代 Edit code inline,在本地建立一個 Node.js Lambda project:

$ npm init

加入一個支持 promise 的 XMLHttpRequest 庫:

$ npm install request-promise --save

新增 index.js:

var rp = require('request-promise');
exports.handler = function(event, context) {
  // 從傳進來的參數之中提取要搜尋的字串
  var text = event.text ? event.text.trim() : null;

  // 向豆瓣 API 發出 HTTP GET 請求
  rp('https://api.douban.com/v2/movie/search?q=' + text)
    .then(function(data) {
      // 回傳成功的結果
      context.succeed(data);
    }).catch(function(error) {
      // 回傳失敗的結果
      context.fail(error);
    });
};

將檔案壓縮成 lambda.zip:

./index.js
./node_module/request-promise
  1. 回到 AWS Lambda

  2. 點選之前建立的 function,例:slackFunction

  3. 將 Code entry type 從 Edit code inline 改為 Upload a .ZIP file

  4. 上傳 lambda.zip

  5. 點選 Actions > Configure test event,加入測試用的請求

{
  "text": "權力的遊戲"
}

點選 Test 測試豆瓣回應的結果:

{
  // ...
  "subjects": [
    {
      // ...
      "title": "权力的游戏 第五季",
      // ...
    }, ...
  ],
  "title": "搜索 \"權力的遊戲\" 的结果"
}

測試 AWS API Gateway

Serverless! 使用 AWS 開發 Slack Slash Commands

  1. 回到 AWS API Gateway

  2. 點選 /movie 的 POST method

  3. 點選 Test,並在 Request Body 加入測試用的請求:

{
  "text": "權力的遊戲"
}

點選 Test 測試 Lambda 回應的豆瓣結果:

{
  // ...
  "subjects": [
    {
      // ...
      "title": "权力的游戏 第五季",
      // ...
    }, ...
  ],
  "title": "搜索 \"權力的遊戲\" 的结果"
}

將 API Gateway endpoint 加入至 Slack Slash Command

Serverless! 使用 AWS 開發 Slack Slash Commands

因為 Slack 發送的請求 Header 格式是 application/www-form-urlencoded,所以需要在 AWS API Gateway 之中將它轉換成為 application/json 格式:

  1. 點選 /movie 的 POST method

  2. 點選 Method Execution

  3. 點選 Integration Request

  4. 點選 Mapping Templates

  5. 點選 Add mapping template

  6. Content-Type 填寫 application/www-form-urlencoded

  7. 將 Input passthrough 改成 Mapping template

  8. 貼上 Ryan Ray 提供的 template gist

  9. Save

最後,一定要記得把 API Gateway 部署給外部使用:

  1. 點選 Deploy API

  2. Deployment stage 選擇 New Stage

  3. 填寫 Stage name,例:development

  4. 點選 Deploy 完成發佈

  5. 然後會得到一個 Invoke URL 格式如下:

https://{hash}.execute-api.{region}.amazonaws.com/{stage}/movie

接下來的步驟,把 AWS API Gateway endpoint 整合進 Slack:

  1. 前往 https://YOURTEAMDOMAIN.slack.com/apps/manage

  2. 在 Search app directory 搜尋框輸入 Slash Commands 並進入

  3. 點選 Add Configuration

  4. 填寫 Choose a Command,例:/movie

  5. 點選 Add Slash Command Integration

  6. 填寫 URL,貼上 AWS API Gateway Invoke URL

  7. Method 選擇 POST

  8. 點選 Save Integration 完成新增

Serverless! 使用 AWS 開發 Slack Slash Commands

最後一個步驟,更新 Lambda function,讓它可以處理 Slack 的請求與回應:

var rp = require('request-promise');
exports.handler = function(event, context) {
  /**
   * event 會收到來自 AWS API Gateway 轉換過的 Slack POST JOSN
   * {
   *   token=YOUR_SLASH_COMMAND_TOKEN
   *   team_id=YOUR_TEAM_ID
   *   team_domain=YOUR_TEAM_DOMAIN
   *   channel_id=YOUR_CHANNEL_ID
   *   channel_name=YOUR_CHANNEL_NAME
   *   user_id=YOUR_USER_ID
   *   user_name=YOUR_USER_NAME
   *   command=/movie
   *   text=權力的遊戲
   *   response_url=YOUR_HOOK_URL
   * }
   */
  if(event.token !== 'YOUR_SLASH_COMMAND_TOKEN') {
    return context.fail('未經授權的請求');
  }
  var text = event.text ? event.text.trim() : null;

  // 向豆瓣 API 發出 HTTP GET 請求
  rp('https://api.douban.com/v2/movie/search?q=' + text)
    .then(function(data) {
      // 提取第一筆電影的結果
      var subject = data.subjects[0];
      // 將豆瓣 API 返回的結果包裝成 Slack 支持的格式
      var result = {
        "response_type": "in_channel",
        "attachments": [{
          "title": subject.title,
          "title_link": subject.alt,
          "text": subject.original_title+"\n"+subject.year+"年",
          "fields": [
            {
              "title": "導演",
              "value": subject.directors[0].name,
              "short": true
            }
          ],
          "thumb_url": subject.images.small
        }]
      };
      // 回傳結果
      context.succeed(result);
    }).catch(function(error) {
      // 回傳失敗的結果
      context.fail(error);
    });
};
  1. 重新壓縮 lambda.zip 然後上傳

  2. 在 Slack channel 輸入 /movie 測試結果

總結

相关推荐