iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 29
3
Modern Web

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

第二十九天:卡米狗發公告

今天我們要作的是主動傳訊息的功能。

目前我們用到的都只是回覆訊息的功能:

認識 Push Message API

  # 傳送訊息到 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

上面這個函數是我們之前寫好的 reply_to_line 函數,裡面的最後一行:

line.reply_message(reply_token, message)

這是在呼叫 line 提供給我們的回覆訊息函數,而 line 也有提供讓我們主動發訊息的函數:

response = line.push_message(channel_id, message)

我們需要傳遞 channel_id,告訴 Line 誰應該收到這個訊息,channel_id 就是 userId, groupId 或 roomId。所以我們需要一個資料模型去保存所有頻道的 channel_id。

文件參考在這裡:https://developers.line.me/en/docs/messaging-api/reference/#send-push-message

保存所有頻道

建立資料模型

rails g model channel channel_id

建立一個資料表叫作 channel,裡面有個欄位叫作 channel_id。

資料庫遷移

rails db:migrate

bj4

儲存頻道

在主程式中加入一行:

Channel.create(channel_id: channel_id)

如果你覺得是這樣寫,那你就錯了,因為這樣會導致相同的資料會一直被存進去,到時候你發公告,同一個人就會收到超多次。

Channel.find_or_create_by(channel_id: channel_id)

先看有沒有相同的資料,如果已經有資料的話就不寫入。如果沒有資料才作寫入。這邊有詳細的說明:https://rails.ruby.tw/active_record_querying.html#find-or-create-by

