rumors-line-bot 過去傳過訊息implementation - HackMD

文章推薦指數: 80 %
投票人數:10人

透過push API 或LINE Notify 告知Chatbot 使用者有新回應。

該notification 的call-to-action 就是請使用者打開「查詢過的訊息列表」介面。

更新相對應的新reply 標記( ...     Signin --- tags:cofacts --- #rumors-line-bot過去傳過訊息implementation Pastdiscussion:https://github.com/cofacts/rumors-line-bot/issues/133 ##Userstory -作為**Chatbot使用者**,我希望chatbot可以列出我過去傳過的訊息以及回應的狀況,讓我能在有需要的時候快速翻找。

-作為**Chatbot使用者**,我希望chatbot在我傳過的訊息有新回應的時候可以通知我,讓我接收最新的不同意見。

##Taskbreakdown 1.Chatbotserver需連結至新的資料庫。

此資料庫: -記錄每名chatbot使用者曾經查詢過哪些article、哪些有新reply -記錄每名chatbot使用者是否點擊觀看新reply(列表標上「新reply標記」,在使用者點入連結前持續顯示) -紀錄每名chatbot使用者曾經接收過哪些reply的推播(避免重複推播) 2.Chatbot提供一個「查詢過的訊息列表」 -應使用LIFF實作、透過richmenu觸發 -使用者透過訊息列表點入訊息,需要標記成已讀、消除新reply標記 >「點入訊息」應該無法直接顯示文章頁面,因為這樣無法送出「有用」或「沒用」。

>這可能要回到chatbot顯示或者直接在LIFF另外實作,才能正常讓使用者送出「有用」或者是「沒用」。

-應[重構現有LIFF實作](https://github.com/cofacts/rumors-line-bot/issues/144),套用server-render或clientUIlibrary以利開發 -應讓postbackbutton[往上捲回去的時候仍然可用](https://github.com/cofacts/rumors-line-bot/issues/49) -~~需在chatbotserver上建立APIserver支援上述列表與操作~~~~server-siderrender,實作CSRFprotection~~不想proxyCofactsAPI也想降低serverload而不走server-siderender,最後作成GraphQLserver+token-basedauthentication 3.需實作一cronjob實作通知,包含: -透過pushAPI或LINENotify告知Chatbot使用者有新回應。

該notification的call-to-action就是請使用者打開「查詢過的訊息列表」介面。

-更新相對應的新reply標記(標成未讀) ##資料庫:mongodbw/[mLab](https://elements.heroku.com/addons/mongolab) 496MBFree! ##資料表:`UserArticleLinks` 紀錄每個botuser與訊息的羈絆(?) -`userId`:ID -`articleId`:ID -`createdAt`:timestamp使用者傳送此訊息、建立此link的時間 -`lastViewedAt`:timestamp使用者上次查看此訊息~~回應~~的時間。

-2020/7/4討論後追加:`UserArticleLink`create時必會寫入 -~~`lastRepliedAt`:timestampCofacts資料庫內最新回應的時間,cronjob或view時更新~~(不用惹) -~~`lastPositiveFeedbackRepliedAt`:[timestamp資料庫內評價為正的最新回應時間](https://g0v-slack-archive.g0v.ronny.tw/index/channel/C2PPMRQGP#ts-1584172124.191900),cronjob或view時更新~~(不用惹) ###什麼時候會新增一筆`UserArticleLink` -傳訊息、資料庫沒有、填寫理由送出後 -傳訊息、選擇一則資料庫訊息,但該訊息沒有回應。

(沒填理由也算) -傳訊息、選擇一則資料庫訊息,有回應。

:::info 大小估算: 2017~2020年所有userselectsanarticle+confirmsubmitarticle=(270,018+18,369)~=300,000,每個都是一個user-articlelink 粗估每個document500byte 300,000*0.5KB=150MB ::: ##資料表:`UserSettings` 一個LINEbotuser僅會有一筆UserSetting。

-`userId` -`allowNewReplyUpdate`:使用者是否允許我們推新回應。

預設true -`newReplyNotifyToken`:若使用者有設定LINENotify,此欄位會存LINEnotify用的token。

預設為空。

:::info 大小估算: 200byte/record*200KLINEbotusers=40MB ::: ###什麼時候會新增一筆`UserSettings` 使用者加chatbot為好友時。

##LINEbotuserflow 1.User點擊richmenu選擇看過去訊息/收到notification並點按 2.LINEbot跳出全頁LIFF,展示文章列表: -時間 -文章內文節錄(即時從CofactsAPI抓) -未讀回應數(即時從CofactsAPI抓回應,然後挑出比`lastViewedAt`晚的)+未讀標記 3.點擊文章列表項目,會傳訊息進Cofactschatbot,chatbot列出未讀的回應列表,供使用者點按,進入`CHOOSING_REPLY`state。

若只有一則未讀回應,自動`skipUser` -此時要更新corresponding`UserArticleLink`的`lastViewedAt` -此設計讓使用者能下接「有用沒用」回應 -此設計亦方便使用者轉傳回應 ###Implementationdetail(2020/5/10) 分成下面三個部分: ####1.LIFF<>chatbotGraphQLAPI的新認證方式 目前LIFF與chatbotGraphQL的authentication,仰賴chatbot在URL上帶有的自產JWT,讓chatbotGraphQL知道目前是哪個`userId`在使用LIFF,以及是哪個searchsession(by`sessionId`)的按鈕。

可是,新功能「過去傳過訊息」LIFF會放在richmenu,URL裡無法帶有會過期的JWT;直接不放exp或設定過久的exp也有些危險,畢竟JWT被chatbotgraphql視為等同密碼的存在,一但leak出去,只要exp沒到,攻擊者就可以一直用該JWT的身份存取API。

因此,我們會新增一種新的authentication:LINEIDtoken法。

1.LIFF透過[liff.getIDToken()](https://developers.line.biz/en/reference/liff/#get-id-token)拿到LINE發的OpenIDIDtoken 2.LIFF發request到chatbotGraphQL時帶有`Authorization:line${IDtoken}`(`line`是為了與JWT使用的`bearer`做出區隔的自訂authenticationtype) 3.chatbotGraphQL收到`line`開頭的authorizationheader之後,使用[verifyIDtoken](https://developers.line.biz/en/reference/social-api/#verify-id-token)API檢查是否正確,並且取用回傳的userID(`sub`)。

以上也是[LINE官方文件](https://developers.line.biz/en/docs/line-login/take-over-session/#transfer-login-session)中提到的作法。

由於LINEIDtoken必須呼叫LINEAPI才能取得,不會有洩漏的機會,所以比目前chatbot自發JWT的機制還更安全。

不過,目前有些功能仍然需要保留「從chatbot傳資料給LIFF回到GraphQL」的機制(如在按鈕上帶當時的`sessionId`,LIFF發request時也必須帶有該`sesionId`回到GraphQL,且此`sessionId`不該在LIFF端被竄改),chatbot自發JWT依然有不可取代的功能在,故會兩者並行。

不過,使用此種方式,會有下面的限制: -LINEdeveloper的LIFFscope設定必須要勾選openid ![](https://s3-ap-northeast-1.amazonaws.com/g0v-hackmd-images/uploads/upload_25d6cd753775a7d5ea75931110150b7f.png) -LIFFchannel的provider必須與chatbot(messagingAPI)provider一致,才能共用userid。

-ChatbotGraphQL會多一個與LINEserver發API確認token的工。

####2.LIFF顯示文章列表的機制 LIFF從chatbotGraphQL取回的資料將不會帶有訊息文字之類的訊息。

整個載入流程會是: 1.LIFF透過`myArticleLinks`API取得要顯示的articlelinks 2.LIFF顯示表格、可點選的超連結,文字部分則為loading樣態(類似https://github.com/zalog/placeholder-loading) 另外,顯示時發送GAvisitevent,`utm_source=myArticleLink`,`utm_medium`從URL拿,預設是`richmenu`。

####3.點擊項目時觸發的東西 點擊表內訊息後,送出「📃Seenewrepliesof`https://cofacts.g0v.tw/article/`」到聊天室,觸發下面動作: -開始新searchsession(發配新`sessionId`) -未來ga的`utm_source`為`myArticleLink`、`utm_medium`為`richmenu` -從URL找出`articleId`,設定`selectedArticleId` -找出相對應的`userArticleLink` -從CofactsAPI找出所有回應之後,過濾掉建立時間比`userArticleLink.lastViewedAt`早的,使用flexcarousel列出這些reply -更新`lastViewedAt`到現在的時間 -切換到`CHOOSING_REPLY`state :::info 未來推播實作時,由pushmessage觸發的LIFFURL應帶有`utm_medium=push`,而發送到聊天室的內容也會稍微不同(如換emoji為📌);chatbot也使用此emoji,判斷utm_medium之後要設定為何。

如此方可將「自己從richmenu點開LIFF」的動作,與「看了pushmessage之後點進來LIFF」,跟其他一般查詢(完全不帶有utm_xxx)分開。

::: ####2020/7/10Update 根據GA文件[trafficsourcedimensions](https://support.google.com/analytics/answer/1033173)與[collectcampaigndata](https://support.google.com/analytics/answer/1033863),`utm_source`應是「導流量到這個網站的來源」、`utm_medium`則是媒介。

對於LIFF裡的網頁來說,source就是顯示URL/button讓使用者進來的流量來源,對這裡來說就是特定的LINEchannel;而媒介則可能是pushmessaage或是richmenu。

因此: -若從rumors-line-bot的richmenu點按進LIFF: -來源是Cofacts的bot、媒介是richmenu -故為`utm_source=rumors-line-bot&utm_medium=richmenu` -若是收到rumors-line-bot的pushnotification: -來源是Cofacts的bot、媒介是pushmessage -故為`utm_source=rumors-line-bot&utm_medium=push`。

`utm_source` -若收到LINEnotify的更新: -來源是叫做LINEnotify的channel(預設),媒介也是pushmessage -故為`utm_source=line-notify&utm_medium=push` -未來若有其他地方點了可以打開LIFF,那應該要使用新的`utm_source`,並且斟酌打開方式來設計`utm_medium`。

![](https://s3-ap-northeast-1.amazonaws.com/g0v-hackmd-images/uploads/upload_04aaea5b5442f0426b777244a59dd319.png) ##Cronjob邏輯 1.從所有`UserArticleLinks`找出最大的`lastRepliedAt`作為`lastScannedAt` 3.去`ListArticle`API(sortby`lastRepliedAt`)列出所有在`lastScannedAt`以後才新建立、且未刪除的`ArticleReply` -刪除回應不會被偵測到 -編輯審閱時間:去掉最近12hr的articleReply 5.由2整理出listofunique,updatedarticleids 6.從user<>selectedarticle關聯表,找出要通知的對象(listofuniqueuserstoupdate) 7.比照這些使用者的`UserSettings`,使用multicastAPI&LINENotify告知使用者「之前的訊息有回應囉」導引使用者點開LIFF 8.redisorDB紀錄這次執行的timestamp × Signin Email Password Forgotpassword or SigninviaFacebook SigninviaGitHub SigninviaGoogle NewtoHackMD?Signup



請為這篇文章評分?