2024/02/03更新:提醒,Cookie會過期,若此方法看起來失效,請先檢查Cookie是否需要更新。
2024/01/28更新:支援單一帳號擷取多筆訂單的資訊,更新的內容在最底部。
平常沒在拍食物,要用的時候一張都找不到,來個沖繩龍蝦墊墊檔。
通常我下完Ubereats的訂單後,手機可能會丟到一邊去做自己的事,所以外送進度到哪了不知道,外送員到門口了也不知道,等到再想起時,餐點可能已經擺在門外好一陣子了。
所以,提醒我去拿餐點這種事當然交給Home Assistant去處理,讓它把Ubereats外送進度抓下來,視需求做廣播提醒(我只有做外送員抵達時才會廣播提醒)。
整體的概念是利用Cookie裡的sid去抓對應的訂單資訊,所以登入網頁版Ubereats後,按下F12打開「開發人員工具」,下圖是Microsoft Edge瀏覽器的介面,然後
- 從左邊工具列中找到應用程式。
- 找到儲存空間。
- 找到Cookie後點一下展開,點選Ubereats的網址。
- 在上方搜尋框中輸入”QA”。
- 將「值」複製下來備用,Domain是”.ubereats.com”或是”.uber.com”都一樣。
這個「值」屬於帳號而非訂單,所以不會因為有新的訂單就有不一樣的「值」,但偶爾還是有遇到「值」有改變,原因沒有深究,可能跟我偶爾會清掉Cookie有關。
回到Home Assistant,流程簡介如下
- 名為ubereats_orders_count的rest sensor負責抓指定sid下的訂單數量。
- 名為ubereats_has_new_order的binary_sensor負責判斷由1得到的訂單數量,若不為0則為True。
- 名為Information:Update_UberEats的automation在ubereats_has_new_order為True時,會以高頻率更新(20秒一次)sensor.ubereats_orders_json。
- 名為ubereats_orders_json的rest sensor負責把訂單內容(json)抓下來。
- 名為ubereats_order的template sensor負責拆解ubereats_orders_json,把需要的資訊取出當成屬性(attribute)。
- 名為ubereats_subtitle的template sensor負責拆解ubereats_orders_json,把外送的進度取出來。
- 名為Information:Notify_When_Ubereat_Arrived的automation在sensor.ubereats_subtitle變成”Arriving now”時做廣播。
以下是上述各步驟的程式碼及說明。
步驟1(sensor),Cookie填入前面得到的「值」,payload則視所在地區更改,在台灣的話輸入'{“timezone”:”Asia/Taipei”}’即可。
- platform: rest
name: UberEats_Orders_Count
resource: https://www.ubereats.com/api/getActiveOrdersV1
method: POST
headers:
Content-Type: application/json
X-CSRF-Token: x
Cookie: "sid=QA.CAESEGCEW2J6wUrTrqJ-iKZeNtoYksPjrQYiATEqJDhiZGIyZjAxLTFkOTEtNGU4Zi1iOTAyLWEzODAwMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
payload: '{"timezone":"Asia/Taipei"}'
force_update: true
value_template: >
{{ value_json.data.orders | count | int }}
unit_of_measurement: "orders"
scan_interval: 60
步驟2(binary_sensor),將sensor.ubereats_orders_count取integer並寫成變數orders_count,當orders_count不為unknown且大於0時則為True,這一步沒有需要修改的部分。
- platform: template
sensors:
ubereats_has_new_order:
value_template: >
{% set orders_count = states("sensor.ubereats_orders_count") | int %}
{{ orders_count != 'unknown' and orders_count > 0 }}
步驟3(automation),condition是需要加上去的,否則Home Assistant會每20秒去更新一次sensor.ubereats_orders_json,但沒有訂單的情況下沒有這個必要。
- alias: Information:Update_UberEats
trigger:
- platform: time_pattern
seconds: "/20"
condition:
- condition: state
entity_id: binary_sensor.ubereats_has_new_order
state: "on"
action:
- service: homeassistant.update_entity
entity_id: sensor.ubereats_orders_json
步驟4(sensor),與步驟1一樣,更改Cookie與payload。scan_interval設為86400秒是因為沒訂單時這個sensor沒有必要自行以預設的頻率(60秒)更新,而是有訂單的話再從步驟3的automation透過homeassistant.update_entity這個服務來更新即可。
- platform: rest
name: UberEats_Orders_JSON
json_attributes:
- uuid
- feedCards
- activeOrderOverview
- contacts
- backgroundFeedCards
json_attributes_path: "$.data.orders[0]"
resource: https://www.ubereats.com/api/getActiveOrdersV1
method: POST
headers:
Content-Type: application/json
X-CSRF-Token: x
Cookie: "sid=QA.CAESEGCEW2J6wUrTrqJ-iKZeNtoYksPjrQYiATEqJDhiZGIyZjAxLTFkOTEtNGU4Zi1iOTAyLWEzODAwMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
payload: '{"timezone":"Asia/Taipei"}'
force_update: true
value_template: "{{ value_json.data.orders[0].feedCards[0].status.currentProgress }}"
scan_interval: 86400
步驟5(sensor),從步驟4得到的json中取出需要用到的資訊,例如預計抵達時間、餐廳名稱、外送員名稱與外送員位置(經緯度)等等,但這些屬於過渡資訊,不需要獨立為實體(entity),所以存成sensor.ubereats_order實體下的屬性(attribute)就可以了。
- platform: template
sensors:
ubereats_order:
value_template: >-
{{ states.sensor.ubereats_orders_json.state }}
attribute_templates:
eta: >-
{{ state_attr("sensor.ubereats_orders_json", "feedCards")[0].status.title if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
title_summary: >-
{{ state_attr("sensor.ubereats_orders_json", "feedCards")[0].status.titleSummary.summary.text if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
subtitle_summary: >-
{{ state_attr("sensor.ubereats_orders_json", "feedCards")[0].status.subtitleSummary.summary.text if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
restaurant_name: >-
{{ state_attr("sensor.ubereats_orders_json", "activeOrderOverview").title if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
courier_name: >-
{{ state_attr("sensor.ubereats_orders_json", "contacts")[0].title if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
courier_description: >-
{{ state_attr("sensor.ubereats_orders_json", "feedCards")[1].courier[0].description if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
latitude: >-
{{ state_attr("sensor.ubereats_orders_json", "backgroundFeedCards")[0].mapEntity[0].latitude if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
longitude: >-
{{ state_attr("sensor.ubereats_orders_json", "backgroundFeedCards")[0].mapEntity[0].longitude if not is_state("sensor.ubereats_orders_json", "unknown") else "N/A" }}
步驟6(sensor),這一條很簡單,應該沒什麼需要解釋的。
- platform: template
sensors:
ubereats_subtitle:
value_template: "{{ states.sensor.ubereats_order.attributes.subtitle_summary }}"
步驟7(automation),action裡面有二個服務(service),第一個是Line通知,第二個是廣播。我在廣播前放了一個condition,當廣播的開關(input_boolean.home_general_broadcast)為開啟時,才會進行廣播,用意是避免在錯誤的時間廣播而打擾到人。
- alias: Information:Notify_When_Ubereat_Arrived
trigger:
- platform: template
value_template: >
{{ states.sensor.ubereats_subtitle.state == "Arriving now" }}
action:
- service: notify.notify_line
data:
message: Ubereats外送員已到住家附近。
- condition: or
conditions:
- condition: state
entity_id: input_boolean.home_general_broadcast
state: "on"
- service: tts.google_translate_say
data:
entity_id: group.home_broadcast
message: Ubereats外送員已到住家附近
完成以上就完成了提醒的部分,如果需要在Home Assistant的分頁中放入外送的資訊,Markdown card是一個不錯的選擇。
type: markdown
content: >-
{% if states.binary_sensor.ubereats_has_new_order.state == "on" %}
<h2>{{ states.sensor.ubereats_order.attributes.restaurant_name }} </h2>
<h3>
{% if states.sensor.ubereats_title.state == "Preparing your order…" %}訂單正在準備中。
{% elif states.sensor.ubereats_title.state == "Picking up your order…" %}正前往領取訂單。
{% elif states.sensor.ubereats_title.state == "Heading your way…" %}正前往您所在位置。
{% elif states.sensor.ubereats_title.state == "Almost here!" %}即將抵達。
{% endif %}
</h3>
<h4>外送員{{ states.sensor.ubereats_order.attributes.courier_name }}預計在 {{ states.sensor.ubereats_remaining.state }} 分鐘後抵達。</h4>
{% else %}
<h4>目前無訂單</h4>
{% endif %}
當中有三個sensor需要另外寫,程式碼如下
- platform: template
sensors:
ubereats_title:
value_template: "{{ states.sensor.ubereats_order.attributes.title_summary }}"
- platform: template
sensors:
ubereats_eta:
value_template: >
{% if states.sensor.ubereats_order.attributes.eta == 'N/A' %}
0
{% else %}
{% set eta = states.sensor.ubereats_order.attributes.eta %}
{% set current_date = states.sensor.date.state %}
{% set hour_offset = 12 if now().hour >= 12 else 0 %}
{% set uber_date_time = strptime(current_date + ' ' + eta + ':00', '%Y-%m-%d %H:%M:%S') %}
{% set uber_timestamp = as_timestamp(uber_date_time) + (hour_offset*60*60) %}
{{ uber_timestamp | timestamp_local }}
{% endif %}
- platform: template
sensors:
ubereats_remaining:
value_template: >
{% if states.sensor.ubereats_order.attributes.eta == 'N/A' %}
0
{% else %}
{% set curTime = states.sensor.date_time_iso.state %}
{% set orderEta = states.sensor.ubereats_eta.state %}
{{ ((as_timestamp(orderEta) - as_timestamp(curTime)) / 60) | int }}
{% endif %}
四種不同進度的結果如下
2024/01/28更新:支援單一帳號擷取多筆訂單的資訊
Ubereats在第一筆訂單下訂後10分鐘內再下訂指定店家的商品可以免運費,吃個飯配個飲料理所當然(?
區別是在Ubereats_Order_JSON裡有個json_attributes_path: “$.data.orders[0]”,其中$.data.orders[0]為第一筆訂單,$.data.orders[1]為第二筆訂單,依此類推,可以自己擴充第N筆。
需要這個功能的話複製下面的程式碼進去就可以了,前面文章裡所提的程式碼不用留著。
Automation的部分
- alias: Information:Update_UberEats
trigger:
- platform: time_pattern
seconds: "/20"
condition:
- condition: state
entity_id: binary_sensor.ubereats_has_new_order
state: "on"
action:
- service: homeassistant.update_entity
entity_id:
- sensor.ubereats_order_1_json
- sensor.ubereats_order_2_json
mode: queued
- alias: Information:Notify_When_Ubereat_Arrived
trigger:
- platform: template
value_template: >
{{ states.sensor.ubereats_order_1_subtitle.state == "Arriving now" or states.sensor.ubereats_order_2_subtitle.state == "Arriving now" }}
action:
- service: notify.notify_line
data:
message: Ubereats外送員已到住家附近。
- condition: or
conditions:
- condition: state
entity_id: input_boolean.home_general_broadcast
state: "on"
- service: tts.google_translate_say
data:
entity_id: group.home_broadcast
message: Ubereats外送員已到住家附近
mode: queued
Binary_sensor的部分(沒有更新)
- platform: template
sensors:
ubereats_has_new_order:
value_template: >
{% set orders_count = states("sensor.ubereats_orders_count") | int %}
{{ orders_count != 'unknown' and orders_count > 0 }}
Sensor的部分(另外把外送地址資訊也一起放進來了)
- platform: rest
name: UberEats_Orders_Count
resource: https://www.ubereats.com/api/getActiveOrdersV1
method: POST
headers:
Content-Type: application/json
X-CSRF-Token: x
Cookie: "sid=QA.CAESEGCEW2J6wUrTrqJ-iKZeNtoYksPjrQYiATEqJDhiZGIyZjAxLTFkOTEtNGU4Zi1iOTAyLWEzODAwMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
payload: '{"timezone":"Asia/Taipei"}'
force_update: true
value_template: >
{{ value_json.data.orders | count | int }}
unit_of_measurement: "orders"
scan_interval: 60
#Order 1
- platform: rest
name: UberEats_Order_1_JSON
json_attributes:
- uuid
- feedCards
- activeOrderOverview
- contacts
- backgroundFeedCards
json_attributes_path: "$.data.orders[0]"
resource: https://www.ubereats.com/api/getActiveOrdersV1
method: POST
headers:
Content-Type: application/json
X-CSRF-Token: x
Cookie: "sid=QA.CAESEGCEW2J6wUrTrqJ-iKZeNtoYksPjrQYiATEqJDhiZGIyZjAxLTFkOTEtNGU4Zi1iOTAyLWEzODAwMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
payload: '{"timezone":"Asia/Taipei"}'
force_update: true
value_template: "{{ value_json.data.orders[0].feedCards[0].status.currentProgress }}"
scan_interval: 86400
- platform: template
sensors:
ubereats_order_1:
value_template: >-
{{ states.sensor.ubereats_order_1_json.state }}
attribute_templates:
eta: >-
{{ state_attr("sensor.ubereats_order_1_json", "feedCards")[0].status.title if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
title_summary: >-
{{ state_attr("sensor.ubereats_order_1_json", "feedCards")[0].status.titleSummary.summary.text if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
subtitle_summary: >-
{{ state_attr("sensor.ubereats_order_1_json", "feedCards")[0].status.subtitleSummary.summary.text if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
address: >-
{{ state_attr("sensor.ubereats_order_1_json", "feedCards")[3].delivery.formattedAddress if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
restaurant_name: >-
{{ state_attr("sensor.ubereats_order_1_json", "activeOrderOverview").title if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
courier_name: >-
{{ state_attr("sensor.ubereats_order_1_json", "contacts")[0].title if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
courier_description: >-
{{ state_attr("sensor.ubereats_order_1_json", "feedCards")[1].courier[0].description if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
latitude: >-
{{ state_attr("sensor.ubereats_order_1_json", "backgroundFeedCards")[0].mapEntity[0].latitude if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
longitude: >-
{{ state_attr("sensor.ubereats_order_1_json", "backgroundFeedCards")[0].mapEntity[0].longitude if not is_state("sensor.ubereats_order_1_json", "unknown") else "N/A" }}
- platform: template
sensors:
ubereats_order_1_title:
value_template: "{{ states.sensor.ubereats_order_1.attributes.title_summary }}"
- platform: template
sensors:
ubereats_order_1_subtitle:
value_template: "{{ states.sensor.ubereats_order_1.attributes.subtitle_summary }}"
- platform: template
sensors:
ubereats_order_1_eta:
value_template: >
{% if states.sensor.ubereats_order_1.attributes.eta == 'N/A' %}
0
{% else %}
{% set eta = states.sensor.ubereats_order_1.attributes.eta %}
{% set current_date = states.sensor.date.state %}
{% set hour_offset = 12 if now().hour >= 12 else 0 %}
{% set uber_date_time = strptime(current_date + ' ' + eta + ':00', '%Y-%m-%d %H:%M:%S') %}
{% set uber_timestamp = as_timestamp(uber_date_time) + (hour_offset*60*60) %}
{{ uber_timestamp | timestamp_local }}
{% endif %}
- platform: template
sensors:
ubereats_order_1_remaining:
value_template: >
{% if states.sensor.ubereats_order_1.attributes.eta == 'N/A' %}
0
{% else %}
{% set curTime = states.sensor.date_time_iso.state %}
{% set orderEta = states.sensor.ubereats_order_1_eta.state %}
{{ ((as_timestamp(orderEta) - as_timestamp(curTime)) / 60) | int }}
{% endif %}
#Order 2
- platform: rest
name: UberEats_Order_2_JSON
json_attributes:
- uuid
- feedCards
- activeOrderOverview
- contacts
- backgroundFeedCards
json_attributes_path: "$.data.orders[1]"
resource: https://www.ubereats.com/api/getActiveOrdersV1
method: POST
headers:
Content-Type: application/json
X-CSRF-Token: x
Cookie: "sid=QA.CAESEGCEW2J6wUrTrqJ-iKZeNtoYksPjrQYiATEqJDhiZGIyZjAxLTFkOTEtNGU4Zi1iOTAyLWEzODAwMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
payload: '{"timezone":"Asia/Taipei"}'
force_update: true
value_template: "{{ value_json.data.orders[1].feedCards[0].status.currentProgress }}"
scan_interval: 86400
- platform: template
sensors:
ubereats_order_2:
value_template: >-
{{ states.sensor.ubereats_order_2_json.state }}
attribute_templates:
eta: >-
{{ state_attr("sensor.ubereats_order_2_json", "feedCards")[0].status.title if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
title_summary: >-
{{ state_attr("sensor.ubereats_order_2_json", "feedCards")[0].status.titleSummary.summary.text if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
subtitle_summary: >-
{{ state_attr("sensor.ubereats_order_2_json", "feedCards")[0].status.subtitleSummary.summary.text if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
address: >-
{{ state_attr("sensor.ubereats_order_2_json", "feedCards")[3].delivery.formattedAddress if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
restaurant_name: >-
{{ state_attr("sensor.ubereats_order_2_json", "activeOrderOverview").title if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
courier_name: >-
{{ state_attr("sensor.ubereats_order_2_json", "contacts")[0].title if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
courier_description: >-
{{ state_attr("sensor.ubereats_order_2_json", "feedCards")[1].courier[0].description if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
latitude: >-
{{ state_attr("sensor.ubereats_order_2_json", "backgroundFeedCards")[0].mapEntity[0].latitude if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
longitude: >-
{{ state_attr("sensor.ubereats_order_2_json", "backgroundFeedCards")[0].mapEntity[0].longitude if not is_state("sensor.ubereats_order_2_json", "unknown") else "N/A" }}
- platform: template
sensors:
ubereats_order_2_title:
value_template: "{{ states.sensor.ubereats_order_2.attributes.title_summary }}"
- platform: template
sensors:
ubereats_order_2_subtitle:
value_template: "{{ states.sensor.ubereats_order_2.attributes.subtitle_summary }}"
- platform: template
sensors:
ubereats_order_2_eta:
value_template: >
{% if states.sensor.ubereats_order_2.attributes.eta == 'N/A' %}
0
{% else %}
{% set eta = states.sensor.ubereats_order_2.attributes.eta %}
{% set current_date = states.sensor.date.state %}
{% set hour_offset = 12 if now().hour >= 12 else 0 %}
{% set uber_date_time = strptime(current_date + ' ' + eta + ':00', '%Y-%m-%d %H:%M:%S') %}
{% set uber_timestamp = as_timestamp(uber_date_time) + (hour_offset*60*60) %}
{{ uber_timestamp | timestamp_local }}
{% endif %}
- platform: template
sensors:
ubereats_order_2_remaining:
value_template: >
{% if states.sensor.ubereats_order_2.attributes.eta == 'N/A' %}
0
{% else %}
{% set curTime = states.sensor.date_time_iso.state %}
{% set orderEta = states.sensor.ubereats_order_2_eta.state %}
{{ ((as_timestamp(orderEta) - as_timestamp(curTime)) / 60) | int }}
{% endif %}
Lovelace Markdown Card的部分
{% if states.sensor.ubereats_order_1_json.state != "unknown" %}
<h2>訂單一 {{ states.sensor.ubereats_order_1.attributes.restaurant_name }} </h2>
<h3>
{% if states.sensor.ubereats_order_1_title.state == "Order received" %}訂單已接受。
{% elif states.sensor.ubereats_order_1_title.state == "Preparing your order…" %}訂單正在準備中。
{% elif states.sensor.ubereats_order_1_title.state == "Picking up your order…" %}正前往領取訂單。
{% elif states.sensor.ubereats_order_1_title.state == "Heading your way…" %}正前往您所在位置。
{% elif states.sensor.ubereats_order_1_title.state == "Almost here!" %}即將抵達。
{% endif %}
</h3>
<h4>外送員{{ states.sensor.ubereats_order_1.attributes.courier_name }}預計再{{ states.sensor.ubereats_order_1_remaining.state }}分鐘後抵達。</h4>
<h4>外送地址為{{ states.sensor.ubereats_order_1.attributes.address }}。</h4>
{% else %}
<h4>訂單一 目前無訂單</h4>
{% endif %}
{% if states.sensor.ubereats_order_2_json.state != "unknown" %}
<h2>訂單二 {{ states.sensor.ubereats_order_2.attributes.restaurant_name }} </h2>
<h3>
{% if states.sensor.ubereats_order_2_title.state == "Order received" %}訂單已接受。
{% elif states.sensor.ubereats_order_2_title.state == "Preparing your order…" %}訂單正在準備中。
{% elif states.sensor.ubereats_order_2_title.state == "Picking up your order…" %}正前往領取訂單。
{% elif states.sensor.ubereats_order_2_title.state == "Heading your way…" %}正前往您所在位置。
{% elif states.sensor.ubereats_order_2_title.state == "Almost here!" %}即將抵達。
{% endif %}
</h3>
<h4>外送員{{ states.sensor.ubereats_order_2.attributes.courier_name }}預計再{{ states.sensor.ubereats_order_2_remaining.state }}分鐘後抵達。</h4>
<h4>外送地址為{{ states.sensor.ubereats_order_2.attributes.address }}。</h4>
{% else %}
<h4>訂單二 目前無訂單</h4>
{% endif %}
如果同時有二筆有效訂單的話,Markdown Card內容會像這樣。