加入後的主程式長這樣:

  def webhook
    # 紀錄頻道
    Channel.find_or_create_by(channel_id: channel_id)

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

    # 關鍵字回覆
    reply_text = keyword_reply(channel_id, 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

接下來要作一個後台網頁去發這個公告。我們會需要兩個 Action,一個 get new Action 用來顯示發公告的後台頁面,另一個 post create Action 用來接收發公告訊息的請求。

管理後台

這次我們手動新增,不使用產生器。

加 Route

修改 config/routes.rb,新增一行:

resources :push_messages, only: [:new, :create]

這是加入一組資源,但我們只使用其中的 new 和 create。

加 Controller

app/controllers 資料夾下建立一個叫作 push_messages_controller.rb 的檔案:

class PushMessagesController < ApplicationController
  before_action :authenticate_user!

  # GET /push_messages/new
  def new
  end

  # POST /push_messages
  def create
  end
end

我們檢查使用者必須先登入,然後開了兩個空的 Action,之後再回頭來改。

加 View

我們要在 app/views/push_messages 下新增一個檔案 new.html.erb

這是 new.html.erb 所需要的全部程式碼:

<%= form_with(url: '/push_messages', local: true) do |form| %>
  <%= text_area_tag 'text' %>
  <%= submit_tag "送出" %>
<% end %>

一個表單的開始是 <%= form_with ..... do ... %>,結束是 <% end %>。表單預設是用 post 方法,所以就不用特別寫出來。

<%= text_area_tag 'text' %> 是輸入文字框。

<%= submit_tag "送出" %> 則是送出按鈕。

如果要了解更多的話可以參考:Action View 表單輔助方法

改 Controller

我們在接收到請求之後要作發訊息的動作:

  def create
    text = params[:text]
    Channel.all.each do |channel|
      push_to_line(channel.channel_id, text)
    end
    redirect_to '/push_messages/new'
  end

text = params[:text] 這是取得剛剛在輸入文字框填的文字

Channel.all.each do |channel| ... end 這段是指我們想要對每一個 channel 作一些事情。

push_to_line(channel.channel_id, text) 這是說我們要主動發訊息 text 給頻道 channel.channel_id,這個函數我們待會才會寫。

push_to_line

  # 傳送訊息到 line
  def push_to_line(channel_id, text)
    return nil if channel_id.nil? or text.nil?
    
    # 設定回覆訊息
    message = {
      type: 'text',
      text: text
    } 

    # 傳送訊息
    line.push_message(channel_id, message)
  end

長得跟之前的 reply_to_line 有 87% 像,就不解釋了。

對一下程式碼

完整的 push_messages_controller.rb 應該長這樣:

require 'line/bot'
class PushMessagesController < ApplicationController
  before_action :authenticate_user!

  # GET /push_messages/new
  def new
  end

  # POST /push_messages
  def create
    text = params[:text]
    Channel.all.each do |channel|
      push_to_line(channel.channel_id, text)
    end
    redirect_to '/push_messages/new'
  end

  # 傳送訊息到 line
  def push_to_line(channel_id, text)
    return nil if channel_id.nil? or text.nil?
    
    # 設定回覆訊息
    message = {
      type: 'text',
      text: text
    } 

    # 傳送訊息
    line.push_message(channel_id, 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
end

發布和測試

自己試試看,你們需要練習自己解決卡關,要養成看 log 的習慣。

關於怎麼做鬧鐘

你需要把主動發訊息這件事情加入排程,請參考:Active Job 基礎 以及 Delayed Job (DJ)

自己摸這個要有花上一週的心理準備,加油加油加油,你是最棒的,耶!

本日重點

  • 學會記錄頻道
  • 學會主動發訊息
  • 學會寫 view 的表單

沒意外的話,明天就講怎麼查天氣。


上一篇
第二十八天:建立管理後台
下一篇
第三十天:卡米狗查天氣
系列文
只要有心,人人都可以做卡米狗33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
luke90329
iT邦新手 5 級 ‧ 2018-01-17 22:16:08

卡米大 那個我記得Push API在免費版本是不是無法使用阿

看更多先前的回應...收起先前的回應...

對,免費板無法用 push message。

我在第三天:作一隻最簡單的 Line 聊天機器人有提到「Plan:Developer Trail 是試用版,Free 是免費版。當然試用版也是免費,差別在於試用版只能加 50 個好友,但是可以使用加值功能,建議選試用版。」

如果你要改試用版,可以直接註冊一個新的試用版,然後把你的 webhook url 接上去,程式碼裡的 secret 跟 token 改一下就好了。

luke90329 iT邦新手 5 級 ‧ 2018-01-18 00:12:49 檢舉

了解 因為機器人是幾個月前創的 不知道有這個試用版xD
對了想請問一下 我想用聊天室內的指令發送公告 以下是程式碼:

  # 發送公告
  def send_announcement(channel_id, received_text)
    return nil unless received_text[0..6] == '/發送公告 '
    text = received_text[7..-1]
    Channel.all.each do |channel|
    push_to_line(channel.channel_id, text) 
  end


  # 傳送訊息到 line
  def push_to_line(channel_id, text)
    return nil if channel_id.nil? or text.nil?
    puts channel_id, text
    
    # 設定回覆訊息
    message = {
      type: 'text',
      text: text
    } 

    # 傳送訊息
    line.push_message(channel_id, message)
  end

請問這樣打是OK的嗎 還是還要在主程式調用send_announcement呢

  # 發送公告
  def send_announcement(channel_id, received_text)
    return nil unless received_text[0..6] == '/發送公告 '
    text = received_text[7..-1]
    Channel.all.each do |channel|
      push_to_line(channel.channel_id, text) 
    end
  end

主程式當然要調用send_announcement

luke90329 iT邦新手 5 級 ‧ 2018-01-18 19:13:46 檢舉

感恩卡米大 用好了XD

nienst iT邦新手 5 級 ‧ 2018-01-31 20:57:51 檢舉

我照著上面的程式,也嘗試一下在聊天室發公告
但奇怪的是,只有該聊天室會出現公告,其他群組並沒有。
而且,發送公告只RUN一次,後會在打 /發送公告 就不會在反應了

應該是我 主程式調用 的方式有問題
reply_text = send_announcement(channel_id, received_text)
我也嘗試過其他方式,但也是怪怪的...

nienst iT邦新手 5 級 ‧ 2018-02-01 19:51:49 檢舉

恩,終於查到原因了
return nil unless received_text[0..6] == '/發送公告 '
text = received_text[7..-1]

字數錯了

return nil unless received_text[0..5] == '/發送公告 '
text = received_text[6..-1]

XD

1
lee98064
iT邦新手 5 級 ‧ 2018-01-18 17:38:45

想請問卡米大~
今天在作heroku資料庫遷移時一直無法遷移成功,錯誤訊息顯示都卡在倒數第二個,想請問怎麼解決呢?

完整錯誤訊息:
https://drive.google.com/open?id=1RswxzP3VdST65cnY7pu8VSeppxD4KmAE24u9EA8sktQ

https://ithelp.ithome.com.tw/upload/images/20180118/20108325knDoP6IlMb.png

看更多先前的回應...收起先前的回應...

看起來是你的資料庫裡面已經有 users 表格了,如果資料消失也沒關係,你可以這樣:

heroku run rake db:drop
heroku run rake db:create
heroku run rake db:migrate

應該可以解決問題

lee98064 iT邦新手 5 級 ‧ 2018-01-18 20:56:56 檢舉

sorry 說錯指令了

heroku pg:reset
heroku run rake db:migrate

這次一定行惹

lee98064 iT邦新手 5 級 ‧ 2018-01-18 21:42:12 檢舉

他不給面紙XDD
還是不行耶XDDD
(錯誤訊息同上XD)

https://ithelp.ithome.com.tw/upload/images/20180118/20107309aVp6ScT5Xl.jpg

可是我可以

lee98064 iT邦新手 5 級 ‧ 2018-01-18 22:31:34 檢舉

我到這步驟也可,但是一遷移資料庫又出現一樣錯誤惹~

lee98064 iT邦新手 5 級 ‧ 2018-01-18 22:42:37 檢舉

然後我看了一下昨天的文章,有新的人留言,發現好像也卡在類似的地方,好像都是那個檔案無法遷移卡住

lee98064 iT邦新手 5 級 ‧ 2018-01-18 22:42:40 檢舉

然後我看了一下昨天的文章,有新的人留言,發現好像也卡在類似的地方,好像都是那個檔案無法遷移卡住

額 好奇怪

可以看一下你的 db/migrate 資料夾內的檔案列表嗎?

lee98064 iT邦新手 5 級 ‧ 2018-01-19 17:20:09 檢舉

db/migrate 資料夾:
https://ithelp.ithome.com.tw/upload/images/20180119/201083255HvonuUrjA.png
然後我參考上一篇的留言讀取 db/development.sqlite3截圖:
https://ithelp.ithome.com.tw/upload/images/20180119/20108325IParTBf3Q4.png

lee98064 iT邦新手 5 級 ‧ 2018-01-19 17:21:28 檢舉

你的情況跟他的不太一樣,是因為你的 migrate 出問題的是 heroku ,他出問題的是本機,這個是觀察本機的資料庫內容,而 heroku 上的資料庫要觀察的話要裝別的東西 XD

不過你怎麼有兩個 add user 的 migration 檔?我只有一個 devise_create_users,你多一個add_devise_to_users 這兩個都打開來看一下,如果一樣的話,刪掉一個。

lee98064 iT邦新手 5 級 ‧ 2018-01-19 19:16:12 檢舉

謝謝卡米大~~
刪掉一個後就就解決了~~
/images/emoticon/emoticon37.gif

cool

0
s12873514
iT邦新手 5 級 ‧ 2018-01-22 00:33:39

不好意思,如果想要設計成一個text放channel_ID,另一個test輸入文字的話該怎麼設計呢

看更多先前的回應...收起先前的回應...
s12873514 iT邦新手 5 級 ‧ 2018-01-22 07:57:43 檢舉

沒事,解決了,謝謝

s12873514 iT邦新手 5 級 ‧ 2018-01-22 09:17:57 檢舉

原本的測試完感覺很奇怪
輸入channel_id: XXXX1 之後,再輸入channel_id: XXXX2
可是他會連XXXX1跟XXXX2一起發公告,而且沒辦法取消

不知道能不能改寫成一次只發送一個群組的方式?

我看不懂你想要作什麼XD

tarzan iT邦新手 5 級 ‧ 2018-03-03 16:11:19 檢舉

我想,s12873514的意思是希望能把公告發出給指定的 channel_id
我照著卡米大的程式,第一個公告只有最新一次有對話的 channel_id 收到;接著我去跟這個機器人"聊"一下,再送出一個公告,結果只有最新這兩個 channel_id 有收到公告。

要怎樣指定一個或多個或全 channel_id 發公告?

nienst iT邦新手 5 級 ‧ 2018-03-10 00:16:29 檢舉

卡米大大的範例,是用Channel.all.each do |channel|把整個Channel資料庫裡的channel_id逐一發送公告。

因此不要把指令放進迴圈,就可以單獨發訊息
push_to_line('Cecdd55d54053daa42f7501a8db081fa2',text)

# 紀錄頻道
if channel_id.include? 'C'
  Channel.find_or_create_by(channel_id: channel_id)
end

event_type = params['events'][0]['type']
if event_type == 'join'
  event_text = "感謝邀請~~\n可使用  /說明/  查看指令用法唷"
  push_to_line(channel_id,event_text)
  push_to_line('Cecdd55d54053daa42f7501a8db081fa2',"邀請 #{channel_id}")
end

if event_type == 'leave'
  event_text = "再見囉~~"
  push_to_line(channel_id,event_text)
  push_to_line('Cecdd55d54053daa42f7501a8db081fa2',"離開 #{channel_id}")
  channel_dell = Channel.find_by(channel_id: channel_id)
  channel_dell.destroy
end
0
nienst
iT邦新手 5 級 ‧ 2018-01-28 19:59:32

終於成功了....原來我的其實是免費版...所以才會一直出現 304號錯誤
因為我用LINE的後台,可以發送1千次訊息,也都可以成功發送。
但撿查很多次程式和步驟,也都找不出問題。
剛剛想說確認一下,才發現我是 免費版 (因為也是好幾個月前創的BOT帳號)
重新創 試用版的 終於成功了。

感恩卡米,讚嘆卡米

0
abc66622002
iT邦新手 5 級 ‧ 2018-06-01 17:37:17

不好意思我push_messages的後台一直找不到網頁,可是我在views和routers.rb都有新增
https://imgur.com/evryOeT
找不到原因...

只看這張圖無法幫你找原因喔

teemok iT邦新手 5 級 ‧ 2018-06-07 16:46:46 檢舉

我想大大是網址打錯了 要 /push_messages/new 才進得去 /push_messages 進不去

0
teemok
iT邦新手 5 級 ‧ 2018-06-06 17:14:49

請問卡米大大 離開群組的指令該怎麼打
https://developers.line.me/en/docs/messaging-api/reference/#leave-event
這邊看半天 實在看不懂

根據 https://github.com/line/line-bot-sdk-ruby/blob/master/lib/line/bot/client.rb

可以知道

如果是群組要寫這行:

line.leave_group(channel_id)

如果是聊天室要寫這行:

line.leave_room(channel_id)
teemok iT邦新手 5 級 ‧ 2018-06-07 19:07:12 檢舉
    # leave
    reply_text = leave(channel_id, received_text)
  # 離開
  def leave(channel_id, received_text)
    return nil unless received_text.include? '女神掰囉'

    response = reply_to_line('掰掰囉')

    line.leave_group(channel_id)
    line.leave_room(channel_id)
  end

感恩卡米大大 成功!

0
小鄭
iT邦新手 5 級 ‧ 2018-08-11 12:55:10

請問發公告是不要要先傳訊號才會收到~ 我第一個群組因為有傳訊息才有收到,第二個群組沒有,要先傳訊息 第二個群組才會收到

這要看你的 channel 什麼時候被記錄

0
nsyncs77
iT邦新手 5 級 ‧ 2018-11-18 18:39:20

請問我填完內容按送出後,沒有收到主動推播
terminal 顯示302 這樣是什麼地方出問題呢? 卡米大
https://ithelp.ithome.com.tw/upload/images/20181118/20113463glwVFzTKGH.png

nsyncs77 iT邦新手 5 級 ‧ 2018-11-19 00:24:10 檢舉

新增channel_id後,問題已自行解決

0
nienst
iT邦新手 5 級 ‧ 2019-02-03 10:00:30

請問卡米大大
發一百群 大約要30~50秒,有時後一百多群 2~3分鐘
有方法可以提升速度嗎?

或者是因為 試用版的 速限
Developer Trial 1,000/min per API endpoint 20,000/min per bot
Other plans 10,000/min per API endpoint 200,000/min per bot

有方法可以提升速度嗎? 有的,有一種東西叫做 sidekiq,它可以幫助你同時進行一些工作

0
40527145
iT邦新手 5 級 ‧ 2019-07-17 16:08:16

無法成功發送公告
請問改版後的line bot 註冊介面如何選擇或是查看自己是免費版還是試用版
不知道是不是因為帳號的原因導致無法發送https://ithelp.ithome.com.tw/upload/images/20190717/20118967PquJtcyCEz.png

我要留言

立即登入留言