iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 30
4

今天就是最後一天惹,有些事情想跟你們講一下,那就是我們前幾天到底在幹嘛。

以下是一些示意圖,說明我們的 HTTP request 傳遞的路徑。

回覆訊息

Line app 指的是手機或PC版的 Line,Line server 在收到訊息後會透過 webhook url 傳遞給我們。接著我們會打 line.reply_message 傳訊息給 Line server,最後再由 Line server 傳給 Line app (最後這段可能不是 HTTP request)。

發公告

我們透過後台管理介面填入公告訊息,用 line.push_message 傳訊息給 Line server。

排程公告

有觀眾說想知道鬧鐘怎麼作,這裡再說明一下。

我們會用到 worker 來處理工作排程。首先是先在後台設定預約發訊息,然後將訊息儲存到工作清單,每個工作可以指定執行時間,接著就等時間到,worker 就會用 line.push_message 去打 Line server。

查天氣

查天氣就更複雜了,我們收到查天氣指令後,要先去氣象局取得圖片檔,然後再把圖上傳到 imgur,最後把圖片連結傳回給 Line server。

為什麼不是直接把氣象局的圖片傳給 Line server 呢?因為 Line server 要求圖檔必須是 https 開頭的網址,但是氣象局的圖檔連結卻是 http 開頭。

2018/3/1 補充:氣象局提供的連結已改為 https 所以可以不用串接 imgur 了,但本文保留串接 imgur 的範例。

2018/3/5 補充:氣象局提供的連結已改為 https 但不知為何 line 還是不吃,所以還是要串接 imgur。

那為什麼不是我們自己保存圖片就好呢?因為存圖片要占空間跟頻寬,所以我選擇用 imgur 的空間放圖。imgur 有一個蠻好的地方是,你可以直接把圖片網址給他,他就會幫你備份圖片了,所以我們不用真的把圖檔抓回來再上傳到 imgur。

查天氣的運作流程

我們作簡單一點,當有人說到天氣的時候就傳回一張雷達回波圖。我們需要作的所有事情是:

調查階段:

  • 學會怎麼抓到最新的雷達回波圖網址
  • 學會怎麼把圖檔弄到 imgur

實作階段:

  • 在主程式呼叫查天氣
  • 增加一個查天氣函數
  • 增加一個取得最新雷達回波圖的函數
  • 增加一個上傳圖片到 imgur 的函數
  • 傳送圖片到 line 的函數

一步步來吧。

學會怎麼抓到最新的雷達回波圖網址

當然,如果我們是用瀏覽器下載,那麼很簡單直接網頁打開右鍵->另存圖片就載好了。可是我們是要用程式去載圖,不是人工載圖。

所以我們要用程式去開啟網頁,然後從網頁原始碼裡面找到圖片連結就行了。

先開這個網頁:http://www.cwb.gov.tw/V7/observe/radar/

然後按下 Ctrl+U,就可以看到網頁原始碼了,把他認真的讀完之後會發現第 234~237 行很可疑,點進去看就會發現全都是圖檔連結,像這樣:http://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js

要能發現第 234~237 行很可疑,你必須要能看懂大部分的 html 跟 js,所以你得學會 html 跟 js。

如果你還沒學過 html 的話,可以參考看看:深入淺出立即上手的 HTML 網頁設計

如果你還沒學過 js 的話,也可以參考看看:JavaScript & jQuery 前端開發入門實戰

