iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 27
4
Modern Web

只要有心,人人都可以做卡米狗系列 第 27

第二十七天:卡米狗見人說人話,見鬼說鬼話

第二天:認識卡米狗提到過,見人說人話,見鬼說鬼話功能是考慮到多個群組都教了相同的關鍵字時,卡米狗應該在每個群組做出不同的回應,這樣才不會被討厭,於是就加入了這樣的功能。當有人說「姆咪姆咪」時,卡米狗會先檢查這個群組有沒有人教過看到「姆咪姆咪」要回應,如果教過多次,就回應最後一次學過的內容,如果都沒學過,那麼就再檢查其他群組有沒有學過「姆咪姆咪」。

也就是說,學說話指令在儲存時,應該也要儲存是在哪個頻道學會的。

修改學說話指令

目前的學說話指令:

  # 學說話
  def learn(received_text)
    #如果開頭不是 卡米狗學說話; 就跳出
    return nil unless received_text[0..6] == '卡米狗學說話;'
    
    received_text = received_text[7..-1]
    semicolon_index = received_text.index(';')

    # 找不到分號就跳出
    return nil if semicolon_index.nil?

    keyword = received_text[0..semicolon_index-1]
    message = received_text[semicolon_index+1..-1]

    KeywordMapping.create(keyword: keyword, message: message)
    '好哦~好哦~'
  end

應該要改成這樣:

  # 學說話
  def learn(channel_id, received_text)
    ...略
    KeywordMapping.create(channel_id: channel_id, keyword: keyword, message: message)
    ...略
  end

重點是多傳入一個參數 channel_id,然後存入 KeywordMapping

修改關鍵字回覆

在關鍵字回覆的部分,原本是:

  # 關鍵字回覆
  def keyword_reply(received_text)
    KeywordMapping.where(keyword: received_text).last&.message
  end

則是改為:

  # 關鍵字回覆
  def keyword_reply(channel_id, received_text)
    message = KeywordMapping.where(channel_id: channel_id, keyword: received_text).last&.message
    return message unless message.nil?
    KeywordMapping.where(keyword: received_text).last&.message
  end

多加了這兩行:

message = KeywordMapping.where(channel_id: channel_id, keyword: received_text).last&.message
return message unless message.nil?

這兩行的意思是,先找同一個頻道內教過的關鍵字,如果有找到的話就直接回傳。

如果你想要深入學習資料模型的查詢,官方也有提供中文版的說明文件,在這裡:Active Record 查詢

主程式

要記得把參數也傳給剛剛改好的函數,原本的主程式是這樣:

  def webhook
    # 學說話
    reply_text = learn(received_text)

    # 關鍵字回覆
    reply_text = keyword_reply(received_text) if reply_text.nil?

    # 推齊
    reply_text = echo2(channel_id, received_text) if reply_text.nil?

    # 記錄對話
    save_to_received(channel_id, received_text)
    save_to_reply(channel_id, reply_text)

    # 傳送訊息到 line
    response = reply_to_line(reply_text)

    # 回應 200
    head :ok
  end

要改成:

  def webhook
    # 學說話
    reply_text = learn(channel_id, received_text)

    # 關鍵字回覆
    reply_text = keyword_reply(channel_id, received_text) if reply_text.nil?

    ...略
  end

這樣就作完了嗎!?

還沒呢,我們的 KeywordMapping 根本沒有 channel_id 欄位呀!

在 KeywordMapping 資料模型中新增欄位

我們需要使用資料庫遷移的方式來對 KeywordMapping 新增欄位,首先要先建立一個資料庫遷移檔。

指令是 rails generate migration 加上註解:

rails generate migration add_channel_id_to_keyword_reply
D:\只要有心,人人都可以作卡米狗\ironman>rails generate migration add_channel_id_to_keyword_reply
      invoke  active_record
      create    db/migrate/20180114163555_add_channel_id_to_keyword_reply.rb

D:\只要有心,人人都可以作卡米狗\ironman>

生成了一個檔案在 db/migrate 裡面,我們要在這個資料庫遷移檔裡打一點字,這是他目前的樣子:

class AddChannelIdToKeywordReply < ActiveRecord::Migration[5.1]
  def change
  end
end

