Try   HackMD

Submit homework to HackMD via Google Form with automated check | 使用 Google Form 繳交作業至 HackMD 並自動化檢查

使用場景

因課程需求,作業需要以公開的形式繳交開發過程筆記及對應程式碼的連結至教師設置的公開筆記作業區。當學員直接編輯作業區筆記繳交作業時,時常忘了設置適當的 HackMD 權限,例如公開閱讀且登入者可修改。若有自動化檢查並提醒學員不適當的權限設置則可以省下可觀的溝通時間。

我們使用 Google 表單取得學員之 GitHub 帳號、作業程式碼網址、HackMD 筆記固定發布網址、基本聯絡資訊以及一些關於課程內容之基本問題。Apps script 是 Google 提供自動化旗下服務的平台,運行於 GCP 之上,語法是 Javascript 的子集合。使用範例如下

image

image

設置方法

此處依我們的需求設置,若有其他需求請自行調整

  1. 以表單設置者的帳號點選表單編輯頁面右上角三點式下拉選單內的指令碼編輯器,建立與此表單連結的 Apps script 專案(注意事項1)
  2. 在專案內貼上此 程式碼 並填入上最上方的 ID 及 token,記得任何修改後都要按存檔按鈕
  3. 在專案內確認當下帳號為表單設置者的帳號 (optional) ,在程式碼編輯器中上方工具列選擇 setTrigger 執行,此時會需要授予該 Apps script 權限(注意事項2)setTrigger 的作用為設置收到表單提交時要觸發的函式。也可以在專案內側邊欄選擇觸發條件,以使用者界面設置,但是欄位有點令人困惑且有可能點錯選項,不如固定執行設置函式。此權限會決定電子郵件發信者
  4. 依據表單內容調整 onSubmit 函式內讀取表單回應的 index
  5. 依據作業要求更改第 5 行的 EMAIL.body
  6. 依據作業需求於第 27 行更改要檢查之項目。logError 函式之作用為依據參數檢查函式之回傳錯誤碼,紀錄錯誤原因於 EMAIL.body 內,若沒有錯誤 logError 會回傳 true,否則回傳 false
  7. checkNotePermission 函式需要一個參數 url,此 url 為筆記固定發布網址字串,第一個 fetch 會取得此筆記的 note ID ,第二個 fetch 以 HackMD API 檢查此筆記之讀寫權限以及發布狀態,若有不符合預期者回傳錯誤碼,符合預期者紀錄筆記固定發布網址於 PUBLISHED_LINKS 內。PUBLISHED_LINKS 型態為 list ,可以滿足檢查並紀錄多個筆記之需求
  8. updateNote 函式需要此表單要更新之筆記的 note ID 字串、GitHub 使用者名稱字串,及要填入到筆記的內容字串。第一個 fetch 取得目前筆記之最新內容,接著尋找欲更新之使用者有無已提交之內容,若有則更新其內容(注意事項3),若無則新增內容於末端,有兩個以上的使用者提交紀錄會拋出錯誤。第二個 fetch 以 HackMD API 更新筆記內容。所有的 fetch 若有失敗皆會回傳錯誤碼
  9. 最下方有一個 test 函式,對照表單題目設置好 index 及回應字串後可從工具列執行以快速測試,此測試方法可以繞過表單上的內容驗證限制。執行此函式也會需要授予權限
  10. Apps script 專案內側邊欄中執行紀錄可以看到每次 onSubmit 執行的紀錄,一次成功的提交會見到紀錄如下
    範例執行紀錄

    image

  11. HackMD API 之已知問題詳見注意事項 4

注意事項

  1. Apps script 可綁定其他服務也可以不綁定,若是從 Google Form 編輯頁面點選三點式下拉選單裡的指令碼編輯器,會自動建立一個綁定該表單的 Apps script 專案。開啟 Apps script 專案有兩種方式,一種是上述方式(若已建立會直接打開現有專案),另一種是從 Apps script 服務首頁 的專案列表中開啟專案。當瀏覽器 profile 有多個 Google 帳號時從三點式下拉選單開啟會 400 Bad Request (too many redirection)。建議直接打開服務首頁,確認右上角的帳號頭像是否是接下來編輯所想要使用的身份,若不是請點選頭像切換帳號。當下使用的帳號會影響到 Apps script 觸發條件的權限的設置,即便該表單以及 Apps script 專案的擁有者是其他帳號。
  2. 瀏覽器建議使用 Chromium 系列的,Firefox 在授予 Apps script 權限時會收到系統提示「出現不明錯誤,請稍後再試」
  3. 目前尋找已提交的內容並更新的正規表示式 (line 131) 有瑕疵,需要依據作業需求調整是要取一行或是多行,即 .*\n 的數量,比較好的寫法是要取\- \[ \]${username}\n\- \[ \] 之間的內容,不過單純用 wildcard 表示中間的字元會有多個相符的結果,所以要扣除掉 -, [, ] 三種字元,才會從指定 GitHub 名稱該行的 \- \[ \] 開始剛好取到下一個 \- \[ \]。但是我還沒找到正確寫法,目前依循 文件 及 ChatGPT 的建議嘗試使用以下寫法仍無法正確篩選
    ​​​​const toReplace = new RegExp(`- \\[ \\] ${username}\n([^\\-\\[\\]]|(\n))*- \\[ \\]`, 'm');
    ​​​​data.content = body.content.replace(toReplace, `- [ ] ${username}\n${linksStr}\n`);
    
    若是將 RegExp 內的字串改為 - \\[ \\] ${username}\n(.|(\\n))*- \\[ \\] 會有多個相符結果而 replace 函式會取最後一個,也就是最廣的 - [ ]- [ ]的範圍取代
  4. HackMD 已知問題:
    a. 同個筆記內若有兩個使用者的編輯紀錄,使用其中一者之 API 更新筆記會清空筆記內容,已回報給 HackMD 團隊。目前解決方法為使用另一個筆記嵌入 ({%hackmd note-id %}) 此筆記內容,以維持此筆記只有一人編輯
    b. 以 HackMD API 更新筆記會清空筆記標題,在 HackMD 筆記總覽頁面會看到其名稱顯示為 Untitled,已回報給 HackMD 團隊
    c. 對於想要更新多個連結,一次請求實際上只會有一個更新,所以要多提交幾次表單才能完全更新,已回報給 HackMD 團隊

參考資料