使用 Powershell + 工作排程器 + Line Notify 來定時提醒公車到站時間
事情發生在一個風和日麗的平凡下午:
我:(把手上的事情弄到一個段落再下班吧)
~~十分鐘過後~~
我:差不多可以走了,公車也差不多要來了叭?
公車:(一分鐘前離站)
我:(゚д゚)
這時候才明白愛恨情仇,最傷最痛是後悔。如果我早知道公車快到站了,也許我就不會錯過。
抱著這股傷痛,決定乾脆寫個小腳本,每天下班提醒我一下,避免重蹈覆轍。
綜上所述!目標是:每天下班前十分鐘,告訴我下一班到達的公車時間
因此至少能夠拆分成三個階段:
- 每天下班前十分鐘(定時執行)
- 告訴我(通知功能)
- 下一班到達的公車時間(查詢資訊)
那麼,我們開始吧!
使用 MOTC API 服務取得公車資訊
首先,最重要的是要有資料來源。幸好我們先前在 Api 筆記的時候,就有介紹過公共運輸動態服務 MOTC Transport API,我們只需要使用這組 API 就能輕鬆拿到公車資訊了,感謝開發該服務的朋朋。
2024.11.25 更新:發現 MOTC 的 API 已經下架了,現在公車資訊已經被整合到運輸資料服務 TDX (Transport Data eXchange),有要動手串公車資訊的朋友可能得觀察新版 API 再進行調整了 QQ
我們的場景是在辦公室查詢下一班目標公車到站時間,比對了 Swagger 上提供的中文敘述後,決定嘗試看看「市區公車之預估到站資料」(EstimatedTimeOfArrival/City/{City}/{RouteName}
) 這支 API
註:為了避免被查水表,以下就用台北的 307 號公車為例,並假設目標站點是「台北車站(忠孝)」
嘗試把縣市和公車路線名稱代入後,可以取得公車行經的站牌資訊,其中就有我們最想要的估計到站時間(秒):
接著讓我們調整一下參數,把取前幾筆的 $top
調大一點,來取得我們目標站點台北車站的站牌 ID:
有了站牌 ID,我們就可以根據 官方提供的查詢語法 來篩選出目標站牌:
如果有需要指定回程,也可以再加上 and direction eq 1
的條件。
如此一來我們就可以呼叫 MOTC API 來取得目標站牌+指定公車的到站估計時間囉:
註:沒有申請會員的話有每日 50 次的使用上限,不過這次的目標也就下班打那一次,十分足夠了 XD
使用 Powershell 呼叫 API 取得資料
現在已經保障了資料來源,接下來就是要有個腳本來去打 API 拿資料回來囉!
基於 懶惰 方便的原則,決定用 Powershell 寫個小東西直接打資料回來就好。
這邊就直接用路線名稱和站牌 ID 來串 Uri,並且直接用 Invoke-RestMethod 來呼叫 API 吧:
$busName = '307' # 公車路線名稱
$stopId = 15250 # 站台 ID
# 呼叫 MOTC API 取得公車資訊
$uri = "https://ptx.transportdata.tw/MOTC/v2/Bus/EstimatedTimeOfArrival/City/Taipei/$($busName)?%24filter=StopId%20eq%20'$stopId'&%24top=1&%24format=JSON"
$response = Invoke-RestMethod -Uri $uri
# 把結果轉成 Json 確認一下
$response | ConvertTo-Json
小提醒:原本我們下的
$filter = StopId eq '15250'
的參數,其中$
和空白符在 Uri 會轉換成 HTML 編碼的%24($)
和%20(空白)
,並不是亂碼,請不要緊張
另存成 .ps1
檔案來測試一下:
看來查詢資料已經沒有問題了,接下來就是通知快要下班的我了
使用 Powershell 彈出通知視窗(初版)
第一次嘗試採用了彈跳視窗,想了一想還是順手記錄下來好了
首先將我們目標的 EstimateTime
取出來,然後顯示還有幾分鐘到站、預計幾點幾分到站
最後用 Wscript.Shell
來顯示彈跳視窗:
$estimateSec = $response.EstimateTime
# 組裝要顯示的訊息
$estimateMin = $estimateSec / 60
$estimateTime = (Get-date).AddSeconds($estimateSec).ToString("HH:mm")
$message = "Bus $($busName) - EstimateTime: in $([Math]::Floor($estimateMin)) minute(s), $estimateTime"
# 使用彈跳視窗將預計抵達的時間列印出來
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup($message)
雖然運作順利,但是……
總覺得有點不是很好看啊,而且工作到一半在畫面正中間跳出這個會煩死吧囧
使用 Powershell 傳送 Line Notify
2024.10 更新: Line Notify 將於 2025 年 3 月停止服務(LINE Notify 結束服務公告),有看到這篇的朋朋請選擇一組新的通知服務來串吧 QQ
就在此時一個靈光乍現,對呀我之前爬訂便當的時候不是用過 Line Notify 嗎!
當下一個直衝 Line Notify 高速申請權杖:
複製權杖之後,衝回 Powershell,前人教學抄起來,Invoke-RestMethod 就直接打下去:
# 使用 Line Notify 傳送通知
$lineUri = 'https://notify-api.line.me/api/notify'
$lineToken = 'Bearer YOUR_LINE_TOKEN'
$header = @{ Authorization = $lineToken }
$body = @{ message = $message }
Invoke-RestMethod -Uri $lineUri -Method Post -Headers $header -Body $body
Line 也不負期望地彈出來:
大功告成!搞定拿資料和通知的部分啦~
補充:如果訊息內容有使用中文的朋友,請注意編碼問題。必須存成 Utf-8 with BOM,否則會出現亂碼:
這時候就需要更改編碼為 Utf-8 with BOM;
再重新嘗試一次就會正常了:
原本卡在這步搞不定,正好黑暗執行緒大大發了篇 PowerShell .ps1 檔 UTF-8 編碼問題之變形錯誤,才知道 PowerShell 5.x 有編碼解析的問題,改成 Utf-8 with BOM 順利完工。這邊補充給各位朋朋,望周知
到這邊 Powershell 的部份就處理好了,目前會長這樣:
$busName = '307' # 公車路線名稱
$stopId = 15250 # 站台 ID
# 呼叫 MOTC API 取得公車資訊
$uri = "https://ptx.transportdata.tw/MOTC/v2/Bus/EstimatedTimeOfArrival/City/Taipei/$($busName)?%24filter=StopId%20eq%20'$stopId'&%24top=1&%24format=JSON"
$response = Invoke-RestMethod -Uri $uri
$estimateSec = $response.EstimateTime
# 組裝要顯示的訊息
$estimateMin = $estimateSec / 60
$estimateTime = (Get-date).AddSeconds($estimateSec).ToString("HH:mm")
$message = "Bus $($busName) - EstimateTime: in $([Math]::Floor($estimateMin)) minute(s), $estimateTime"
# 使用 Line Notify 傳送通知
$lineUri = 'https://notify-api.line.me/api/notify'
$lineToken = 'Bearer YOUR_LINE_TOKEN'
$header = @{ Authorization = $lineToken }
$body = @{ message = $message }
Invoke-RestMethod -Uri $lineUri -Method Post -Headers $header -Body $body
使用 工作排程器 定時執行 Powershell 腳本
秉持著前面選擇 Powershell 的 偷懶 簡單精神,這邊的定時執行就直接使用 Windows 內建的工作排程器來處理:
因為我們的場景相對簡單,只有要在特定時間幫我們呼叫 Powershell 腳本,因此直接選擇「建立基本動作」
接著讓我們選擇每週,並指定平日的時候再執行:
最後選擇啟動程式,讓工作排程器開啟 Powershell 並呼叫我們的腳本:
這邊的「程式或指令碼」輸入 powershell
,接著在「新增引數」的部份告訴 Powershell 我們要執行的腳本 -File "C:\Scripts\BusReminder.ps1"
(記得換成你的腳本路徑呦)
註:沒有調整過執行原則的朋友們,可以在引數上加入
-ExecutionPolicy Bypass
來關閉警告,也就是-ExecutionPolicy Bypass -File "C:\Scripts\BusReminder.ps1"
這樣子的感覺
註:如果跟我一樣會把腳本做成 Function 並存成 psm1 檔案的朋朋,這邊的引數會需要變成跟 Profile 一樣的處理方式,先 Import 進來再呼叫方法(這邊假設為
Run-BusNotify()
)例如,:Import-Module "C:\Scripts\BusReminder.psm1";Run-BusNotify;
接著只需要完成就可以在排程中找到囉,馬上就來執行看看是不是正常運作吧:
大功告成!
後日談
自從有了公車到站提醒後,下班再也沒有煩惱了呢
提醒:公車還有十分鐘
我:(還有十分鐘耶,把手上的事情弄到一個段落再下班吧)
~~弄了十五分鐘~~
公車:(離站)
我:(゚д゚)
這時候才明白科技終究是有極限的。
參考資料
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)