2019/06/25補充:
自從LINE@改成2.0後,原本用的開發用方案一律轉成輕量型,即便是用程式自動推播,也算進訊息數裡,而輕量型一個月最多就500則。
因此很遺憾的,自動推播功能得取消,目前這篇實作出的機器人,會是自動回覆用,像是回覆當地天氣、回覆目前google trend、回覆目前幾個新聞的rss……等,程式碼一樣可以執行,但在排程的部份將會取消。
2019/07/03補充:
推播功能逐步移至Telegram機器人上,可看這篇:
https://medium.com/@augustus0818/telegram-bot-api-1-e4ea74d3b064
本篇用到的資源
實作完上一篇的「用LINE bot api建立line@圖文選單」以後,開始對line api的說明文件有些了解,這篇紀錄的是實作一個推播天氣機器人的甘苦路。
實作一個「每日早上7點,自動推播天氣預報」的機器人,用了以下資源:
放個廣告賺點養主機的$$,謝謝
- 程式語言:node.js
- 主機:google cloud platform
- 排程:GCP cron
- 資料庫:firebase
- 氣象資料:中央氣象局
之前陸陸續續寫的幾篇筆記文,就是在為這個功能做準備,可以參考:
node.js / GCP:用Google Cloud Platform(GCP)建node.js網站
氣象資料:接氣象局api、跨網域get資料
資料庫:如果對firebase不熟,也可以考慮 如何用Google Excel當作資料庫
這篇不會像之前幾篇一樣,完整的列出所有程式碼,因為實作完後,總共寫了四百多行,都是血淚,但會寫出幾個踩到的坑及要注意的事。
先提供實作後的成果:
https://line.me/R/ti/p/%40gnp0209t
npm init完後,要安裝4個套件:
- express
- request:跟氣象局要資料
- line bot sdk nodejs:LINE push
- firebase admin:存取firebase資料庫
npm install express request @line/bot-sdk firebase-admin --save
安裝完後,開一個index.js引用上面4個套件:
拿訂閱者的基本資料
當使用者傳訊息給line的機器人,line api會回傳幾項資訊,標粗體的就是需要存起來的:
{
"destination": "xxxxxxxxxx",
"events": [
{
"replyToken": "0f3779fba3b349968c5d07db31eab56f",
"type": "message",
"timestamp": 1462629479859,
"source": {
"type": "user",
"userId": "U4af4980629..."
},
"message": {
"id": "325708",
"type": "text",
"text": "Hello, world"
}
}
]
}
replyToken直接存成變數就行,不用存進資料庫。
放個廣告賺點養主機的$$,謝謝
message.text看個人,這篇是拿使用者打的字當關鍵字,讓機器人讀到後可以自動回覆用的,所以只在使用者選擇了地區後才會存結果。
source.userId,這個必存,他就像web push時的token一樣,是要發送對象的id,沒有這個id,push時會沒有目標可以push。
從line的get profile文件中可以知道,有了userId以後,可以拿到使用者的公開資訊,一共有3個:名稱、大頭照、個人訊息:
{
"displayName": "LINE taro",
"userId": "U4af4980629...",
"pictureUrl": "https://obs.line-apps.com/...",
"statusMessage": "Hello, LINE!"
}
userId,是這個機器人給這個帳號的id,所以每個帳號對不同的機器人會有不同id。
這一步驟的流程如下:
- 當使用者傳了訊息給機器人 →
- 拿到userId →
- 用userId拿公開資料 →
- 存入firebase
前3步會用到line reply api,這部份可以直接看說明文件的範例碼:
ttps://line.github.io/line-bot-sdk-nodejs/guide/webhook.html#error-handling
第四步存到firebase的資料架構如下:
firsebase_id: {
userId: {
name: encodeURIComponent(line.displayName),
img: line.pictureUrl,
status: encodeURIComponent(line.statusMessage),
location: encodeURIComponent('預設地區')
}
}
拿userId當key,是之後在跑迴圈發push時,抓資料的地方比較單純,不用一層一層找。
name、status、location這3個,因為值通常是中文,所以編碼後再存。
*踩坑處:userLocation的部份,要先提供一個預設值,以免有人沒填地區,回傳的值變成undefined時,會讓迴圈卡住,造成push失敗。
實際存到firebase的結果:

