今天要作的是卡米狗的推齊功能,也就是當看到有兩次以上有人說出相同的句子,那麼就跟著說的功能。要作到這件事,卡米狗必須要有一點記性才行,所以我們必須記錄每個群組中所發生的對話。當有人說出一句話時,就檢查最近有沒有人也說出相同的話,如果有的話卡米狗就跟著說。
我們希望的是這樣:
B哥:「采瑤生日快樂~~」
小昕:「采瑤生日快樂~~」
卡米狗:「采瑤生日快樂~~」
但事實上是這樣:
B哥:「采瑤生日快樂~~」
小昕:「采瑤生日快樂~~」
卡米狗:「采瑤生日快樂~~」
毛毛:「采瑤生日快樂~~」
卡米狗:「采瑤生日快樂~~」
卡米狗不應該推齊兩次的,因為正常人推齊只會推一次,所以卡米狗要記得自己上次說了什麼。
整理了一下之後,我們可以寫一個大概的程式碼如下:
def 推齊(channel_id, received_text)
如果在 channel_id 最近沒人講過 received_text,卡米狗就不回應
如果在 channel_id 卡米狗上一句回應是 received_text,卡米狗就不回應
回應 received_text
end
這種不能執行的程式碼稱為虛擬碼,是用來表達邏輯、幫助思考和討論用的。
channel_id
代表目前的群組、聊天室或私聊的 ID,我們這裡姑且通稱為頻道 ID。
這是目前的程式碼:
def webhook
# 學說話
reply_text = learn(received_text)
# 關鍵字回覆
reply_text = keyword_reply(received_text) if reply_text.nil?
# 傳送訊息到 line
response = reply_to_line(reply_text)
# 回應 200
head :ok
end
在卡米狗中,推齊功能的順位是最低的,所以我們會把推齊擺在關鍵字回覆的後面。
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?
# 傳送訊息到 line
response = reply_to_line(reply_text)
# 回應 200
head :ok
end
我們還需要記錄對話:
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
主程式大概就是這樣了。
我們需要實作 channel_id
、save_to_received
、save_to_reply
、echo2
這四個函數,並且需要兩個資料模型,分別儲存收到的對話
以及回應的對話
。
建立 received
資料模型:rails generate model received channel_id text
D:\只要有心,人人都可以作卡米狗\ironman>rails generate model received channel_id text
invoke active_record
create db/migrate/20180113153959_create_receiveds.rb
create app/models/received.rb
invoke test_unit
create test/models/received_test.rb
create test/fixtures/receiveds.yml
D:\只要有心,人人都可以作卡米狗\ironman>
建立 reply
資料模型:rails generate model reply channel_id text
D:\只要有心,人人都可以作卡米狗\ironman>rails generate model reply channel_id text
invoke active_record
create db/migrate/20180113154217_create_replies.rb
create app/models/reply.rb
invoke test_unit
create test/models/reply_test.rb
create test/fixtures/replies.yml
D:\只要有心,人人都可以作卡米狗\ironman>
進行資料庫遷移:rails db:migrate
D:\只要有心,人人都可以作卡米狗\ironman>rails db:migrate
== 20180113153959 CreateReceiveds: migrating ==================================
-- create_table(:receiveds)
-> 0.5013s
== 20180113153959 CreateReceiveds: migrated (0.5027s) =========================
== 20180113154217 CreateReplies: migrating ====================================
-- create_table(:replies)
-> 0.0013s
== 20180113154217 CreateReplies: migrated (0.0024s) ===========================
D:\只要有心,人人都可以作卡米狗\ironman>
根據 Line Messaging API 的文件,我們知道要從 params['events'][0]['source']
底下去找 groupId
、roomId
或者是 userId
。
如果對話是發生在群組,groupId
就會有值,如果對話是發生在聊天室,roomId
就會有值。
所以我們要這樣寫:
# 頻道 ID
def channel_id
source = params['events'][0]['source']
return source['groupId'] unless source['groupId'].nil?
return source['roomId'] unless source['roomId'].nil?
source['userId']
end
可以浪漫一點:
# 頻道 ID
def channel_id
source = params['events'][0]['source']
source['groupId'] || source['roomId'] || source['userId']
end
在儲存前應該先檢查有沒有值,因為 received_text
不一定有值。
# 儲存對話
def save_to_received(channel_id, received_text)
return if received_text.nil?
Received.create(channel_id: channel_id, text: received_text)
end
# 儲存回應
def save_to_reply(channel_id, reply_text)
return if reply_text.nil?
Reply.create(channel_id: channel_id, text: reply_text)
end
按照我們一開始講的虛擬碼邏輯去寫:
def echo2(channel_id, received_text)
# 如果在 channel_id 最近沒人講過 received_text,卡米狗就不回應
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.pluck(:text)
return nil unless received_text.in? recent_received_texts
# 如果在 channel_id 卡米狗上一句回應是 received_text,卡米狗就不回應
last_reply_text = Reply.where(channel_id: channel_id).last&.text
return nil if last_reply_text == received_text
received_text
end
require 'line/bot'
class KamigoController < ApplicationController
protect_from_forgery with: :null_session
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
# 頻道 ID
def channel_id
source = params['events'][0]['source']
source['groupId'] || source['roomId'] || source['userId']
end
# 儲存對話
def save_to_received(channel_id, received_text)
return if received_text.nil?
Received.create(channel_id: channel_id, text: received_text)
end
# 儲存回應
def save_to_reply(channel_id, reply_text)
return if reply_text.nil?
Reply.create(channel_id: channel_id, text: reply_text)
end
def echo2(channel_id, received_text)
# 如果在 channel_id 最近沒人講過 received_text,卡米狗就不回應
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.pluck(:text)
return nil unless received_text.in? recent_received_texts
# 如果在 channel_id 卡米狗上一句回應是 received_text,卡米狗就不回應
last_reply_text = Reply.where(channel_id: channel_id).last&.text
return nil if last_reply_text == received_text
received_text
end
# 取得對方說的話
def received_text
message = params['events'][0]['message']
message['text'] unless message.nil?
end
# 學說話
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 keyword_reply(received_text)
KeywordMapping.where(keyword: received_text).last&.message
end
# 傳送訊息到 line
def reply_to_line(reply_text)
return nil if reply_text.nil?
# 取得 reply token
reply_token = params['events'][0]['replyToken']
# 設定回覆訊息
message = {
type: 'text',
text: reply_text
}
# 傳送訊息
line.reply_message(reply_token, message)
end
# Line Bot API 物件初始化
def line
@line ||= Line::Bot::Client.new { |config|
config.channel_secret = '9160ce4f0be51cc72c3c8a14119f567a'
config.channel_token = '2ncMtCFECjdTVmopb/QSD1PhqM6ECR4xEqC9uwIzELIsQb+I4wa/s3pZ4BH8hCWeqfkpVGVig/mIPDsMjVcyVbN/WNeTTw5eHEA7hFhaxPmQSY2Cud51LKPPiXY+nUi+QrXy0d7Hi2YUs65B/tVOpgdB04t89/1O/w1cDnyilFU='
}
end
def eat
render plain: "吃土啦"
end
def request_headers
render plain: request.headers.to_h.reject{ |key, value|
key.include? '.'
}.map{ |key, value|
"#{key}: #{value}"
}.sort.join("\n")
end
def response_headers
response.headers['5566'] = 'QQ'
render plain: response.headers.to_h.map{ |key, value|
"#{key}: #{value}"
}.sort.join("\n")
end
def request_body
render plain: request.body
end
def show_response_body
puts "===這是設定前的response.body:#{response.body}==="
render plain: "虎哇花哈哈哈"
puts "===這是設定後的response.body:#{response.body}==="
end
def sent_request
uri = URI('http://localhost:3000/kamigo/eat')
http = Net::HTTP.new(uri.host, uri.port)
http_request = Net::HTTP::Get.new(uri)
http_response = http.request(http_request)
render plain: JSON.pretty_generate({
request_class: request.class,
response_class: response.class,
http_request_class: http_request.class,
http_response_class: http_response.class
})
end
def translate_to_korean(message)
"#{message}油~"
end
end
上傳程式碼囉~
要在上傳程式碼之後才能作資料庫遷移,因為資料庫遷移需要讀取資料庫遷移檔。Heroku 上的資料庫遷移指令是 heroku run rake db:migrate
:
D:\只要有心,人人都可以作卡米狗\ironman>heroku run rake db:migrate
Running rake db:migrate on people-all-love-kamigo... up, run.4769 (Free)
D, [2018-01-13T16:57:33.099237 #4] DEBUG -- : (0.6ms) SELECT pg_try_advisory_lock(8162367372296191845)
D, [2018-01-13T16:57:33.115389 #4] DEBUG -- : (2.9ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
I, [2018-01-13T16:57:33.116984 #4] INFO -- : Migrating to CreateReceiveds (20180113153959)
D, [2018-01-13T16:57:33.119682 #4] DEBUG -- : (0.6ms) BEGIN
== 20180113153959 CreateReceiveds: migrating ==================================
-- create_table(:receiveds)
D, [2018-01-13T16:57:33.166042 #4] DEBUG -- : (45.6ms) CREATE TABLE "receiveds" ("id" bigserial primary key, "channel_id" character varying, "text" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
-> 0.0463s
== 20180113153959 CreateReceiveds: migrated (0.0464s) =========================
D, [2018-01-13T16:57:33.170513 #4] DEBUG -- : SQL (0.7ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20180113153959"]]
D, [2018-01-13T16:57:33.173887 #4] DEBUG -- : (3.1ms) COMMIT
I, [2018-01-13T16:57:33.174003 #4] INFO -- : Migrating to CreateReplies (20180113154217)
D, [2018-01-13T16:57:33.174944 #4] DEBUG -- : (0.6ms) BEGIN
== 20180113154217 CreateReplies: migrating ====================================
-- create_table(:replies)
D, [2018-01-13T16:57:33.184287 #4] DEBUG -- : (8.8ms) CREATE TABLE "replies" ("id" bigserial primary key, "channel_id" character varying, "text" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
-> 0.0093s
== 20180113154217 CreateReplies: migrated (0.0093s) ===========================
D, [2018-01-13T16:57:33.185682 #4] DEBUG -- : SQL (0.6ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20180113154217"]]
D, [2018-01-13T16:57:33.187624 #4] DEBUG -- : (1.7ms) COMMIT
D, [2018-01-13T16:57:33.193606 #4] DEBUG -- : ActiveRecord::InternalMetadata Load (2.0ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]]
D, [2018-01-13T16:57:33.201843 #4] DEBUG -- : (0.5ms) BEGIN
D, [2018-01-13T16:57:33.204353 #4] DEBUG -- : (1.6ms) COMMIT
D, [2018-01-13T16:57:33.205359 #4] DEBUG -- : (0.7ms) SELECT pg_advisory_unlock(8162367372296191845)
D:\只要有心,人人都可以作卡米狗\ironman>
成功!
失敗的在底下留言,謝謝。
剩沒幾天了,還有很多可以學的,我想知道你們比較想學些什麼?
接下來我們還可以做的事情有這些:
或者你有想到,但上面沒列出來的也可以。
請在本文留言讓我知道你想學些什麼,可複選。
怎麼辦都好想學QQ 不過就選幾個好了
讓卡米狗能查天氣
打造一個管理後台
讓卡米狗的關鍵字回應能根據目前頻道,作出不同的回應
好哦~好哦~
希望可以學到這些(會不會有點太貪心? ? )
讓卡米狗能發公告
讓卡米狗的關鍵字回應能根據目前頻道,作出不同的回應
讓卡米狗能查天氣
打造一個管理後台
也很感謝你寫了這個序列文,讓我學到很多
好哦~好哦~
想請問一下,我程式碼有輸入錯誤嗎?
因為不會輸入兩次就回話(沒有出現推齊功能)
需要觀察一下 heroku logs -t
在收到訊息時的 log
這是我輸入二次的“小凹”的結果
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.pluck(:text)
是一行程式,你似乎再中間多了一個換行。
我好像忘記要解釋這行程式碼QQ
有了,感謝你
從以前就很想自己做一隻卡米狗來玩了,
現在終於成功了,
謝謝卡卡米 :)
只是...
能不能請你教我做類似行事曆的功能呢?
就是可以讓使用者設定事項,
時間快到的時候,
機器人會發訊息提醒使用者。
再次謝謝卡卡米的教學 :)))
我嘗試往這個方向講一點東西好了 但是不一定有時間講完就是了
卡卡米你好
我想要學 "已讀機器人" ,可以知道有誰已讀
以及,匯出群組成員清單
以上主要是想用來 "清除幽靈人口"
LINE的群組有購買防翻機器人,但他的已讀機常常不回應
而且只列出已讀,功用不大。 因為我想踢的是 "未讀的幽靈人口"
目前只能一個一個去比對,找出三天以上沒已讀的名單
如果可以,我也想知道防翻機器人怎麼寫,呵呵
照著API文件的說明,嘗試了很久,終於抓到 使用者名稱了
但是.... 竟然必須加好友才能得知名稱....(應該是想保護個資)
那這樣就無法寫出,匯出群組名單的功能了....
client = Line::Bot::Client.new { |config|
config.channel_secret = ""
config.channel_token = ""
}
response = client.get_profile("")
case response
when Net::HTTPSuccess then
contact = JSON.parse(response.body)
p contact['displayName']
p contact['pictureUrl']
p contact['statusMessage']
else
p "#{response.code} #{response.body}"
end
已讀機器人目前是無法用 line message api 作出來的。
因為 line 不會在有人已讀的時候通知你的聊天機器人,他只會在有人發訊息時通知你。
你頂多作最後發文時間機器人
恩恩,而且有人 入群 退群 也是沒有通知的
我嘗試過 用 @ TAG人 看的到也只是文字
所以目前想不可以匯出群組名單的方法
請問你怎麼用測試環境的呢?
有時候要測試一個方法的邏輯問題時,每次都要commit上去好耗時XD
有比較快的測試方法嗎??
謝謝你
看了兩天,還是不知所以然
XD 這要寫的話又可以寫很久了
如果只是要測試一個方法的邏輯問題
可以用 irb 做簡單的指令測試
cmd輸入rails generate model received channel_id text要建立資料模型出現錯誤,請問要怎麼改編碼問題呢
試試看把資料夾路徑上有中文的全改成英文
只有發現C碟下有一個"使用者"的資料夾是中文的(?這個算嗎但它不能改名字
到這邊除了"使用者"資料夾 沒有其他中文
路徑上的資料夾名稱 都要用英文 比較好
你的檔案是放在D槽
還是一樣
請問一下為什麼它一開始有反應後來就不理我了?
我在圖中好像有看到錯誤 但我不懂它指的是錯在哪裡
謝謝!
你似乎沒有 Received 這個東西
不好意思 這是什麼意思 是說我在哪裡漏打received嗎?
是你的程式打了 Received 但是你沒有先定義 Received,可能是文章中的
你漏做了?
不是 我今天又檢查一遍之後發現是我之前漏安裝了PostgreSQL
安裝過後問題還是一樣 然後發現我之前有個地方打錯了
目前大概是這樣:
請問我該從哪裡補救... 還有如何把打錯的那個資料庫刪除?
把資料夾名稱改成英文的試試看
請問為什麼每次上傳後都還要跑下列步驟才能使用、不是有更新過就好了嗎?
$ gem install bundler
$ bundle update
請問你在哪裡跑這些步驟呢?
上傳到heroku之後
我在做 Heroku上的資料庫遷移 這步時,他為甚麼會寫說我的 keyword_mappings 已經存在導致上傳失敗??
我猜你的 db/migrate 資料夾下有兩個以上的 create keyword_mapping 檔案
卡卡米你好, 請問有沒有辦法對指定user進行Echo動作呢?
我嘗試了很多次 用userId來對指定user, 可是沒辦法。
你的意思是說只對某些 user 做 echo 嗎? 可以
是嗎? 大大可以教我嗎?
這是我簡單的Echo指令, 引用你之前的程式。
可是要在哪裡加入 指定user做Echo呢?
加上一個 if 判斷 params['events'][0]['source']['userId'] 是來自誰
感謝你! 我成功做出來了。 只是如果要讓卡米狗安靜的功能 大大有沒有提供呢? 想要學如何開啟和關閉卡米狗。
你好,請問我推齊功能加入儲存的方法就會全部訊息都無法顯示,可以幫我看看是什麼問題嗎
你好,請問我推齊功能加入儲存的方法就會全部訊息都無法顯示,可以幫我看看是什麼問題嗎
你的 heroku 上面還沒有 receiveds 資料表。
謝謝
推齊無法成功
重複做了好幾次步驟還是無法
cmd部分有錯嗎
請大大幫幫忙
要用 heroku logs -t 查看錯誤訊息
不好意思麻煩大大幫忙了
不知道哪段是重點
你程式改好後有上傳程式碼嗎?
# 如果在 channel_id 最近沒人講過 received_text,卡米狗就不回應
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.pluck(:text)
這一段的.pluck()在我的環境不能用~~~
抓這個問題抓了很久
最後改成
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.map(&:text)
就可以了~~!!
提供給有遇到類似問題的人參考吶
heroku logs -t
上傳程式/資料庫
請求大師開示
用25天的程式碼可以學說話。
改成第26天的程式碼的話
機器人沒有任何反應,連學說話都不行
換回25天的沒問題
照複製大大的程式碼(有改lineAPI)也沒反應
heroku有更新到最新了
已解決。莫名就好了...
另外想問一下
如果我要把這程式改給另一隻機器人用
是要在重寫一個才行嗎?
單純改lineAPI好像沒作用
只要改 line access token 和 line secret 的值以及做對應 LINE 後台的設定即可