var HDRadar_1000_n_val=new Array(
new Array("2018/01/18 01:20","/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180120.png"),
new Array("2018/01/18 01:10","/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180110.png"),
new Array("2018/01/18 01:00","/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180100.png"),
new Array("2018/01/18 00:50","/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180050.png"),
...

這是 js 程式碼,我們需要的部分在第二行後半段:/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180120.png,這是網頁路徑,省略了網域的寫法。

把網域加回去就會是 http://www.cwb.gov.tw/V7/observe/radar/Data/HD_Radar/CV1_1000_201801180120.png

這就是我們要的圖片連結。

小結

http://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js 的原始碼,然後取出第二行的網頁路徑,最後在前面補上 http://www.cwb.gov.tw 就會是我們要的網址。

學會怎麼把圖檔弄到 imgur

imgur 有提供 api,這是說明文件:https://apidocs.imgur.com/#4b8da0b3-3e73-13f0-d60b-2ff715e8394f

使用 api 需要 Client-ID,這東西就跟 Line channel secret 那些東西差不多。

你可以透過這個網址:https://api.imgur.com/oauth2/addclient 取得你的 Client-ID。

照著填就可以。

小結

透過使用 imgur 提供的 api,我們可以很容易就上傳圖片到 imgur。

接下來是實作階段的部分。

在主程式呼叫查天氣

  def webhook
    # 查天氣
    reply_image = get_weather(received_text)

    # 有查到的話 後面的事情就不作了
    unless reply_image.nil?
      # 傳送訊息到 line
      response = reply_image_to_line(reply_image)

      # 回應 200
      head :ok

      return 
    end

    # 紀錄頻道
    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

我在最前面加入了這段程式碼:

# 查天氣
reply_image = get_weather(received_text)

# 有查到的話 後面的事情就不作了
unless reply_image.nil?
  # 傳送訊息到 line
  response = reply_image_to_line(reply_image)

  # 回應 200
  head :ok

  return 
end

我們要作一個查天氣函數 get_weather 如果輸入的文字包含天氣,就傳回 https 的雷達回波圖網址,然後就將圖片傳回給 line,這裡因為之前都是傳文字而已,所以還要多作一個函數 reply_image_to_line 來傳圖片。

增加一個查天氣函數

  def get_weather(received_text)
    return nil unless received_text.include? '天氣'
    upload_to_imgur(get_weather_from_cwb)
  end

第一行是說如果輸入的文字不包含天氣,就傳回 nil。

第二行呼叫了兩個函數,第一個函數是 get_weather_from_cwb,這是取得雷達回波圖的函數,會得到一個網址,再把這個網址傳給 upload_to_imgur 這個上傳圖片到 imgur 的函數。

增加一個取得最新雷達回波圖的函數

第十六天:做一個最簡單的爬蟲學到的在 rails 發 HTTP request 跟在第二十五天:卡米狗學說話學到的字串處理又要派上用場了,就跟你說前面的文章都是在打基礎吧,漏掉一篇你就做不出來了。

  def get_weather_from_cwb
    uri = URI('http://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js')
    response = Net::HTTP.get(uri)
    start_index = response.index('","') + 3
    end_index = response.index('"),') - 1
    "http://www.cwb.gov.tw" + response[start_index..end_index]
  end

前兩行就是第十六天講過的,後三行就是第二十五天講過的。比較難懂的可能會是第三行跟第四行,先看一下這張圖:

總而言之就是網址的開頭前面是 "," 後面是 "), 如果你有學過 js 應該就會知道,這個開頭跟結尾應該是不會錯的,所以我們決定取出介於這中間的字。

這行是在抓起點:

start_index = response.index('","') + 3

這是在抓終點:

end_index = response.index('"),') - 1

增加一個上傳圖片到 imgur 的函數

  def upload_to_imgur(image_url)
    url = URI("https://api.imgur.com/3/image")
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true
    request = Net::HTTP::Post.new(url)
    request["authorization"] = 'Client-ID be2d83405627ab8'

    request.set_form_data({"image" => image_url})
    response = http.request(request)
    json = JSON.parse(response.read_body)
    begin
      json['data']['link'].gsub("http:","https:")
    rescue
      nil
    end
  end

我們設定好 request header 和 request body 之後打一個 post request 出去,他會返回一個 json,接著我作了 json 的解析,並且在解析失敗時傳回 nil,確保程式不會隨意掛點。

request["authorization"] = 'Client-ID be2d83405627ab8'

這行是要填入你自己的 Client-ID,be2d83405627ab8 是我亂打的。

傳送圖片到 line 的函數

  # 傳送圖片到 line
  def reply_image_to_line(reply_image)
    return nil if reply_image.nil?
    
    # 取得 reply token
    reply_token = params['events'][0]['replyToken']
    
    # 設定回覆訊息
    message = {
      type: "image",
      originalContentUrl: reply_image,
      previewImageUrl: reply_image
    }

    # 傳送訊息
    line.reply_message(reply_token, message)
  end

其實跟傳文字幾乎一樣,只差在 message 裡面不一樣而已。

上傳實測

成功!

本日重點

如果你原本是完全不會寫程式,你從第一篇一直看到這篇,最後有作出東西的話,請在底下留言:「感恩卡米,讚嘆卡米」,讓我能證明只要有心,人人都可以作卡米狗是真的。


上一篇
第二十九天:卡米狗發公告
下一篇
只要有心,人人都可以作卡米狗 - 完賽心得
系列文
只要有心,人人都可以做卡米狗33
2
luke90329
iT邦新手 5 級 ‧ 2018-01-18 18:50:55

卡米大 有個地方你打錯了

  def get_weather(received_text)
    return nil unless received_text.include? '天氣'
    imgur(get_weather_from_cwb)
  end

應該改成

  def get_weather(received_text)
    return nil unless received_text.include? '天氣'
    upload_to_imgur(get_weather_from_cwb)
  end

感恩卡米,讚嘆卡米xDD

感謝抓錯 /images/emoticon/emoticon37.gif

0
lee98064
iT邦新手 5 級 ‧ 2018-01-19 19:17:26

感恩卡米,讚嘆卡米
也謝謝卡米大大的教學呦~~
/images/emoticon/emoticon07.gif

/images/emoticon/emoticon02.gif

0
s12873514
iT邦新手 5 級 ‧ 2018-01-21 21:17:29

請問一下,如果單純想要傳imgur圖片上line的話........

你遇到什麼問題是這篇沒有講到的嗎?

0
nienst
iT邦新手 5 級 ‧ 2018-01-28 10:46:20

感恩卡米,讚嘆卡米

之前就一直想要學 LINE_BOT
爬過很多文章,但只學會複製貼上範例的 "回音機器人"
光是照他們的Heroku教學,我就摸了半個月才成功...
其他都看不懂,也不知道如何修改成我想要的樣子
這30天的課程裡,學到了很多,也知道該去哪邊充實自己欠缺的部分。

雖然第29天的發公告我還沒成功做完,但我會好好的看相關的說明,試者努力完成。
有問題在跟你請教

我剛剛終於試成功了,開心

感恩卡米,讚嘆卡米

0
cyrc
iT邦新手 5 級 ‧ 2018-01-29 11:21:07

感恩卡米,讚嘆卡米

雖然我會寫程式,只是不懂網頁相關~前端後端~:D

0
ninjazyx
iT邦新手 5 級 ‧ 2018-01-31 14:50:08

感恩卡米,讚嘆卡米

0
ss012932
iT邦新手 5 級 ‧ 2018-01-31 19:29:55

感恩卡米,讚嘆卡米
感謝卡米大的細心用心
讓我們可以學習
雖然我還差最後一步QAQQ~
圖片出不來啊...!!https://ithelp.ithome.com.tw/upload/images/20180131/20108477Wth9nx2gXO.jpg

0
nienst
iT邦新手 5 級 ‧ 2018-02-23 22:52:37

卡米大大好
我想要用關鍵字陣列,判斷字串裡是否有包含陣列裡的值
但 .include? 似乎無法用 陣列

我期望的結果(陣列想讓使用者自行建立在資料庫)
Europe_UnitedStates_Area = ['美國','紐約','倫敦']
if text.include? Europe_UnitedStates_Area
P "歐美區"
end

目前使用的方式
if (text.include? '美國' or text.include? '紐約' or text.include? '倫敦')

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

當你需要一個函數 但是發現內建不支援的時候 可以自己寫 舉例來說

class String
  def include_any?(array)
    array.each do |str|
      return true if self.include? str
    end
    false
  end
end

你可以作一個 string.rb 檔案,寫入上面的程式碼,然後把這個檔案放在 config/inializers 資料夾下

然後你就可以寫 text.include_any? 你的陣列

nienst iT邦新手 5 級‧ 2018-02-24 18:25:37 檢舉

/images/emoticon/emoticon41.gif
原來如此 感謝卡米大大

nienst iT邦新手 5 級‧ 2018-02-25 15:14:15 檢舉

後來測試了很久... 資料庫欄位是不是無法定義成array呀
add_column :channel_statuses, :keyword_array, :array

試很久試不出來... 只好改回string
再寫了一個函數,把string轉成array
再用include_any? 做判斷
ㄎㄎ 感覺這樣好迂迴...
而且目前只能做 關鍵字的 or 篩選
我還再思考 怎麼再加一層 and 的篩選

這是今天試很久寫出來的

取得關鍵字篩選

def channel_keyword_array(channel_id)
channel_keyword_text = ChannelStatus.where(channel_id: channel_id).last&.keyword_array

p channel_keyword_text

re_keyword_array=[]
comma_index = channel_keyword_text.index(',')
until comma_index.nil?
  comma_index = channel_keyword_text.index(',')
  p "comma_index = #{comma_index}"
  re_keyword_array << channel_keyword_text[0..comma_index-1] unless comma_index.nil?
  p re_keyword_array
  channel_keyword_text = channel_keyword_text[comma_index+1..-1] unless comma_index.nil?
  p channel_keyword_text
end
re_keyword_array << channel_keyword_text
p re_keyword_array

return re_keyword_array

end

  1. 資料庫欄位可以定義成 array
  2. 有時候會透過寫入多筆資料進資料庫,然後用查詢的方式達成
  3. 多重條件的比對本來就很複雜
nienst iT邦新手 5 級‧ 2018-02-26 20:52:47 檢舉

疑? 那是我定義的方式有問題嗎?
add_column :channel_statuses, :keyword_array, :array

我有空再多試幾次看看,應該是有哪個步驟錯了
/images/emoticon/emoticon41.gif

我猜應該是類似這樣:

add_column :channel_statuses, :keyword_array, :string, array: true

我沒有試過 你可以試一下

nienst iT邦新手 5 級‧ 2018-03-02 00:52:17 檢舉

不得不再次的 感恩卡米 讚嘆卡米 從不帶人走冤路
雖然我還是不死心了又試了幾次 ARRAY
這次我有再認真的看錯誤訊息
Caused by:
PG::SyntaxError: ERROR: syntax error at or near "array"
LINE 1: ALTER TABLE "channel_statuses" ADD "boss_array" array

Caused by:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near
"array"
LINE 1: ALTER TABLE "channel_statuses" ADD "boss_array" array

雖然 還是看不懂 試不出來

但我一嘗試卡米大大的方法~~~
-- add_column(:channel_statuses, :boss_array, :string, {:array=>true})
D, [2018-03-01T15:43:27.322201 #4] DEBUG -- : (1.3ms) ALTER TABLE "channel_s
tatuses" ADD "boss_array" character varying[]
-> 0.0015s
== 20180301152626 AddBossToChannelstatus: migrated (0.0048s) ==================

馬上就嚐到 成功的喜悅了
雖然我剛學RUBY不久 無法參透 WHY
總之 還是先筆記起來 呵呵

不一定都是看錯誤訊息,你知道 keyword 是 array 你可以試著 google 看看

nienst iT邦新手 5 級‧ 2018-03-02 19:31:48 檢舉

恩 我之前是用錯誤訊和add_column的關鍵字去查
剛剛用 add_column array 確實有查到這個方法
感謝卡米大大/images/emoticon/emoticon41.gif

0
lee98064
iT邦新手 5 級 ‧ 2018-02-26 20:30:53

卡米大大晚上好XDD
今天在用查天氣的功能時忽然發現沒反應,logs出現這一行

26T12:17:11.971082 #4] FATAL -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c] NoMethodError (undefined method `+' for nil:NilClass):

完整的:

2018-02-26T12:17:11.970197+00:00 app[web.1]: I, [2018-02-26T12:17:11.970047 #4]  INFO -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c] Completed 401 Unauthorized in 1059ms (ActiveRecord: 3.1ms)
2018-02-26T12:17:11.971047+00:00 app[web.1]: F, [2018-02-26T12:17:11.970958 #4] FATAL -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c]
2018-02-26T12:17:11.971169+00:00 app[web.1]: F, [2018-02-26T12:17:11.971082 #4] FATAL -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c] NoMethodError (undefined method `+' for nil:NilClass):
2018-02-26T12:17:11.971248+00:00 app[web.1]: F, [2018-02-26T12:17:11.971173 #4] FATAL -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c]
2018-02-26T12:17:11.971419+00:00 app[web.1]: F, [2018-02-26T12:17:11.971341 #4] FATAL -- : [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c] app/controllers/kamigo_controller.rb:243:in `get_weather_from_cwb'
2018-02-26T12:17:11.971421+00:00 app[web.1]: [7e9deecc-0bdd-4cc8-be15-b4fd3b31ac3c] app/controllers/kamigo_controller.rb:232:in `get_weather'

想問一下要怎麼處理XD
然後我試了一下卡米狗也掛了XDD

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

QQ 應該是氣象局那邊有改 要檢查一下惹


謝謝卡米大


謝謝卡米大

還真的要改版了XDD
https://m.facebook.com/story.php?story_fbid=1851088648244358&id=129328600420380

然後我改抓mobile版網頁的JS檔就正常了XDD
https://www.cwb.gov.tw/V7/js/HDRadar_TW_1000_n_val.js

然後忽然發現中央氣象局更新成https了XDDD
看來圖可以直接丟LINE不用imgur了XDD

喔喔喔喔 我還沒改XD

改好了,其實就是幫 http 加 s 就改好了,然後圖片網址真的可以直接丟給 line,就省去了 imgur 的工

讚!XDDD

0
tarzan
iT邦新手 5 級 ‧ 2018-03-04 02:49:27

感恩卡米,讚嘆卡米

我不會寫程式,沒學過 Html、js,依照卡米大每篇教學和 Q&A,還有卡米大提供的網路參考資訊,似懂非懂地不斷試誤、修改,總算把這30篇教過的功能全部成功作出!(雖然很多我自己想改的功能,多數殘念中...)

卡米大在第26天提到的還可以做的事情,期待後續教學:

  • 讓卡米狗能抽籤
  • 讓卡米狗能擷取用戶的使用者名稱以及大頭貼
  • 讓卡米狗能接收及傳送貼圖
  • 讓卡米狗能傳送含有按鈕的選單
  • 製作小遊戲,比方說井字遊戲

太神啦~

0
jeep1014
iT邦新手 5 級 ‧ 2018-05-16 08:10:14

感恩卡米,讚嘆卡米

真的一步一步做,就能做出來,不像很多網站跟著做,結果不知道是自己太弱還是偷藏步,最後只能宣告放棄,跟著卡米大一步一步,我也做出一隻了耶~~~

另外想請問卡米大,要如何在本地端就能更新heroku上的資料庫內容呢? "heroku run rake db:migrate" 應該只有把程式碼推上去而已吧?

感恩卡米,讚嘆卡米

卡米大,我想我應該找到了,直接在heroku console下 "KeywordMapping.create({keyword:"Q2", message:"A2"})"就能直接在遠端新增了~~~

太神啦~

我要留言

立即登入留言