[Day 30] 開發實戰 (四):功能實作 (下)
今天要來把剩下的功能都開發完吧!
進度
進度圖沒有變化。
今天要來接著開發計算與定時提醒功能。
使用者故事 (User Story)
複習一下使用者故事。
2. 計算最佳做法功能
- 作為頻道成員,我可以下達指令要求開始計算。
- 作為頻道成員,當我要求開始計算時,Discord BOT 會檢查是否所有成員當天都已提交線索,若不是,則會彈出提示訊息詢問是否確定要進行計算。
- 作為頻道成員,我可以在計算開始前查看本次計算的組合數量。
- 作為頻道成員,在 Discord BOT 計算時,我會看到「正在輸入…」的提示。
- 作為頻道成員,計算完成後,我可以在特定頻道中看到 Discord BOT 提供的結果。
- 作為頻道成員,我可以下達指令,查看當前最新的計算結果。
3. 其他功能
- 作為頻道成員,我可以享有 Discord BOT 定時檢查所有人是否已報線索的功能,若有未報者,該成員會被 Tag 提醒。
- 作為頻道成員,我可以開啟或關閉 Discord BOT 的定時檢查功能。
2. 計算最佳做法功能
設定指令去執行計算本身並不難,這個功能最大的挑戰,是這個計算時間很長,需要有一些提示避免誤以為 Discord BOT 出事了,並且也需要避免這個計算影響到其他功能。
這邊就用一個簡單的函數代表繁重的計算:
1 | import time |
(1) 開始計算
要用什麼方式觸發?
相較於昨天的線索,這邊需要思考一下該怎麼觸發比較合適。會有這樣的原因是,我希望使用者可以知道「已經開始計算」和「計算完畢了」,所以,一次觸發就要回傳兩次訊息。
但是,Interaction 只能回應一次,如果想要再繼續傳送就要使用其他作法 (後面會介紹),因此,這邊就不能使用 hybrid command,不然使用 slash command 就會造成錯誤。最後,我選擇使用 slash command 來觸發。
如果想要依舊有 hybrid command 的效果也不是不行,只是要分成兩個函數分開處理。
正在輸入…
- 作為頻道成員,在 Discord BOT 計算時,我會看到「正在輸入…」的提示。
這邊只要使用 Day 16 介紹的 typing()
就可以了:
1 |
|
不過,其實我後來才發現有另一種選擇,就是使用 defer()
,改成顯示「正在思考…」。先來看一下效果:
使用的方法如下:
1 |
|
需要注意的是,如同前面所述,一個 Interaction 只能回應一次,而 defer()
也算一個回應,所以後面也要使用 followup
。此外,後面的第一個 followup
的訊息,會取代原本「正在思考…」的位置。
所以,如果把程式碼改成這樣:
1 |
|
最後執行完畢的效果就變成這樣:
計算到一半,Discord BOT 會下線
依照上面的寫法,執行 heavy_calculation
的時候會卡住 event loop,導致計算期間 Discord BOT 是不會有任何反應的。這個期間下的其他指令必須等到計算完畢才會觸發。
上圖就是一個簡單的範例。我在計算中嘗試報線索 (正常使用時,不會這樣做),但卻要等到計算完成才會看到線索格式錯誤的訊息。
卡住 event loop 的另一個問題是,這會導致 Discord BOT 下線,會造成大家恐慌XD。大家會不知道現在到底是 Discord BOT 發生非預期錯誤,還是只是還是還在計算中。因此,我們需要換個寫法,讓這個耗時的計算不會卡住 event loop。
這個問題蠻經典的,有很多人提問,可以參考這個 issue 或是這個 Discussion。簡單來說,這邊需要使用 asyncio
的 loop.run_in_executor
。
1 | loop = asyncio.get_running_loop() |
此外,為了避免有人手滑(?)再度觸發一次,需要做個簡單的防護措施。
1 |
|
效果如下:
可以看到,這次 Discord BOT 就是先回應線索格式不正確,才回應計算完成的,代表有成功避免 event loop 被卡住。
(2) 查看結果
- 作為頻道成員,我可以下達指令,查看當前最新的計算結果。
這個功能就比較沒什麼特別的了,跟查看線索資訊的概念是一樣的。
1 |
|
3. 定時提醒
有時候大家忙著上班 (?),會一個不小心忘記報線索,這時候如果有一個定時提醒的功能就很棒。這個功能是每天下午 1 點檢查一次,如果當天還有人沒有報線索,就會發訊息 Tag 對方。
- 作為頻道成員,我可以享有 Discord BOT 定時檢查所有人是否已報線索的功能,若有未報者,該成員會被 Tag 提醒。
- 作為頻道成員,我可以開啟或關閉 Discord BOT 的定時檢查功能。
(1) 定時提醒
既然是「定時」提醒,就需要使用到 Day 12 所介紹到的 Task 功能。
1 | # cogs/remind.py |
為了 Demo,只好調整一下時間。
(2) 開啟/關閉定時提醒
但這個功能,我其實有點擔心會不會大家覺得太吵,所以想說還是要預留一個開關。
1 |
|
這邊使用 .is_running()
來判斷是否正在運行,如果是,就使用 cancel()
立刻中止。反之,就使用 start()
再次啟動。
要終止 task 除了可以使用
cancel()
,也可以使用stop()
。差別是,stop()
還會執行完當下的這一次,也就是說,還會再提醒一次。所以,這邊我選擇使用cancel()
。
小結
今天介紹了如何在不影響原本功能的情況下,去執行繁重的計算,也介紹了如何做到每日提醒的功能。
總算是介紹完所有功能了,明天再來補個完賽心得吧!