要加一個欄位的話,要這樣寫:

class AddChannelIdToKeywordReply < ActiveRecord::Migration[5.1]
  def change
    add_column :keyword_mappings, :channel_id, :string
  end
end

add_column 後面第一個參數是資料表名稱,第二個參數是要新增的欄位名稱,以及第三個參數:要新增的欄位格式。欄位格式的話,沒意外通常都會是 :string

這裡寫好之後存檔,就可以作資料庫遷移了。為什麼我知道是這樣寫呢?文件在這裡:Active Record 遷移

資料庫遷移

D:\只要有心,人人都可以作卡米狗\ironman>rails db:migrate
== 20180114163555 AddChannelIdToKeywordReply: migrating =======================
-- add_column(:keyword_mappings, :channel_id, :string)
   -> 0.0012s
== 20180114163555 AddChannelIdToKeywordReply: migrated (0.0020s) ==============


D:\只要有心,人人都可以作卡米狗\ironman>

如果資料庫遷移檔沒打錯字的話,就會看到這個結果。

進行實測

首先上傳程式碼,要養成開著 heroku logs -t 的習慣。

測了一下會發現:

2018-01-14T16:56:36.918562+00:00 app[web.1]: I, [2018-01-14T16:56:36.918392 #4]  INFO -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] Started POST "/kamigo/webhook" for 203.104.146.154 at 2018-01-14 16:56:36 +0000
2018-01-14T16:56:36.920295+00:00 app[web.1]: I, [2018-01-14T16:56:36.920209 #4]  INFO -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] Processing by KamigoController#webhook as */*
2018-01-14T16:56:36.920486+00:00 app[web.1]: I, [2018-01-14T16:56:36.920397 #4]  INFO -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c]   Parameters: {"events"=>[{"type"=>"message", "replyToken"=>"bffeaf21d2b64743b3268bd177ebbaff", "source"=>{"userId"=>"Uc68d82df46b7899e7d716f396ae8e91a", "type"=>"user"}, "timestamp"=>1515948996430, "message"=>{"type"=>"text", "id"=>"7310568889858", "text"=>"A"}}], "kamigo"=>{"events"=>[{"type"=>"message", "replyToken"=>"bffeaf21d2b64743b3268bd177ebbaff", "source"=>{"userId"=>"Uc68d82df46b7899e7d716f396ae8e91a", "type"=>"user"}, "timestamp"=>1515948996430, "message"=>{"type"=>"text", "id"=>"7310568889858", "text"=>"A"}}]}}
2018-01-14T16:56:36.920998+00:00 app[web.1]: W, [2018-01-14T16:56:36.920917 #4]  WARN -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] Can't verify CSRF token authenticity.
2018-01-14T16:56:36.925356+00:00 app[web.1]: D, [2018-01-14T16:56:36.925257 #4] DEBUG -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c]   KeywordMapping Load (1.6ms)  SELECT  "keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_mappings"."channel_id" = $1 AND "keyword_mappings"."keyword" = $2 ORDER BY "keyword_mappings"."id" DESC LIMIT $3  [["channel_id", "Uc68d82df46b7899e7d716f396ae8e91a"], ["keyword", "A"], ["LIMIT", 1]]
2018-01-14T16:56:36.925763+00:00 app[web.1]: I, [2018-01-14T16:56:36.925658 #4]  INFO -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] Completed 500 Internal Server Error in 5ms (ActiveRecord: 1.6ms)
2018-01-14T16:56:36.927283+00:00 app[web.1]: F, [2018-01-14T16:56:36.927195 #4] FATAL -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c]
2018-01-14T16:56:36.927428+00:00 app[web.1]: F, [2018-01-14T16:56:36.927362 #4] FATAL -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column keyword_mappings.channel_id does not exist
2018-01-14T16:56:36.927431+00:00 app[web.1]: LINE 1: ...keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_m...
2018-01-14T16:56:36.927432+00:00 app[web.1]:                                                              ^
2018-01-14T16:56:36.927438+00:00 app[web.1]: : SELECT  "keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_mappings"."channel_id" = $1 AND "keyword_mappings"."keyword" = $2 ORDER BY "keyword_mappings"."id" DESC LIMIT $3):
2018-01-14T16:56:36.927567+00:00 app[web.1]: F, [2018-01-14T16:56:36.927496 #4] FATAL -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c]
2018-01-14T16:56:36.927701+00:00 app[web.1]: F, [2018-01-14T16:56:36.927580 #4] FATAL -- : [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] app/controllers/kamigo_controller.rb:82:in `keyword_reply'
2018-01-14T16:56:36.927702+00:00 app[web.1]: [2a0784f2-c2b7-46c1-818e-5e5dd799e64c] app/controllers/kamigo_controller.rb:10:in `webhook'
2018-01-14T16:56:36.929306+00:00 heroku[router]: at=info method=POST path="/kamigo/webhook" host=people-all-love-kamigo.herokuapp.com request_id=2a0784f2-c2b7-46c1-818e-5e5dd799e64c fwd="203.104.146.154" dyno=web.1 connect=0ms service=11ms status=500 bytes=1827 protocol=https

我先把前面那些多餘的字移除:

Started POST "/kamigo/webhook" for 203.104.146.154 at 2018-01-14 16:56:36 +0000
Processing by KamigoController#webhook as */*
  Parameters: {"events"=>[{"type"=>"message", "replyToken"=>"bffeaf21d2b64743b3268bd177ebbaff", "source"=>{"userId"=>"Uc68d82df46b7899e7d716f396ae8e91a", "type"=>"user"}, "timestamp"=>1515948996430, "message"=>{"type"=>"text", "id"=>"7310568889858", "text"=>"A"}}], "kamigo"=>{"events"=>[{"type"=>"message", "replyToken"=>"bffeaf21d2b64743b3268bd177ebbaff", "source"=>{"userId"=>"Uc68d82df46b7899e7d716f396ae8e91a", "type"=>"user"}, "timestamp"=>1515948996430, "message"=>{"type"=>"text", "id"=>"7310568889858", "text"=>"A"}}]}}
Can't verify CSRF token authenticity.
  KeywordMapping Load (1.6ms)  SELECT  "keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_mappings"."channel_id" = $1 AND "keyword_mappings"."keyword" = $2 ORDER BY "keyword_mappings"."id" DESC LIMIT $3  [["channel_id", "Uc68d82df46b7899e7d716f396ae8e91a"], ["keyword", "A"], ["LIMIT", 1]]
Completed 500 Internal Server Error in 5ms (ActiveRecord: 1.6ms)

ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column keyword_mappings.channel_id does not exist
LINE 1: ...keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_m...
                                                             ^
SELECT  "keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_mappings"."channel_id" = $1 AND "keyword_mappings"."keyword" = $2 ORDER BY "keyword_mappings"."id" DESC LIMIT $3):

app/controllers/kamigo_controller.rb:82:in `keyword_reply'
app/controllers/kamigo_controller.rb:10:in `webhook'
at=info method=POST path="/kamigo/webhook" host=people-all-love-kamigo.herokuapp.com request_id=2a0784f2-c2b7-46c1-818e-5e5dd799e64c fwd="203.104.146.154" dyno=web.1 connect=0ms service=11ms status=500 bytes=1827 protocol=https

我們要關注的重點在:

Completed 500 Internal Server Error in 5ms (ActiveRecord: 1.6ms)

當你看到 500 Internal Server Error,表示程式跑到一半就掛了,掛點原因通常會寫在這個訊息後面。

一個正常的 Log 是長這樣:

 Completed 200 OK in 269ms (ActiveRecord: 9.9ms)

掛點原因:

ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column keyword_mappings.channel_id does not exist
LINE 1: ...keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_m...
                                                             ^
SELECT  "keyword_mappings".* FROM "keyword_mappings" WHERE "keyword_mappings"."channel_id" = $1 AND "keyword_mappings"."keyword" = $2 ORDER BY "keyword_mappings"."id" DESC LIMIT $3):

app/controllers/kamigo_controller.rb:82:in `keyword_reply'
app/controllers/kamigo_controller.rb:10:in `webhook'

他說:ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column keyword_mappings.channel_id does not exist,意思是 keyword_mappings 表格裡面沒有 channel_id 這個欄位。

這個叫做 exception message。一般來說遇到絕大多數的問題都可以拿 exception message 去餵給 google ,就能得到問題的答案。不過看到這裡應該就能猜到是忘記作 Heroku 上的資料庫遷移了。

另外,最後面的那兩行:

app/controllers/kamigo_controller.rb:82:in `keyword_reply'
app/controllers/kamigo_controller.rb:10:in `webhook'

這個叫做 stack trace

意思是他死在 kamigo_controller.rb 的第 82 行,是在 keyword_reply 方法裡。而為什麼他會跑進這個方法呢?原來是在 kamigo_controller.rbwebhook 方法裡的第 10 行的呼叫了 keyword_reply 方法。

透過閱讀 stack trace 你通常就能夠找到錯誤的根源。

在 Heroku 上的資料庫遷移

一如往常:

heroku run rake db:migrate

如果你明明已經跑了資料庫遷移程式,但他還是找不到新欄位的話,可以試試看重開 heroku server:

heroku restart

正確的測試流程

  • 把他邀請進群組 1
  • 把他邀請進群組 2
  • 在群組 1 教他看到 A 要回答 B
  • 在群組 2 教他看到 A 要回答 C
  • 在群組 1 說 A 看他是不是回答 B
  • 在群組 2 說 A 看他是不是回答 C

應該是順利啦~

本日重點

  • 學會怎麼對已經存在的資料模型加一個欄位
  • 學會見人說人話,見鬼說鬼話的本領
  • 學會在 Heoku 上除錯的方法

上一篇
第二十六天:卡米狗推齊
下一篇
第二十八天:建立管理後台
系列文
只要有心,人人都可以做卡米狗33
0
weiclin
iT邦高手 4 級 ‧ 2018-01-15 12:33:19

現在才發現米大有參加鐵人賽 xD

/images/emoticon/emoticon37.gif

0
jerryw47
iT邦新手 5 級 ‧ 2018-01-15 16:08:53

想問一下,如果要向卡米狗那樣可以刪除一些關鍵字的話(卡米狗壞壞),該如何寫進程式裡?

你需要新增一個資料模型去紀錄上一次回應的關鍵字,然後遇到有人說[卡米狗壞壞]時,就去查這個表,找到應該要刪除哪一組學習紀錄。

刪除的指令是 KeywordMapping.where(...這裡就看你要用什麼方式查).destroy_all

不過我並沒有直接刪除,而是標記成封鎖。

0
luke90329
iT邦新手 5 級 ‧ 2018-01-15 22:20:39

卡米大我想問一下就是如果我發一張圖片給卡米狗 取得image_id後
有辦法直接儲存然後轉送給其他人嗎(以關鍵字觸發)

{ 
   "events"=>[
      {
        "type"=>"message", 
        "replyToken"=>"fb2915667ecf4589a7c512690f1d4b64", 
        "source"=>{
        "userId"=>"Ud383eebf313db8b24dd8af72ae5a0b35", 
        "type"=>"user"
        }, 
        "timestamp"=>1516022386121, 
        "message"=>{
        "type"=>"image", 
        "id"=>"0000000000000"
      }
   }
], 
"kamigo"=>{
        "events"=>[
      {
        "type"=>"message", 
        "replyToken"=>"fb2915667ecf4589a7c512690f1d4b64", 
        "source"=>{
        "userId"=>"Ud383eebf313db8b24dd8af72ae5a0b35", 
        "type"=>"user"
        }, 
        "timestamp"=>1516022386121, 
        "message"=>{
        "type"=>"image", 
        "id"=>"0000000000000"
        }
      }
    ]
  }
}

這是 Line Messaging API 官方文件提供的圖片訊息格式:

{
    "type": "image",
    "originalContentUrl": "https://example.com/original.jpg",
    "previewImageUrl": "https://example.com/preview.jpg"
}

你必須提供網址,但是你從 Line Webhook 獲得的圖片訊息不會含有網址,所以不行直接用。

你要把圖片下載下來之後再找個地方放圖,最後把網址傳出去。

0
NekoShounen
iT邦新手 5 級 ‧ 2018-04-19 21:29:47

你好 昨天的問題經過把資料夾改成英文真的解決了 謝謝
可是我在做新的步驟的時候又掛了... 我怕漏了什麼又把23~26天重做很多次
又變回一直被已讀的狀態 請問是哪裡出問題
https://ithelp.ithome.com.tw/upload/images/20180419/20109266fPaU1h0nKn.jpg

kamigo_controller 第 107 行 你呼叫了 learn 方法
但你傳遞的參數數量跟你的方法定義的參數數量不同
你傳兩個參數,但你的方法定義只接收一個參數
在log訊息中可以看見這些資訊喔~

0
ke0914
iT邦新手 5 級 ‧ 2018-07-10 15:10:02

https://ithelp.ithome.com.tw/upload/images/20180710/20110562OvkTkImMF8.jpg
為甚麼他是以我最後一個打的回覆訊息回復?

ke0914 iT邦新手 5 級‧ 2018-07-10 15:36:07 檢舉

剛剛隨便弄弄改改
就好了 哈哈

我也是出現相同問題
請問大大是怎麼解決的

0
such0305
iT邦新手 5 級 ‧ 2018-07-11 22:12:08

卡米大,我在 在 {KeywordMapping 資料模型中新增欄位}
這個部分出了錯誤了QQ。 我原先做到28天,因為錯誤所以回到27天重作,結果就壞惹
https://ithelp.ithome.com.tw/upload/images/20180711/20109721AbXeZmouWQ.jpg

然後我現在遇到了奇怪的問題,localhost一直無法連線,上網爬了一些資料,也是過開記事本(host)刪除註解,防火牆也看過,結果還是這樣QAQQQ 想請問卡米大這種類似情形該怎麼辦??

https://ithelp.ithome.com.tw/upload/images/20180711/20109721LipeKYrNq3.jpg

執行網頁伺服器時的畫面:
https://ithelp.ithome.com.tw/upload/images/20180711/201097210cemArZAp5.jpg

應該是改到不該改的檔案了 但看不出來你改到什麼了耶

0
join1314
iT邦新手 5 級 ‧ 2018-09-14 16:49:46

您好,我做到第25天,資料都可正常傳送,但是第26天上傳後,就沒有回應,用postmanz發現找不到網頁,又發現LINE Developers那邊的Webhook URL也是出現找不到的紅字,後來把主程式刪減到
def webhook
head :ok
end
這三行,重新verify,就success了。之後就重新把刪掉的再做上去,但是還是沒反應,麻煩幫我看看可以怎麼處理...謝謝您

重作前的log https://drive.google.com/open?id=12kq2ZurqTQJ0xOLv95rnuQrV0X07zNRc
重作後的log https://drive.google.com/open?id=1pCJgHknbEZftDrdco_nSHDx8IQSuFP5l

https://ithelp.ithome.com.tw/upload/images/20180914/20111702I2MSQC4l2R.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702PXyHxhrwEP.jpg

手殘,圖片還沒傳完就不小心按了留言,繼續傳
https://ithelp.ithome.com.tw/upload/images/20180914/20111702vcqHLaZCm5.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702ttoEWhrvjE.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702P4rLR5tdq2.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702L8iUeFlWIF.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702xXanmLqrBT.jpg
https://ithelp.ithome.com.tw/upload/images/20180914/20111702COcLbep98v.jpghttps://ithelp.ithome.com.tw/upload/images/20180914/20111702PZ43dQ6EPx.jpghttps://ithelp.ithome.com.tw/upload/images/20180914/201117021WogeA3sre.jpg

你在第 65 行用到一個變數叫做 keyword,但是你沒有告訴他 keyword 是什麼。

0
acegen
iT邦新手 5 級 ‧ 2018-12-04 15:34:27

卡米大我想問個問題,如果我們把機器人分別

  1. 自己加他(這當主要回復訊息)
  2. A群組
  3. B群組

如果在自己的群組裡教機器人 A;0
在A群組裡教 A;1
那在B群組裡怎讓它顯示我們教的是A;0,而不是A;1呢?
(實際情況,他會顯示A;1)
謝謝

你要記錄自己的 user id ,然後優先用自己的 user id 查詢一次自己有沒有教過

acegen iT邦新手 5 級‧ 2018-12-06 12:10:18 檢舉

好的,謝謝卡米大,我再自行摸索看看!

我要留言

立即登入留言