存使用者選擇的地區
這步驟的流程如下:
- 用圖文選單讓使用者發送「我想選擇預報地區」 →
- 回傳carousel樣式,讓使用者選擇地區 →
- 當使用者發送的訊息有「區」這個字以後,更新到firebase
建立圖文選單的部份,可以直接用line@後台製作。也可以用api設定(用LINE bot api建立line@圖文選單)。
當message.text是「我想選擇預報地區」,用reply api去回傳訊息讓使用者點選地區,replay api就一行:
client.replyMessage(event.replyToken, message);
message部份,就是一個物件,比較特別的是,用line的後台發訊息,格式很固定,但如果是用api的方式發,樣式就多了起來。
除了一般的文字、圖片,用api的方式還有2種很特別的:template、flex。
這2種樣式,怕截圖下來如果因為侵權被吉就糟了,所以直接附上網址,可以點進去看:
https://developers.line.biz/en/docs/messaging-api/message-types/#template-messages
這2種物件的寫法,說明文件如下:
這篇選用carousel來製作地區選單,格式如下:
在line上呈現的結果如圖:

從氣象局拿資料
取資料的方式在「接氣象局api、跨網域get資料」這篇就有說明。
這段就寫取哪些資料,跟整理完後的資料格式。
每日天氣預報的部份,預計要發送的資料有2:氣溫、降雨機率。
所以用氣象局api拿的資料就這兩個。
在時間的參數上,因為主要是想讓大家出門上班前,可以決定要不要帶傘,及要不要穿外套,所以時間是抓0600–2100這段時間,包含了大部份上下班的時間。
氣象局的api,氣溫跟降雨機率給的時間參數是不一樣的,因為區間也不同。
- 降雨機率是每6小時為一段,氣溫則是每3小時。
- 降雨用的是startTime/endTime,氣溫用的是dataTime。
因為兩者參數不同,所以在request時也要分開給,最後的處理方式是:
- 用一個function處理「今天」的日期 →
- 拿「今天」的日期去寫進requset的url裡 →
- 整理資料
在處理「今天」的function,有2個坑:
月、日的部份,氣象局的要是2位數,因此要記得補0。
function如下:
這個index.js裡,要用到「今天」的日期時,就用today這個變數就行。
主機所在地會影響new Date的日期。
因為用的是GCP的主機,為了一年後還有免費額度存在,所以主機所在地選擇了美洲。但美洲跟台灣是有時差的,上面的funciton,在get Date()時會出現美洲的時間,因此必須寫好時區。
方法是在index.js裡加一行:
process.env.TZ = 'Asia/Taipei';
這行必須在get Date()之前。
一直寫到現在,整份index.js的架構如下:
整理氣象資料後的結果:

以台北市各區當key,push到各人時比較好抓資料。
LINE push api
LINE push api很簡單,就一行:
client.pushMessage(userId, message);
userId在上面就有取到,並且存在firebase了,只要把firebase的資料抓下來,用迴圈就可以抓完所有存到的userId。
推播的message,選用bubble,最後會出現的樣式如圖:

建立排程
當存了userId,也寫好要push的message object後,最後一步就是建一個排程,定時每日早上7點執行function,push天氣預報了。
一開始排程是用node.js的一個套件,但後來看了一下GCP的後台,發現GCP本身就有提供排程功能,免費版的可以設定20個排程。
進到GCP的後台,到專案的資訊主頁,可以看到選單上有一個「Cron工作」:

有設排程的話,可以看到排程的設定。
排程無法在後台用UI來設定,需要上傳一個cron.yaml到GCP上。
首先在跟app.yaml同層的位置新增一個cron.yaml,檔案內容如下:
url就是要執行的網址,index.js就寫:
app.get('/weather', (req, res) => {
triggerPushWeather();
res.send('succsee');
});
triggerPushWeather裡就是寫拿資料、推播的內容,就會執行這個function了。
cron.yaml建立完後,這個檔案要另外傳到GCP上,用gcloud cli的命令如下:
gcloud app deploy cron.yaml
按下enter,在按y確認,就會傳到GCP上,成功的話,再進到GCP →Cron工作,就會看見排程在上面:

之後每天早上7點,就會執行function,推播天氣預報到有加入好友並且選擇地區的使用者了。
北北基天氣如何呢?這邊加:
https://line.me/R/ti/p/%40gnp0209t

