├── 2020-10-04_114621.png ├── 2020-10-04_114626.png ├── 2020-10-04_114629.png ├── 2020-10-04_114632.png ├── 2020-10-04_114635.png ├── 2020-10-04_114637.png ├── 2020-10-04_114640.png ├── 2020-10-04_114643.png ├── 2020-10-04_114645.png ├── 2020-10-04_114648.png ├── 2020-10-04_130228.png ├── LICENSE ├── LineBot_AWS_Lambda_QuickEx.zip ├── README.md ├── get5_CloudWatchLogGroup.py ├── lambda_function.py ├── typl_lambda_make_rich_menu2.py ├── typl_template.py └── 使用 AWS Lambda 部署 LINE Bot.pdf /2020-10-04_114621.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114621.png -------------------------------------------------------------------------------- /2020-10-04_114626.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114626.png -------------------------------------------------------------------------------- /2020-10-04_114629.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114629.png -------------------------------------------------------------------------------- /2020-10-04_114632.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114632.png -------------------------------------------------------------------------------- /2020-10-04_114635.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114635.png -------------------------------------------------------------------------------- /2020-10-04_114637.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114637.png -------------------------------------------------------------------------------- /2020-10-04_114640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114640.png -------------------------------------------------------------------------------- /2020-10-04_114643.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114643.png -------------------------------------------------------------------------------- /2020-10-04_114645.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114645.png -------------------------------------------------------------------------------- /2020-10-04_114648.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_114648.png -------------------------------------------------------------------------------- /2020-10-04_130228.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/2020-10-04_130228.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 spectreConstantine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LineBot_AWS_Lambda_QuickEx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/LineBot_AWS_Lambda_QuickEx.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 使用 AWS Lambda 快速部署 LINE Bot 2 | ( 搭配 AWS API Gateway [並隨附範例 LineBot_AWS_Lambda_QuickEx.zip](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/raw/main/LineBot_AWS_Lambda_QuickEx.zip "範例LineBot_AWS_Lambda_QuickEx.zip") ) 3 | -[本文 PowerPoint(pdf) 版](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/raw/main/%E4%BD%BF%E7%94%A8%20AWS%20Lambda%20%E9%83%A8%E7%BD%B2%20LINE%20Bot.pdf) 4 | 5 | ## 什麼是 AWS Lambda? 用它來部署 Line Bot 有什麼不同和好處? 6 | 7 | ### 讓你專注在應用程式開發,不用架設伺服器、設定監控、網路安全性及建設基礎架構 8 | 9 | - 它就像是 Python 中的一個 Lambda Function 。這是 Lambda 這個服務的簡單概念。 10 | - 應用程式即是一個 Function,**預設是 3 秒鐘**,最長可執行 15 分鐘。 11 | - 應用程式執行的前與後,服務為停止狀態。 12 | - **把應用程式寫成可執行的** Function **,其他由雲服務代管。這是** Function As a Service **的簡單概念。** 13 | - 雲服務提供Function的執行環境(Runtime),執行你包裝好的Function 應用程式。 14 | - 應用程式的Function 只存在執行階段(Runtime),執行環境終止也代表所有資料不會留存。 15 | - **不用執行或架設任何伺服器,應用程式只會使用執行階段的運算時間。這是** Serverless **的簡單概念。** 16 | - 不用擔心或管理任何伺服器。由雲服務負責所有的其他工作;除了你開發的應用程式。 17 | - 使用雲服務提供的Free-Tier 方案。在方案提供的限額內永久免費。 18 | 19 | ## 用 Lambda 來部署 Line Bot 我需要具備什麼條件? 20 | 21 | ### 註冊的 LINE 與 AWS 服務帳號,以及與你要開發的應用程式相關的知識及其商業邏輯 22 | 23 | - 註冊的 AWS 雲服務帳號 24 | - 可以註冊AWS Free-Tier 方案: https://aws.amazon.com/tw/free/ ,提供一年有限度的免費服務。 25 | - 註冊帳號需要有可收到信的email 帳號。需要提供有效的信用卡卡號(驗證USD$1 但不會收費)。 26 | - 註冊帳號過程為全自動化,過程中請確認輸入正確的資料,註冊完後服務即可使用。 27 | - **註冊的** LINE **服務帳號** 28 | - 在LINE 的開發網站建立一個LINE Provider 和Message API Channel。 29 | - 啟用LINE Message API的webhook。稍後會說明細節。 30 | - **對** Python **程式語言或其他** AWS Lambda **支援的程式語言有基本的認識與瞭解** 31 | - 參考雲服務商及LINE 服務商提供的SDK範例,協助你開發LINE Bot 應用程式。 32 | 33 | ## 有什麼注意事項? 34 | 35 | ### 注意安全性的控管,並關注 AWS Free-Tier 的免費限額! 36 | 37 | - 安全性 38 | - 雲服務廠商專注於基礎架構及服務本身的安全性,使用者需自行控管應用程式本身的安全性。 39 | - 你使用的email 帳號與密碼及註冊帳號的密碼等安全性保護也很重要。 40 | - 如果疏於管理安全性,可能會導致服務被惡意使用造成**鉅額的使用費用**! 41 | - **查看** AWS Free-Tier **的細節** 42 | - AWS 的Free-Tier 免費方案,提供 AWS Lambda 每月 **1 百萬個**免費請求(request)。 43 | - 如果需搭配使用其他雲服務儲存AWS Lambda 執行運算完的資料,需注意相關的費用與成本。 44 | - **設定** AWS **費用的郵件提醒通知並定期查看** AWS **管理頁面的費用細節** 45 | - 即時收到費用通知,瞭解使用狀況。並在需要時與雲服務支援人員保持連繫以瞭解細節。 46 | 47 | ## 架構簡圖概觀 48 | 49 | - ![架構圖概觀](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114621.png) 50 | 51 | ## 步驟 1. 登入 AWS 管理界面建立 Lambda Function 52 | 53 | ### 預設的帳號為 Root Account 有最高的權限。可參考 AWS 文件啟用雙階段認證! 54 | 55 | - 使用瀏覽器開啟 https://console.aws.amazon.com/ 56 | - 使用註冊的email 帳號和密碼登入,或使用另外建立的 Administrative Access 的 IAM 帳號登入。細節請見後面**其他參考資訊**。 57 | - 登入後可以在右上角先選擇區域。可以使用 東京(ap-northeast-1) 或 香港(ap-east-1) 離台灣較近。 58 | - 需注意 香港(ap-east-1) 區域需在登入後額外啟用該區域。請參考AWS 網站的說明。 59 | - 點取左上角的 **Services**,點選運算類別的 **Lambda** 服務開啟 **Lambda** 服務界面。按一下建立函式 60 | - 選最左邊的從頭開始撰寫,為函式命名。選Python 3.7 為執行時間,其他預設值不變,選 **建立函式** 。 61 | 62 | ![步驟1](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114626.png) 63 | 64 | - 在新增觸發條件選按一下,選 **API Gateway**,**Create API**,**HTTP API**,安全性為開啟。按一下新增。 65 | 66 | ![步驟1](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114629.png) 67 | 68 | - 點選 API Gateway 下方詳細資訊,將 API 終端節點旁的網址填入 LINE Bot 的Webhook(參考步驟3) 69 | 70 | ![步驟1](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114632.png) 71 | 72 | ## 步驟 2. 打包應用程式及相依套件上傳到 AWS Lambda 73 | 74 | ### 為簡化細節,此文件提供基礎的範例套件。僅需上傳一次。爾後直接在界面編寫程式 75 | 76 | - 點畫面中間的 Lambda 圖示,在右側的操作上點一下選取上傳 .zip 檔案。 77 | - 預設的函式左邊只有一個根目錄和一個 .py 檔案。點選 .py 檔案後右側即是該檔案的程式碼內容。 78 | - 按上傳後選取隨附的 LINE Bot 及相關套件[範例LineBot_AWS_Lambda_QuickEx.zip](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/raw/main/LineBot_AWS_Lambda_QuickEx.zip "範例LineBot_AWS_Lambda_QuickEx.zip"),然後按儲存。 79 | 80 | ![步驟2](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114635.png) 81 | 82 | - 上傳後的程式及套件會覆蓋原有的預設執行環境。如果已先在剛才的環境撰寫程式需先匯出備份。 83 | - 如果上傳超過 3MB 的.zip 會有訊息提醒你 Lambda 編輯畫面無法使用。需使用 AWS CLI 等工具。 84 | - 將 **LINE Bot** 的 **Channel Access Token** 及 **Channel Secret** 新增到程式碼下方的環境變數 ( **參考步驟** 3) 85 | - 由於安全性的考量,這裡採用環境變數的方式儲存這兩個設定,再從程式碼裡用os.getenv去存取。 86 | 87 | ![步驟2](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114637.png) 88 | 89 | - 環境變數預設是自動使用 AWS KMS 加密的。在免費方案中採用 KMS 有提供有限的免費用量。 90 | - 可以直接線上在中間畫面中修改你的 **LINE Bot** 程式。修改後按一下 **Deploy** 即可部署最後修訂的內容。 91 | 92 | ![其他](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114645.png) 93 | 94 | - 在 AWS Lambda 裡也有提供發行新版本或匯出函式等多樣性的功能。請參考 AWS 網站取更多資訊。 95 | 96 | ## 步驟 3. 修改 LINE Message API Webhook 指向 AWS API Gateway 97 | 98 | ### 你的 Line Bot 應用程式已部署完成。讓 LINE Server 指向你的 Line Bot 產生關聯 99 | 100 | - 開啟 LINE 的開發頁面 https://developers.line.biz/console/ 並登入你的 LINE 帳號建立 Provider 101 | - 建立Provider為建立Line Bot 的第一個步驟。建立完後選擇該Provider後點選建立Channel。 102 | - **在建立** Channel **裡選擇建立** Message API **,並啟用** Usewebhook 103 | - 建立 Message API 的後,點選 Basic Settings 設定。在頁面中找到 Channel secret 的值。 104 | - 在下一個 Messaging API 頁籤中找到 Channel access token,點Issue,取得 token 的值。 105 | - 回到 Basic Settings 按一下LINE Official Account Manager 的連結,選取左邊 Message API。 106 | - 在 Webhook 網址中填入步驟 1 中的 API Gateway 下方詳細資料的 API 終端節點。按儲存。 107 | 108 | ![步驟3](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114640.png) 109 | 110 | - 回LINE Developers 頁面Messaging API 頁籤,找到Webhook settings,將Use webhook 啟用。 111 | 112 | ![步驟3](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114643.png) 113 | 114 | - **完成啦** ~ **這樣你的** LINE Bot **就可以使用囉**! **是不是很容易呢**? 115 | - 在 Messaging API 頁籤中找 Bot information ,打開手機的LINE 掃描 QR Code 加入好友即可對話。可以參考文末擷圖。 116 | 117 | ## 其他參考資訊 118 | 119 | #### 如何打包套件? 120 | 121 | - 建立一個資料夾。使用 pip install **Package** -t .。**然後進入資料夾內**,全選,右鍵,傳送到,壓縮的資料夾。 122 | - https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/python-package.html 123 | #### 開發 **LINE Bot** 時碰到錯誤或問題如何除錯 (Debug/Troubleshooting)? 124 | - 所有的記錄包含Print() 函式的內容會包含在CloudWatch的Log Group 裡。請參考後面的螢幕擷圖。 125 | 126 | ![其他](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_114648.png) 127 | 128 | #### 建立您的第一個 IAM **管理員用戶和群組** 129 | - https://docs.aws.amazon.com/zh_tw/IAM/latest/UserGuide/getting-started_create-admin-group.html 130 | #### AWS Lambda **常見問答集** 131 | - https://aws.amazon.com/tw/lambda/faqs/ 132 | #### LINEBot SDK for Python 133 | - https://github.com/line/line-bot-sdk-python 134 | #### AWS Lambda Deployment Package in Python 135 | - https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/python-package.html 136 | #### AWS Lambda Runtimes 137 | - https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/lambda-runtimes.html 138 | #### LINE Flex Message Simulator 139 | - https://developers.line.biz/flex-simulator/ 140 | #### JSON Formatter & Validator 141 | - https://jsonformatter.curiousconcept.com/ 142 | #### Lambda Deployment Packages with Docker 143 | - https://blog.quiltdata.com/an-easier-way-to-build-lambda-deployment-packages-with-docker-instead-of-ec2-9050cd486ba8 144 | #### 歡迎試用一下我用相同的方法建立的 桃園市圖書館 Line Bot: @082gynfz 145 | 146 | - 除了使用上述的 AWS Lambda/AWS API Gateway, 也搭配了 AWS S3/DynamoDb/SQS 及 EC2/MongoDB 等服務。 147 | ![桃園市圖書館](https://github.com/spectreConstantine/LINE-Bot-AWS-Lambda-Python/blob/main/2020-10-04_130228.png) 148 | 149 | 150 | 151 | 152 | ``` 153 | 文章by alvin.constantine@outlook.com © 2020 All rights reserved 154 | ``` 155 | -------------------------------------------------------------------------------- /get5_CloudWatchLogGroup.py: -------------------------------------------------------------------------------- 1 | # --- The MIT License (MIT) Copyright (c) alvinconstantine(alvin.constantine@outlook.com), Tue Oct 6 14:15pm 2020 --- 2 | 3 | import boto3 4 | import json 5 | import prettytable as pt 6 | 7 | region_name = 'ap-east-1' 8 | client = boto3.client('logs', region_name=region_name) 9 | logGroupName = '/aws/lambda/typl-lambda-linebot' 10 | 11 | log_streams_response = client.describe_log_streams(logGroupName=logGroupName, orderBy='LastEventTime', descending=True, limit=5) 12 | for no_, i in enumerate(log_streams_response['logStreams']): 13 | logStreamName = i['logStreamName'] 14 | log_events_response = client.get_log_events( 15 | logGroupName=logGroupName, 16 | logStreamName=logStreamName, 17 | startFromHead=True 18 | ) 19 | filenameprefix = logGroupName.split('/')[-1] 20 | filenamepostfix = logStreamName.split(']')[-1] 21 | filename = ('%d_' % (no_+1)) + filenameprefix+'.'+filenamepostfix 22 | counter = 1 23 | cloudWatchTable = pt.PrettyTable(encoding='utf-8') 24 | cloudWatchTable.field_names = ['順序', '記錄類型', '日期', '時間', '內容'] 25 | cloudWatchTable.align['內容'] = 'l' 26 | for i in log_events_response['events']: 27 | if i['message'].startswith('['): 28 | iMessage = i['message'].strip('') 29 | sMessage = iMessage.split('\t') 30 | if len(sMessage) != 1: 31 | eMessage = sMessage.pop(2) 32 | tMessage = sMessage.pop(1) 33 | dMessage = tMessage.split('T') 34 | dMessage[-1] = dMessage[-1].replace('Z', '') 35 | sMessage.insert(1, dMessage[-1]) 36 | sMessage.insert(1, dMessage[0]) 37 | else: 38 | capturedMessage = sMessage.copy() 39 | errQuote = sMessage[0][sMessage[0].find('['):sMessage[0].find(']')+1] 40 | sMessage[0] = sMessage[0].replace(errQuote+' ', '') 41 | sMessage = [errQuote, '', '', sMessage[0].replace('\r', '\n').replace('\xa0', ' ')] 42 | cloudWatchTable.add_row([counter]+sMessage) 43 | counter += 1 44 | elif i['message'].startswith('START RequestId'): 45 | if counter>1: 46 | cloudWatchTable.add_row(['----', '-------', '---------', '------------', '-'*160]) 47 | print('=== 正在儲存 CloudWatch 記錄 %s 檔案 ===' % filename) 48 | with open(filename+'.txt', 'w', encoding =' utf-8-sig') as f: 49 | f.write(str(cloudWatchTable)) 50 | -------------------------------------------------------------------------------- /lambda_function.py: -------------------------------------------------------------------------------- 1 | from linebot import (LineBotApi, WebhookHandler) # --- The MIT License (MIT) Copyright (c) alvinconstantine(alvin.constantine@outlook.com), Tue Oct 6 14:14pm 2020 --- 2 | from linebot.exceptions import (LineBotApiError, InvalidSignatureError) 3 | from linebot.models import (MessageEvent, TextMessage, TextSendMessage, SourceUser, SourceGroup, SourceRoom, TemplateSendMessage, ConfirmTemplate, MessageAction, ButtonsTemplate, 4 | ImageCarouselTemplate, ImageCarouselColumn, URIAction, PostbackAction, DatetimePickerAction, CameraAction, CameraRollAction, LocationAction, CarouselTemplate, CarouselColumn, 5 | PostbackEvent, StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage, ImageMessage, VideoMessage, AudioMessage, FileMessage, UnfollowEvent, FollowEvent, 6 | JoinEvent, LeaveEvent, BeaconEvent, MemberJoinedEvent, MemberLeftEvent, FlexSendMessage, BubbleContainer, ImageComponent, BoxComponent, TextComponent, SpacerComponent, 7 | IconComponent, ButtonComponent, SeparatorComponent, QuickReply, QuickReplyButton, ImageSendMessage) 8 | import requests, traceback, logging, boto3, json, sys, os 9 | from botocore.exceptions import ClientError 10 | from bs4 import BeautifulSoup 11 | 12 | # === 將這個 Lambda 設定的環境變數 (environment variable) 輸出作為參考 ] === 13 | logger = logging.getLogger() 14 | logger.setLevel(logging.INFO) 15 | channel_secret = os.getenv('LINE_CHANNEL_SECRET', None) 16 | channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None) 17 | if not channel_secret or not channel_access_token: 18 | logger.error('需要在 Lambda 的環境變數 (Environment variables) 裡新增 LINE_CHANNEL_SECRET 和 LINE_CHANNEL_ACCESS_TOKEN 作為環境變數。') 19 | sys.exit(1) 20 | line_bot_api = LineBotApi(channel_access_token) 21 | handler = WebhookHandler(channel_secret) 22 | logger.info(os.environ) 23 | 24 | # ===[ 定義你的函式 ] === 25 | def get_userOperations(userId): 26 | return None 27 | 28 | # === [ 定義回覆使用者輸入的文字訊息 - 依據使用者狀態,回傳組成 LINE 的 Template 元素 ] === 29 | def compose_textReplyMessage(userId, userOperations, messageText): 30 | return TextSendMessage(text='好的!已收到您的文字 %s!' % messageText) 31 | 32 | # === [ 定義回覆使用者與程式使用者界面互動時回傳結果後的訊息 - 依據使用者狀態,回傳組成 LINE 的 Template 元素 ] === 33 | def compose_postbackReplyMessage(userId, userOperations, messageData): 34 | return TextSendMessage(text='好的!已收到您的動作 %s!' % messageData) 35 | 36 | # === [ 主程式 - 這裡是主要的 lambda_handler 程式進入點區段,相當於 main() ]==================================================================================== 37 | def lambda_handler(event, context): 38 | 39 | # ==== [ 處理文字 TextMessage 訊息程式區段 ] === 40 | @handler.add(MessageEvent, message=TextMessage) 41 | def handle_text_message(event): 42 | userId = event.source.user_id 43 | messageText = event.message.text 44 | userOperations = get_userOperations(userId) 45 | logger.info('收到 MessageEvent 事件 | 使用者 %s 輸入了 [%s] 內容' % (userId, messageText)) 46 | line_bot_api.reply_message(event.reply_token, compose_textReplyMessage(userId, userOperations, messageText)) 47 | 48 | # ==== [ 處理使用者按下相關按鈕回應後的後續動作 PostbackEvent 程式區段 ] === 49 | @handler.add(PostbackEvent) 50 | def handle_postback(event): 51 | userId = event.source.user_id 52 | messageData = json.loads(event.postback.data) 53 | userOperations = get_userOperations(userId) 54 | logger.info('收到 PostbackEvent 事件 | 使用者 %s' % userId) 55 | line_bot_api.reply_message(event.reply_token, compose_postbackReplyMessage(userId, userOperations, messageData)) 56 | 57 | # ==== [ 處理追縱 FollowEvent 的程式區段 ] === 58 | @handler.add(FollowEvent) 59 | def handle_follow(event): 60 | userId = event.source.user_id 61 | logger.info('收到 FollowEvent 事件 | 使用者 %s' % userId) 62 | line_bot_api.reply_message(event.reply_token, TextSendMessage(text='歡迎您的加入 !')) 63 | 64 | # === [ 這裡才是 lambda_handler 主程式 ]===================================================================================== 65 | try: 66 | signature = event['headers']['x-line-signature'] # === 取得 event (事件) x-line-signature 標頭值 (header value) 67 | body = event['body'] # === 取得事件本文內容(body) 68 | # eventheadershost = event['headers']['host'] 69 | handler.handle(body, signature) # === 處理 webhook 事件本文內容(body) 70 | 71 | # === [ 發生錯誤的簽章內容(InvalidSignatureError)的程式區段 ] === 72 | except InvalidSignatureError: 73 | return { 74 | 'statusCode': 400, 75 | 'body': json.dumps('InvalidSignature') } 76 | 77 | # === [ 發生錯誤的LineBotApi內容(LineBotApiError)的程式區段 ] === 78 | except LineBotApiError as e: 79 | logger.error('呼叫 LINE Messaging API 時發生意外錯誤: %s' % e.message) 80 | for m in e.error.details: 81 | logger.error('-- %s: %s' % (m.property, m.message)) 82 | return { 83 | 'statusCode': 400, 84 | 'body': json.dumps(traceback.format_exc()) } 85 | 86 | # === [ 沒有錯誤(回應200 OK)的程式區段 ] === 87 | return { 88 | 'statusCode': 200, 89 | 'body': json.dumps('OK') } 90 | -------------------------------------------------------------------------------- /typl_lambda_make_rich_menu2.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | import logging 3 | import json 4 | import os 5 | import sys 6 | import json 7 | import uuid 8 | import boto3 9 | import errno 10 | import logging 11 | import datetime 12 | import tempfile 13 | import traceback 14 | from errno import EEXIST 15 | from linebot import (LineBotApi, WebhookHandler) 16 | from linebot.exceptions import (LineBotApiError, InvalidSignatureError) 17 | from linebot.models import ( 18 | MessageEvent, TextMessage, TextSendMessage, 19 | SourceUser, SourceGroup, SourceRoom, 20 | TemplateSendMessage, ConfirmTemplate, MessageAction, 21 | ButtonsTemplate, ImageCarouselTemplate, ImageCarouselColumn, URIAction, 22 | PostbackAction, DatetimePickerAction, 23 | CameraAction, CameraRollAction, LocationAction, 24 | CarouselTemplate, CarouselColumn, PostbackEvent, 25 | StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage, 26 | ImageMessage, VideoMessage, AudioMessage, FileMessage, 27 | UnfollowEvent, FollowEvent, JoinEvent, LeaveEvent, BeaconEvent, 28 | MemberJoinedEvent, MemberLeftEvent, 29 | FlexSendMessage, BubbleContainer, ImageComponent, BoxComponent, 30 | TextComponent, SpacerComponent, IconComponent, ButtonComponent, 31 | SeparatorComponent, QuickReply, QuickReplyButton, 32 | ImageSendMessage, RichMenu) 33 | 34 | 35 | line_bot_api = LineBotApi('***') 36 | handler = WebhookHandler('***') 37 | rich_menu_initial = { 38 | "size":{ 39 | "width":2500, 40 | "height":1686 41 | }, 42 | "selected":True, 43 | "name":"請點選選單上的服務", 44 | "chatBarText":"請點選選單上的服務", 45 | "areas":[ 46 | { 47 | "bounds":{ 48 | "x":179, 49 | "y":218, 50 | "width":587, 51 | "height":558 52 | }, 53 | "action":{ 54 | "type":"postback", 55 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 1, 0) 56 | } 57 | }, 58 | { 59 | "bounds":{ 60 | "x":988, 61 | "y":224, 62 | "width":563, 63 | "height":547 64 | }, 65 | "action":{ 66 | "type":"postback", 67 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 2, 0) 68 | } 69 | }, 70 | { 71 | "bounds":{ 72 | "x":1754, 73 | "y":233, 74 | "width":556, 75 | "height":538 76 | }, 77 | "action":{ 78 | "type":"postback", 79 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 3, 0) 80 | } 81 | }, 82 | { 83 | "bounds":{ 84 | "x":186, 85 | "y":842, 86 | "width":579, 87 | "height":519 88 | }, 89 | "action":{ 90 | "type":"postback", 91 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 4, 0) 92 | } 93 | }, 94 | { 95 | "bounds":{ 96 | "x":989, 97 | "y":842, 98 | "width":562, 99 | "height":508 100 | }, 101 | "action":{ 102 | "type":"postback", 103 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 5, 0) 104 | } 105 | }, 106 | { 107 | "bounds":{ 108 | "x":1763, 109 | "y":848, 110 | "width":545, 111 | "height":520 112 | }, 113 | "action":{ 114 | "type":"postback", 115 | "data":'{"kind":"menu","level":"%d","choice":"%d","item":"%d"}' % (0, 6, 0) 116 | } 117 | } 118 | ] 119 | } 120 | rich_menu_list = line_bot_api.get_rich_menu_list() 121 | if rich_menu_list: 122 | print('rich_menu_list', rich_menu_list) 123 | line_bot_api.delete_rich_menu(line_bot_api.get_default_rich_menu()) 124 | else: 125 | myRichMenu_Id = line_bot_api.create_rich_menu(rich_menu=RichMenu.new_from_json_dict(rich_menu_initial)) 126 | print('myRichMenu_Id:', myRichMenu_Id) 127 | uploadImageFile=open('D:/rich_menu_forallusers.jpg', 'rb') 128 | setImageResponse = line_bot_api.set_rich_menu_image(myRichMenu_Id, 'image/jpeg', uploadImageFile) 129 | if setImageResponse: 130 | print('setImageResponse:', setImageResponse) 131 | setImageResponse = line_bot_api.set_default_rich_menu(myRichMenu_Id) 132 | default_rich_menu = line_bot_api.get_default_rich_menu() 133 | print('default_rich_menu', default_rich_menu) 134 | -------------------------------------------------------------------------------- /typl_template.py: -------------------------------------------------------------------------------- 1 | from linebot import (LineBotApi, WebhookHandler) 2 | from linebot.exceptions import (LineBotApiError, InvalidSignatureError) 3 | from linebot.models import (MessageEvent, TextMessage, TextSendMessage, SourceUser, SourceGroup, SourceRoom, TemplateSendMessage, ConfirmTemplate, MessageAction, ButtonsTemplate, 4 | ImageCarouselTemplate, ImageCarouselColumn, URIAction, PostbackAction, DatetimePickerAction, CameraAction, CameraRollAction, LocationAction, CarouselTemplate, CarouselColumn, 5 | PostbackEvent, StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage, ImageMessage, VideoMessage, AudioMessage, FileMessage, UnfollowEvent, FollowEvent, 6 | JoinEvent, LeaveEvent, BeaconEvent, MemberJoinedEvent, MemberLeftEvent, FlexSendMessage, BubbleContainer, ImageComponent, BoxComponent, TextComponent, SpacerComponent, 7 | IconComponent, ButtonComponent, SeparatorComponent, QuickReply, QuickReplyButton, ImageSendMessage) 8 | from botocore.exceptions import ClientError 9 | from pymongo import MongoClient, ASCENDING, DESCENDING 10 | from bs4.element import NavigableString 11 | from bs4 import BeautifulSoup 12 | from decimal import Decimal 13 | from errno import EEXIST 14 | import typl_elements as typl 15 | import traceback 16 | import datetime 17 | import requests 18 | import logging 19 | import errno 20 | import boto3 21 | import json 22 | import json 23 | import sys 24 | import os 25 | 26 | # === 將這個 Lambda 設定的環境變數 (environment variable) 輸出作為參考 ] === 27 | logger = logging.getLogger() 28 | logger.setLevel(logging.INFO) # 設定記錄等級為 INFO DEBUG 29 | 30 | # === 在環境變數取得 LineBot Message API 的 channel_secret 和 channel_access_token 設定 LineBot Message API 的變數 line_bot_api 和 handler ] === 31 | channel_secret = os.getenv('LINE_CHANNEL_SECRET', None) 32 | channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None) 33 | line_bot_api = LineBotApi(channel_access_token) 34 | handler = WebhookHandler(channel_secret) 35 | 36 | # === 設定 AWS EC2(MongoDB)/DynamoDB/SQS 的變數和取得環境變數 ] === 37 | aws_access_key_id = os.getenv('ACCESS_KEY') 38 | aws_secret_access_key = os.getenv('SECRET_KEY') 39 | ec2instance_id = os.getenv('EC2INSTANCE_ID') 40 | region_name = os.getenv('REGION_NAME') 41 | sqsclient = boto3.client('sqs', region_name=region_name) 42 | dynamodb = boto3.resource('dynamodb', region_name='ap-east-1') 43 | dynamodbTb_userop = dynamodb.Table('typl-userop') 44 | mongo_user = os.getenv('MONGO_USER') 45 | mongo_password = os.getenv('MONGO_PASSWORD') 46 | logger.debug(os.environ) 47 | 48 | # ===[ 定義 my_messageLogger 函式 ] === 49 | def my_messageLogger(loggingMessage): 50 | pass 51 | 52 | # === [ 從桃園市圖書館的網站抓取館藏細節回傳所有館藏的字典清單 ] === 53 | def get_bookDetailPage(name, bookid): 54 | return books 55 | 56 | # === [ 從桃園市圖書館的網站抓取分館開放時間細節顯示在 LINE 裡 ] === 57 | def get_libOpeningPage(libraryCode): 58 | return books 59 | 60 | # === [ 從桃園市圖書館的網站抓取分館最新資訊顯示在 LINE 裡 ] === 61 | def get_libNewsPage(library, thisYear): 62 | return allNews, newsPage 63 | 64 | # === [ 從桃園市圖書館的網站抓取借閱清單回傳清單 ] === 65 | def get_bookBorrowingPage(user, password): 66 | return username, books 67 | 68 | # === [ 從桃園市圖書館的網站抓取預約清單回傳清單 ] === 69 | def get_bookExpectingPage(user, password): 70 | return username, books 71 | 72 | # === [ 第八步驟 - 如果前面使用 purgeSQS_userQueue 時發生像是 Only one PurgeQueue operation on is allowed every 60 seconds 的錯誤這個函式會被呼叫做為替代方案 ] === 73 | def failsafeSQS_batchDeletion(books_left, sqsQurl_for_user): 74 | pass 75 | 76 | # === [ 第七步驟 - 清空 Purge 使用者的 SQS 裡的 Queue 內容,如果無法呼叫 purge_queue 則改用 failsafeSQS_batchDeletion 做為備案 ] === 77 | def purgeSQS_userQueue(userId, books_left, onerror): 78 | pass 79 | 80 | # ===[ 第六步驟 - 從 SQS Queue 佇列裡取回書籍清單 ] === 81 | def reply_CarouselFlexReceiveQ(function_id, userId, books, books_total): 82 | return FlexSendMessage(alt_text="正在顯示書本清單", contents={"type":"carousel", "contents":carouselBubbles}) 83 | 84 | # === [ 第五步驟 - 從 SQS 取得使用者的 Queue 的 QurueUrl ] === 85 | def getSQS_userQueueUrl(userId): 86 | 87 | # === [ 第四步驟(3) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 88 | def reply_rankCarouselFlexMongo(mongoBooks, books_total): 89 | return FlexSendMessage(alt_text="正在顯示書本清單", contents={"type":"carousel", "contents":carouselBubbles}) 90 | 91 | 92 | # === [ 第四步驟(2.4) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 93 | def reply_cartCarouselFlexMongo_4(mongoBooks, currentLibrary, currentClassifyNos): 94 | return FlexSendMessage(alt_text="正在待借清單清單", contents={"type":"carousel", "contents":carouselBubbles}) 95 | 96 | # === [ 第四步驟(2.3) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 97 | def reply_cartCarouselFlexMongo_3(mongoBooks, currentLibrary): 98 | return FlexSendMessage(alt_text="正在待借清單清單", contents={"type":"carousel", "contents":carouselBubbles}) 99 | 100 | # === [ 第四步驟(2.2) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 101 | def reply_cartCarouselFlexMongo_2(mongoBooks): 102 | return FlexSendMessage(alt_text="正在待借清單清單", contents={"type":"carousel", "contents":carouselBubbles}) 103 | 104 | # === [ 第四步驟(2.1) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 105 | def reply_cartCarouselFlexMongo_1(mongoBooks): 106 | return FlexSendMessage(alt_text="正在待借清單清單", contents={"type":"carousel", "contents":carouselBubbles}) 107 | 108 | # === [ 第四步驟(1) - 從 MongoDB 取回的書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 109 | def reply_bookCarouselFlexMongo(mongoBooks, books_total): 110 | return FlexSendMessage(alt_text="正在顯示書本清單", contents={"type":"carousel", "contents":carouselBubbles}) 111 | 112 | # === [ 第三步驟 - 將未讀取的書籍清單傳到 SQS Queue 裡的主程式區段 ] === 113 | def sendQ_MongoBooks(userId, mongoBooks): 114 | # === [ 依據屬性的鍵值決定 SQS Queue 裡的屬性類別 ] === 115 | def getQ_attributeType(attributeKey, attribValue): 116 | return {'DataType': 'Number', 'StringValue': attribValue} if '_id' in attributeKey else {'DataType': 'String', 'StringValue': attribValue} 117 | 118 | # ===[ 第二步驟 - 取得 MongoDB(EC2) 伺服器並回傳 MongoClient ] === 119 | def get_MongoClient(): 120 | return mongoClient 121 | 122 | # ===[ 第一步驟 - 先取得 MongoDB(EC2) 伺服器連線,再依據功能 funtion_id (分類號/關鍵字/待借清單) 送出查詢參數並回傳指定查詢的書籍清單 ] === 123 | def get_MongoBooks(function_id, userId, **kwargs): 124 | return books_total, books_fromMongo[:books] 125 | 126 | # === [ 回覆博客來類別選單書籍 - 依據使用者篩選出分類,從 MongoDB 取回書籍資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 127 | def reply_splacardCarouselFlex(userId, selectedMongoCollection, epochTstamp, kind, level, choice, item): 128 | return reply_rankCarouselFlexMongo(replyMongoData, books_total) 129 | 130 | # === [ 取得使用者操作 - 從 DynamoDb 的 dynamodbTb 表格取回使用者的操作 Operation 記錄 ] === 131 | def getDynamo_userEntireItem(userId, dynamodbTb): 132 | return response['Item'] if 'Item' in response else None 133 | 134 | # === [ 寫入使用者操作 - 將使用者的操作 Operation 記錄寫入 DynamoDb 的 dynamodbTb_userop 表格 ] === 135 | def putDynamo_userOperation(userId, epochTstamp, kind, nkind, level, choice, item, **kwargs): 136 | return True 137 | 138 | # === [ 更新使用者操作 - 將使用者的操作 Operation 記錄更新 DynamoDb 的 dynamodbTb_userop 表格 ] === 139 | def updateDynamo_userOperation(userId, epochTstamp, kind, nkind, level, choice, item, **kwargs): 140 | return response 141 | 142 | # === [ 更新使用者操作的書本數 - 更新使用者 DynamoDb 的 dynamodbTb_userop 表格裡的 book 書本數目 ] === 143 | def updateDynamo_userBooksCount(userId, decreased_books): 144 | 145 | # === [ 寫入使用者個人資料 - 將使用者的個人資料 User Profile 寫入 DynamoDb 的 dynamodbTb_userpf 表格 ] === 146 | def putDynamo_userpf(userId, epochTstamp): 147 | return True 148 | 149 | # === [ 更新使用者常用分類號資料 - 更新使用者的 DynamoDb 的 dynamodbTb 使用者的表格 ] === 150 | def removeDynamo_usercnORbc(dynamodbTb, typestr, userId, epochTstamp, cnORbc): 151 | return True 152 | 153 | # === [ 寫入使用者常用分類號資料 - 加入使用者的 DynamoDb 的 dynamodbTb 使用者的表格 ] === 154 | def putDynamo_usercnORbc(dynamodbTb, typestr, userId, epochTstamp, cnORbc): 155 | 156 | # === [ 更新使用者常用分類號資料 - 更新使用者的 DynamoDb 的 dynamodbTb 使用者的表格 ] === 157 | def updateDynamo_usercnORbc(dynamodbTb, typestr, userId, epochTstamp, cnORbc, lastcnORbcLists): 158 | return True 159 | 160 | # === [ 新增使用者常用分類號資料 - 新增使用者的 DynamoDb 的 dynamodbTb 使用者的表格 ] === 161 | def addDynamo_usercnORbc(dynamodbTb, typestr, userId, epochTstamp, cnORbc): 162 | return True 163 | 164 | # === [ 新增或修改使用者常用分館清單 - 新增使用者的 DynamoDb 的 dynamodbTb 使用者的表格 ] === 165 | def addDynamo_userlb(dynamodbTb, typestr, userId, epochTstamp, cnORbc): 166 | return True, None 167 | 168 | # === [ 寫入使用者個人資料 - 將使用者的個人資料 User Profile 寫入 DynamoDb 的 dynamodbTb_userpf 表格 ] === 169 | def putDynamo_userid(userId, epochTstamp, useraccount, password): 170 | return True 171 | 172 | # === [ 回覆常用分類號資料 - 依據從 DynamoDb 取回的分類號資料,回傳組成 LINE 的 FlexBubble 頁面完整的輪播 Carousel Flex Bubbles 圖片元素 ] === 173 | def reply_classifyCarouselFlexDynamo(userId, lastclassifynoLists): 174 | return FlexSendMessage(alt_text="正在顯示分類號資料目錄", contents={"type":"carousel", "contents":carouselBubbles}) 175 | 176 | # === [ 選擇常用分類號資料 - 依據從 DynamoDb 取回的分類號資料,回傳組成 LINE 的 quickReply 元素 ] === 177 | def quick_Classify(userId, lastclassifynoLists): 178 | return TextSendMessage(text="請點選下列任一個常用分類號快速選單。點選完後請稍候書籍查詢。", quick_reply=QuickReply(items=quickReplies)) 179 | 180 | # === [ 回覆完整書目分類資料快速選單 - 依據使用者分類號快速選單,回傳組成 LINE QuickReply 的元素 ] === 181 | def quick_Dictionary(userId): 182 | return TextSendMessage(text="請點選下列第一層分類號選單。點選後請稍候顯示。", quick_reply=QuickReply(items=quickReplies)) 183 | 184 | # === [ 回覆完整圖書館分館快速選單 - 依據使用者分區分館資訊快速選單,回傳組成 LINE QuickReply 的元素 ] === 185 | def quick_Libraries(userId, level, choice, item): 186 | return TextSendMessage(text="請點選下列分區分館資訊選單。點選後請稍候顯示。", quick_reply=QuickReply(items=quickReplies)) 187 | 188 | # === [ 回覆臨近圖書館分館地圖 - 依據使用者地埋位址快速選單,回傳臨近的分館 ] === 189 | def quick_closestLibrary(district, latitude, longitude): 190 | return library, address[library] 191 | 192 | # === [ 變更查詢書籍用的指定月份快速選單 - 修改當月顯示月份快速選單,回傳組成 LINE QuickReply 的元素 ] === 193 | def quick_changeDate(userId, level, choice, item, theyear, themonth): 194 | return TextSendMessage(text="目前正在使用的年/月份為 %d 年 %d 月。請點選下列要變更的月份,點選後請稍候顯示。" % (theyear, themonth), quick_reply=QuickReply(items=quickReplies)) 195 | 196 | # === [ 回覆博客來類別選項快速選單 - 依據使用者分篩選出類別快速選單,回傳組成 LINE QuickReply 的元素 ] === 197 | def quick_booksCategrories(userId, level, choice, item): 198 | return TextSendMessage(text="請選擇下列排行榜分類。", quick_reply=QuickReply(items=quickReplies)) 199 | 200 | # === [ 回覆使用者輸入的文字訊息 - 依據使用者狀態,回傳組成 LINE 的 Text 文字元素 ] === 201 | def compose_textReplyMessage(userId, epochTstamp, messageText, userInfoItem): 202 | pass 203 | 204 | # === [ 回覆使用者與程式使用者界面互動時回傳結果後的訊息 - 依據使用者狀態,回傳組成 LINE 的 Template 元素 ] === 205 | def compose_postbackReplyMessage(userId, epochTstamp, messageData, userInfoItem): 206 | pass 207 | 208 | # === [ 這裡才是 lambda_handler 主程式區段 ]==================================================================================== 209 | def lambda_handler(event, context): 210 | 211 | # ==== [ 處理文字 TextMessage 訊息程式區段 ] === 212 | @handler.add(MessageEvent, message=TextMessage) 213 | def handle_text_message(event): 214 | messageText = event.message.text 215 | userId = event.source.user_id 216 | epochTstamp = event.timestamp 217 | userInfoItem = getDynamo_userEntireItem(userId, dynamodbTb_userop) 218 | logger.debug('收到 MessageEvent 事件 | 使用者 %s 輸入了 [%s] 內容' % (userId, messageText)) 219 | line_bot_api.reply_message(event.reply_token, compose_textReplyMessage(userId, epochTstamp, messageText, userInfoItem)) 220 | 221 | # ==== [ 處理影 VideoMessage 音 AudioMessage 訊息程式區段 ] === 222 | @handler.add(MessageEvent, message=(ImageMessage, VideoMessage, AudioMessage)) 223 | def handle_content_message(event): 224 | userId = event.source.user_id 225 | logger.debug('收到 ImageMessage, VideoMessage, AudioMessage 事件 | 使用者 %s' % userId) 226 | line_bot_api.reply_message(event.reply_token, TextSendMessage(text='收到一個影音檔但目前沒有處理這個檔案的功能')) 227 | 228 | # ==== [ 處理檔案訊息 FileMessage 的程式區段 ] === 229 | @handler.add(MessageEvent, message=FileMessage) 230 | def handle_file_message(event): 231 | userId = event.source.user_id 232 | logger.debug('收到 FileMessage 事件 | 使用者 %s' % userId) 233 | line_bot_api.reply_message(event.reply_token, TextSendMessage(text='收到一個檔案但目前沒有處理這個檔案的功能')) 234 | 235 | # ==== [ 處理追縱 FollowEvent 的程式區段 ] === 236 | @handler.add(FollowEvent) 237 | def handle_follow(event): 238 | pass 239 | 240 | # ==== [ 處理取消追縱 FollowEvent 的程式區段 ] === 241 | @handler.add(UnfollowEvent) 242 | def handle_unfollow(event): 243 | pass 244 | 245 | # ==== [ 處理使用者按下相關按鈕回應後的後續動作 PostbackEvent 程式區段 ] === 246 | @handler.add(PostbackEvent) 247 | def handle_postback(event): 248 | messageData = json.loads(event.postback.data) 249 | userId = event.source.user_id 250 | epochTstamp = event.timestamp 251 | userInfoItem = getDynamo_userEntireItem(userId, dynamodbTb_userop) 252 | line_bot_api.reply_message(event.reply_token, compose_postbackReplyMessage(userId, epochTstamp, messageData, userInfoItem)) 253 | 254 | # ==== [ 處理 BeaconEvent 的程式區段 ] === 255 | @handler.add(BeaconEvent) 256 | def handle_beacon(event): 257 | pass 258 | 259 | # ==== [ 處理成員加入 MemberJoinedEvent 程式區段 ] === 260 | @handler.add(MemberJoinedEvent) 261 | def handle_member_joined(event): 262 | pass 263 | 264 | # ==== [ 處理機器人加入了一個新聊天室, 有人傳了第一個訊息, 沒有使用者 ID 的程式區段 ] === 265 | @handler.add(JoinEvent) 266 | def handle_join(event): 267 | pass 268 | 269 | # ==== [ 使用者離開目前的群組, 事件裡沒有使用者ID的程式區段 ] === 270 | @handler.add(LeaveEvent) 271 | def handle_leave(): 272 | pass 273 | 274 | # ==== [ 使用者離開臨時聊天室, 事件裡使用者ID在不同位置的程式區段 ] === 275 | @handler.add(MemberLeftEvent) 276 | def handle_member_left(event): 277 | pass 278 | 279 | # === [ 這裡才是 lambda_handler 主程式 ]===================================================================================== 280 | try: 281 | signature = event['headers']['x-line-signature'] # === 取得 event (事件) x-line-signature 標頭值 (header value) 282 | body = event['body'] # === 取得事件本文內容(body) 283 | eventheadershost = event['headers']['host'] 284 | handler.handle(body, signature) # === 處理 webhook 事件本文內容(body) 285 | 286 | # === [ 發生錯誤的簽章內容(InvalidSignatureError)的程式區段 ] === 287 | except InvalidSignatureError: 288 | return { 289 | 'statusCode': 400, 290 | 'body': json.dumps('InvalidSignature') } 291 | 292 | # === [ 發生錯誤的LineBotApi內容(LineBotApiError)的程式區段 ] === 293 | except LineBotApiError as e: 294 | logger.error('呼叫 LINE Messaging API 時發生意外錯誤: %s' % e.message) 295 | for m in e.error.details: 296 | logger.error('-- %s: %s' % (m.property, m.message)) 297 | return { 298 | 'statusCode': 400, 299 | 'body': json.dumps(traceback.format_exc()) } 300 | 301 | # === [ 沒有錯誤(回應200 OK)的程式區段 ] === 302 | return { 303 | 'statusCode': 200, 304 | 'body': json.dumps('OK') } -------------------------------------------------------------------------------- /使用 AWS Lambda 部署 LINE Bot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacquesBlazor/LINE-Bot-AWS-Lambda-Python/0bf3b8b3d5e250bcc2edbfa183dd312b812fdd76/使用 AWS Lambda 部署 LINE Bot.pdf --------------------------------------------------------------------------------