iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 30
7

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

以下是一些示意圖,說明我們的 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
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
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

1
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 應該是氣象局那邊有改 要檢查一下惹

lee98064 iT邦新手 5 級 ‧ 2018-02-26 22:41:21 檢舉


謝謝卡米大

lee98064 iT邦新手 5 級 ‧ 2018-02-26 22:42:13 檢舉


謝謝卡米大

lee98064 iT邦新手 5 級 ‧ 2018-02-27 15:35:16 檢舉

還真的要改版了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 的工

lee98064 iT邦新手 5 級 ‧ 2018-03-01 20:59:26 檢舉

讚!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" 應該只有把程式碼推上去而已吧?

jeep1014 iT邦新手 5 級 ‧ 2018-05-16 10:18:26 檢舉

感恩卡米,讚嘆卡米

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

太神啦~

2
winter861047
iT邦新手 5 級 ‧ 2018-06-08 11:40:06

感恩卡米,讚嘆卡米
真的學了很多,跟著卡米大的腳步,讓我這個一直想使用自動化網路服務的麻瓜踏進了這扇門,
雖然建立後台和發公告那兩篇沒作成功~~~

另外,我想更進階的使用爬蟲來抓資料,上網查了之後,知道有nokogiri可用,後來也很順利用硬幹的方式抓到資料(超新手根本看不懂那些html的東西,只知道可以用nokogiri來搜尋<>標籤)。

但是但是但是那個網頁卻有設計下拉式的選單來選擇要查資料的日期,而且那個網頁預設都會抓前一天的資料,我直接連上網頁的話都會抓到前一天的資料···· 崩╰(〒皿〒)╯潰····

測試的時候我在C:\crawler 開啟cmd之後 輸入ruby wra_crawler.rb並執行
硬幹的程式碼如下:

require 'nokogiri'
require 'open-uri'
url = 'http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx'
doc = Nokogiri::HTML( open(url), nil, 'UTF-8' )

for i in 0..221
puts "第 #{i}個是"
puts doc.xpath("//td")[i].text
end

後來我用"爬蟲 遇到 下拉式選單"當成關鍵字搜尋GOOGLE,看到了https://www.ptt.cc/bbs/Python/M.1465556140.A.584.html
我想這可能是要用第12天、第14天、第15天的教學所說的send post的方法送出表單資料來抓他回傳給你的東西(應該填完欲查詢的日期之後,對方會回傳一個完整的html給我吧?),但是不知道該從何下手...

這邊附上目前作出來的部分,希望卡米大能開釋一下...

Gemfile的部分多了

#nokogiri
gem 'nokogiri'

以下是部分kamigo_controller的部分

require 'line/bot'
require 'nokogiri'
require 'open-uri'
class KamigoController < ApplicationController
  protect_from_forgery with: :null_session
def webhook
#查水庫資料,模仿查天氣
  reply_Reservoir = get_Reservoir(received_text)
  unless  reply_Reservoir.nil?
  response = reply_to_line(reply_Reservoir)
  head :ok
  return
  end
 #略
 #.
 #.
   def get_Reservoir(received_text)
    url = 'http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx'
    doc = Nokogiri::HTML( open(url), nil, 'UTF-8' )
    if received_text.include? '水庫'
        #曾文水庫
        i = 165
        response = ""
        response = response + doc.xpath("//td")[i].text + " "
        i = i + 7
        response = response + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "水位(公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "蓄水量(百分比%)=" + doc.xpath("//td")[i].text + "  \n"
        #南化水庫
        i = 176
        response = response + doc.xpath("//td")[i].text + " "
        i = i + 7
        response = response + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "水位(公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "蓄水量(百分比%)=" + doc.xpath("//td")[i].text + "  \n"
        #高屏溪攔河堰
        i = 198
        response = response + doc.xpath("//td")[i].text + " "
        i = i + 7
        response = response + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "水位(公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[i].text + "  "
        i = i + 1
        response = response + "蓄水量(百分比%)=" + doc.xpath("//td")[i].text + "  \n"
   end
  end

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

根據 第十二天:從瀏覽器認識 HTTP 協定 你可以觀察到你的目標網站:http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx 在你按下查詢按鈕時,會傳送出什麼資訊。

你可以用類似這篇的教學,或者相關的關鍵字,使用 ruby 做出跟你在 chrome 上看到的相同的 request。

OK我來試看看,感謝卡米大指點!

卡米大不好意思再請教一下,我要怎麼觀察自己發出的POST正不正確啊,第15天教的是我們設定rails server回覆給連線到他的人看到的,那我們要怎麼看自己發給別人網站的post request header呢?

我照著你上面那篇連結裡的範例,把我要填入的資料都填好,可是不知道為什麼接收到的還是6/7號的html資料,程式碼如下

require 'net/http'
uri = URI('http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx')
res = Net::HTTP.post_form(uri, 'ctl00$cphMain$cboSearch' => '所有水庫', 'ctl00$cphMain$ucDate$cboYear' => '2018', 'ctl00$cphMain$ucDate$cboMonth' => '6', 'ctl00$cphMain$cboDay' => '8')
puts res.body

以下是部分終端機的內容

<div id="ctl00_cphMain_ctl00">

<span></span>
<span></span>
<div id="search">
<span></span>
查詢方式:
<select name="ctl00$cphMain$cboSearch" id="ctl00_cphMain_cboSearch">
<option selected="selected" value="防汛重點水庫">防汛重點水庫</option>
<option value="所有水庫">所有水庫</option>
<option value="水庫及攔河堰">水庫及攔河堰</option>

</select>
<select name="ctl00$cphMain$ucDate$cboYear" id="ctl00_cphMain_ucDate_cboYear">
<option value="1970">1970</option>
<option value="1971">1971</option>
<option value="1972">1972</option>
<option value="1973">1973</option>
<option value="1974">1974</option>
<option value="1975">1975</option>
<option value="1976">1976</option>
<option value="1977">1977</option>
<option value="1978">1978</option>
<option value="1979">1979</option>
<option value="1980">1980</option>
<option value="1981">1981</option>
<option value="1982">1982</option>
<option value="1983">1983</option>
<option value="1984">1984</option>
<option value="1985">1985</option>
<option value="1986">1986</option>
<option value="1987">1987</option>
<option value="1988">1988</option>
<option value="1989">1989</option>
<option value="1990">1990</option>
<option value="1991">1991</option>
<option value="1992">1992</option>
<option value="1993">1993</option>
<option value="1994">1994</option>
<option value="1995">1995</option>
<option value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
<option value="1999">1999</option>
<option value="2000">2000</option>
<option value="2001">2001</option>
<option value="2002">2002</option>
<option value="2003">2003</option>
<option value="2004">2004</option>
<option value="2005">2005</option>
<option value="2006">2006</option>
<option value="2007">2007</option>
<option value="2008">2008</option>
<option value="2009">2009</option>
<option value="2010">2010</option>
<option value="2011">2011</option>
<option value="2012">2012</option>
<option value="2013">2013</option>
<option value="2014">2014</option>
<option value="2015">2015</option>
<option value="2016">2016</option>
<option value="2017">2017</option>
<option selected="selected" value="2018">2018</option>
<option value="2019">2019</option>

</select>年<select name="ctl00$cphMain$ucDate$cboMonth" id="ctl00_cphMain_ucDate_cboMonth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option selected="selected" value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>

</select>月<select name="ctl00$cphMain$ucDate$cboDay" id="ctl00_cphMain_ucDate_cboDay">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option selected="selected" value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="28">28</option>
<option value="29">29</option>
<option value="30">30</option>
<option value="31">31</option>

</select>日
<a id="ctl00_cphMain_btnQuery" title="查詢" class="search" href="javascript:__doPostBack('ctl00$cphMain$btnQuery','')"></a>
<a id="ctl00_cphMain_btnExcel" title="Excel下載" class="excel" href="javascript:__doPostBack('ctl00$cphMain$btnExcel','')"></a>

把你發出去的網址改成自己的網址 就是打到自己 這樣你就可以觀察你打出去的東西是什麼 可以參考寫爬蟲那一天的文章

卡米大感謝你的回覆,今天一整天一直在找方法觀察自己發出去的Post request到底有沒有錯,目前還沒有結果

看到你提供了這個方法之後,我馬上拿第十五天:從 Rails 認識 HTTP 協定 的程式碼來觀察看看(其實看不太懂15天是怎麼把180萬字整理成漂亮的排版...不過我想我直接拿來用,對他發出一個post request,應該也能看到些內容,吧?)

#第15天的程式碼
def request_headers
render plain: request.headers.to_h.reject{ |key, value|
  key.include? '.'
}.map{ |key, value|
  "#{key}: #{value}"
}.sort.join("\n")
end

#route.rb裡面是連到這個路徑
get '/kamigo/request_headers', to: 'kamigo#request_headers'

測試看看,和做到第15天的時候看到的一樣。

.
.
.

差點忘了是要測post,跑出一堆錯誤,後來再去route.rb補上下面兩行

post '/kamigo/request_headers', to: 'kamigo#request_headers'
post '/kamigo/request_body', to: 'kamigo#request_body'
#這次用來測試的程式碼
require 'net/http'
require 'nokogiri'
require 'open-uri'

uri = URI('http://localhost:3000/kamigo/request_headers')
res = Net::HTTP.post_form(uri, 'ctl00$cphMain$cboSearch' => '所有水庫')
doc = Nokogiri::HTML(res.body)
puts doc 
puts "\n"
uri = URI('http://localhost:3000/kamigo/request_body')
res = Net::HTTP.post_form(uri, 'ctl00$cphMain$cboSearch' => '所有水庫')
doc = Nokogiri::HTML(res.body)
puts doc

這邊是發出post request的終端機

#上面看起來和get差不多
C:\crawler>ruby 5wra_crawler.rb
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>CONTENT_LENGTH: 64
CONTENT_TYPE: application/x-www-form-urlencoded
GATEWAY_INTERFACE: CGI/1.2
HTTP_ACCEPT: */*
HTTP_ACCEPT_ENCODING: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
HTTP_HOST: localhost:3000
HTTP_USER_AGENT: Ruby
HTTP_VERSION: HTTP/1.1
ORIGINAL_FULLPATH: /kamigo/request_headers
ORIGINAL_SCRIPT_NAME:
PATH_INFO: /kamigo/request_headers
QUERY_STRING:
REMOTE_ADDR: 127.0.0.1
REQUEST_METHOD: POST
REQUEST_PATH: /kamigo/request_headers
REQUEST_URI: /kamigo/request_headers
ROUTES_71168780_SCRIPT_NAME:
SCRIPT_NAME:
SERVER_NAME: localhost
SERVER_PORT: 3000
SERVER_PROTOCOL: HTTP/1.1
SERVER_SOFTWARE: puma 3.11.4 Love Song
warden: Warden::Proxy:60507760 @config={:default_scope=>nil, :scope_defaults=>{}, :default_strategies=>{}, :intercept_401=>false, :failure_app=>#<:delegator:0x00000000071d0c98>}</:delegator:0x00000000071d0c98></p></body></html>


#下面這幾行不知道是什麼鬼
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>#<0x00000000072c21b0></0x00000000072c21b0></p></body></html>

看一下執行rails s 的終端機寫什麼

Started POST "/kamigo/request_body" for 127.0.0.1 at 2018-06-09 22:00:02 +0800
Processing by KamigoController#request_body as */*
  Parameters: {"ctl00$cphMain$cboSearch"=>"所有水庫"}
Can't verify CSRF token authenticity.
  Rendering text template
  Rendered text template (0.0ms)
Completed 200 OK in 16ms (Views: 6.8ms | ActiveRecord: 0.0ms)

他有收到"ctl00$cphMain$cboSearch"=>"所有水庫"耶!?這樣是不是表示post成功了啊

但是...如果是這樣,那為什麼我沒辦法透過類似的POST來改變http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx
到我想要的日期呢!?

真的好想改日期啊...

卡米大我成功改變日期了!
關鍵是還要多傳一個__VIEWSTATE

他裡面的值,一大堆東西,上網查了一下這好像是
ASP.net用来保存WEB控件回傳時的狀態值,_VIEWSTATE中存放了所有控件在ViewState中的狀態值

我比較擔心的是__VIEWSTATE這一大串值不知道什麼情況下會改變?這樣就變成要一直重新上傳程式碼了。

我用卡米狗教學前幾天你介紹給我們的Postman,把全部Request header下面的Form Data 全部打在body的key那邊(一開始還以為要打在params那裡,結果都沒用,不知道這個是作什麼用途的@@...有空在來查查看)
附個圖給大家參考參考



我的機器人終於可以做一點正事了,感動╰(〒皿〒)╯

最後附上測試的程式碼給大家參考看看

require 'net/http'
require 'nokogiri'
require 'open-uri'

	time1 = Time.new
uri = URI('http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx')
res = Net::HTTP.post_form(uri, 
	#'__EVENTTARGET' => 'ctl00$cphMain$btnQuery',
	'ctl00$cphMain$cboSearch' => '水庫及攔河堰', 
	'ctl00$cphMain$ucDate$cboYear' => time1.year, 
	'ctl00$cphMain$ucDate$cboMonth' => time1.month, 
	'ctl00$cphMain$ucDate$cboDay' => time1.day,
	#'__EVENTTARGET' => ''
	'__VIEWSTATE' => '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',
		#'ctl00$ctl02' => 'ctl00$cphMain$btnQuery',
	#'__ASYNCPOST' => 'true'
	#'__EVENTARGUMENT' => '',
	)
#puts res.body
doc = Nokogiri::HTML(res.body)

#for i in 0..9999
#puts "第 #{i}個是"
#puts doc.xpath("//td")[i].text
#end

      response = ""
      #曾文水庫
      axis = 638
response =  doc.xpath("//td")[axis].text + "\n"  \
+ doc.xpath("//td")[axis + 7].text + "\n" \
+ "水位(公尺)=" + doc.xpath("//td")[axis + 8 ].text + "\n" \
+ "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[axis + 9].text + "\n" \
+ "蓄水量(百分比%)=" + doc.xpath("//td")[axis + 10].text + "\n\n"
       puts response
      #鳳山水庫
      axis = 726
response =  doc.xpath("//td")[axis].text + "\n"  \
+ doc.xpath("//td")[axis + 7].text + "\n" \
+ "水位(公尺)=" + doc.xpath("//td")[axis + 8 ].text + "\n" \
+ "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[axis + 9].text + "\n" \
+ "蓄水量(百分比%)=" + doc.xpath("//td")[axis + 10].text + "\n\n"
       puts response
      #南化水庫
      axis = 649
response =  doc.xpath("//td")[axis].text + "\n"  \
+ doc.xpath("//td")[axis + 7].text + "\n" \
+ "水位(公尺)=" + doc.xpath("//td")[axis + 8 ].text + "\n" \
+ "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[axis + 9].text + "\n" \
+ "蓄水量(百分比%)=" + doc.xpath("//td")[axis + 10].text + "\n\n"
       puts response
      #高屏溪攔河堰
      axis = 759
 response =  doc.xpath("//td")[axis].text + "\n"  \
+ doc.xpath("//td")[axis + 7].text + "\n" \
+ "水位(公尺)=" + doc.xpath("//td")[axis + 8 ].text + "\n" \
+ "有效蓄水量(萬立方公尺)=" + doc.xpath("//td")[axis + 9].text + "\n" \
+ "蓄水量(百分比%)=" + doc.xpath("//td")[axis + 10].text + "\n\n"
       puts response

太神啦~

0
小鄭
iT邦新手 5 級 ‧ 2018-08-11 22:19:54

Client-ID 怎樣取的~我翻來翻去 研究不出來

我不是有附圖了嗎? @@

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

前面要加 Client-ID + 網站給你的碼嗎? 怎都沒反應

看更多先前的回應...收起先前的回應...
小鄭 iT邦新手 5 級 ‧ 2018-08-12 13:17:08 檢舉

請問這一章有改變啥嗎?做了老半天沒反應

小鄭 iT邦新手 5 級 ‧ 2018-08-12 21:20:42 檢舉

https://ithelp.ithome.com.tw/upload/images/20180812/20110539UzZLnpfChO.jpg
這是heroku logs 畫面 一直沒反應

對 前面要加 Client-ID

小鄭 iT邦新手 5 級 ‧ 2018-08-12 21:33:20 檢舉

是不是每次上船都要取得Client ID:碼 取的新的舊的還可以用嗎

小鄭 iT邦新手 5 級 ‧ 2018-08-12 21:41:11 檢舉

有了 要加Client-ID 感謝

0
ahstucs
iT邦新手 5 級 ‧ 2018-08-12 18:26:04

感恩卡米,讚嘆卡米
真的超級謝謝你的,至少讓我這個暑假沒有浪費的感覺
期待之後選單的教學啦~

/images/emoticon/emoticon12.gif

0
魚仔
iT邦新手 5 級 ‧ 2018-08-20 10:36:54

感動!!!終於做出來了~~
卡米大 謝謝你的教學文~第一次寫ruby on rail 發現真的好多便捷的東西 但因為不太懂常常死在便捷的地方
by the way
def get_weather_from_cwb
uri = URI('http://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js')
response = Net::HTTP.get(uri)

這邊的URI http要加s 才可以得到資料('https://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js')

喔喔喔感謝告知

0
zoe
iT邦新手 5 級 ‧ 2018-10-12 16:46:27

最後一個log一直有問題...
一開始自己打程式碼,
出現這三個問題
Can't verify CSRF token authenticity.
Completed 500 Internal Server Error in 1954ms
NoMethodError (undefined method +' for nil:NilClass):`

嘗試了三四種方法還是沒有解決

後來想說會不會程式碼有打錯
用copypaste的方式再log一次
還是出現一樣的問題...

2018-10-12T08:39:54.236628+00:00 app[web.1]: I, [2018-10-12T08:39:54.236490 #4]  INFO -- : [09202962-3044-4828-90a4-08b09912dc84] Started POST "/lunch/webhook" for 203.104.146.155 at 2018-10-12 08:39:54 +0000
2018-10-12T08:39:54.242575+00:00 app[web.1]: I, [2018-10-12T08:39:54.242437 #4]  INFO -- : [09202962-3044-4828-90a4-08b09912dc84] Processing by LunchController#webhook as */*
2018-10-12T08:39:54.242732+00:00 app[web.1]: I, [2018-10-12T08:39:54.242651 #4]  INFO -- : [09202962-3044-4828-90a4-08b09912dc84]   Parameters: {"events"=>[{"type"=>"message", "replyToken"=>"6de2dde0b8614705bff8f0529d0126fb", "source"=>{"userId"=>"Ucfe4509d3abac11c8947957be685bf0d", "type"=>"user"}, "timestamp"=>1539333593541, "message"=>{"type"=>"text", "id"=>"8706496702856", "text"=>"天氣"}}], "lunch"=>{"events"=>[{"type"=>"message", "replyToken"=>"6de2dde0b8614705bff8f0529d0126fb", "source"=>{"userId"=>"Ucfe4509d3abac11c8947957be685bf0d", "type"=>"user"}, "timestamp"=>1539333593541, "message"=>{"type"=>"text", "id"=>"8706496702856", "text"=>"天氣"}}]}}
2018-10-12T08:39:54.257095+00:00 app[web.1]: W, [2018-10-12T08:39:54.256947 #4]  WARN -- : [09202962-3044-4828-90a4-08b09912dc84] Can't verify CSRF token authenticity.
2018-10-12T08:39:56.197502+00:00 app[web.1]: I, [2018-10-12T08:39:56.197336 #4]  INFO -- : [09202962-3044-4828-90a4-08b09912dc84] Completed 500 Internal Server Error in 1954ms
2018-10-12T08:39:56.198210+00:00 app[web.1]: F, [2018-10-12T08:39:56.198121 #4] FATAL -- : [09202962-3044-4828-90a4-08b09912dc84]
2018-10-12T08:39:56.198339+00:00 app[web.1]: F, [2018-10-12T08:39:56.198253 #4] FATAL -- : [09202962-3044-4828-90a4-08b09912dc84] NoMethodError (undefined method `+' for nil:NilClass):
2018-10-12T08:39:56.198426+00:00 app[web.1]: F, [2018-10-12T08:39:56.198345 #4] FATAL -- : [09202962-3044-4828-90a4-08b09912dc84]
2018-10-12T08:39:56.198605+00:00 app[web.1]: F, [2018-10-12T08:39:56.198521 #4] FATAL -- : [09202962-3044-4828-90a4-08b09912dc84] app/controllers/lunch_controller.rb:54:in `get_weather_from_cwb'
2018-10-12T08:39:56.198608+00:00 app[web.1]: [09202962-3044-4828-90a4-08b09912dc84] app/controllers/lunch_controller.rb:47:in `get_weather'
2018-10-12T08:39:56.198610+00:00 app[web.1]: [09202962-3044-4828-90a4-08b09912dc84] app/controllers/lunch_controller.rb:8:in `webhook'
2018-10-12T08:39:56.213804+00:00 heroku[router]: at=info method=POST path="/lunch/webhook" host=whats-for-zoe-lunch.herokuapp.com request_id=09202962-3044-4828-90a4-08b09912dc84 fwd="203.104.146.155" dyno=web.1 connect=0ms service=1982ms status=500 bytes=1827 protocol=https

status 500 就是程式執行到一半掛掉了,所以是程式碼有問題

zoe iT邦新手 5 級 ‧ 2019-02-15 10:55:30 檢舉

謝謝米大

0
韓寶寶
iT邦新手 5 級 ‧ 2018-11-01 00:53:44

感恩卡米,讚嘆卡米

我有做出一個機器人,也能順利發出指令,要求BOT學習關鍵字,並發出我指定的回應。
目前並未打算讓其他人增加關鍵字,所以後台端的部份,雖然卡住無法解決,但也沒有很急迫需要修正。

想請問卡米大另一部份的問題。
https://ithelp.ithome.com.tw/questions/10191328
我這篇文章發問之後,有工程師前輩告訴我關於regexp規則的事情。
有爬了幾篇版上的文章,發現完全沒有概念下,看得一頭霧水。

https://ithelp.ithome.com.tw/questions/10191365
所以之後又發了這篇問題。
想請問卡米大有沒有推薦的0基礎新手適用的網站,我想去學習一下regexp規則,修改我的BOT。

如果你只是要做「包含」的判斷的話,可以參考這篇文章
https://stackoverflow.com/questions/9708409/rails-how-to-find-by-a-field-containing-a-certain-string

韓寶寶 iT邦新手 5 級 ‧ 2018-11-01 13:00:52 檢舉

謝謝卡米大。
我來研究一下。

0
hm5829
iT邦新手 5 級 ‧ 2018-11-01 19:13:04

請問卡米大,如果我想要在LINE機器人加入取得 webservice內資料的功能該朝什麼方向去研究?因為查過很多資料但是礙於程度太低的問題毫無頭緒所以只好來問問神人/images/emoticon/emoticon02.gif

這是我的 webservice 網址:http://210.242.237.231/HospInfo/AppReg.asmx

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

你想要用 ruby 串接這個?

https://github.com/savonrb/savon

hm5829 iT邦新手 5 級 ‧ 2018-11-02 08:09:46 檢舉

恩恩,雖然每個語言寫LINE BOT 都接觸過,但因為跟著你的文章慢慢學習最後想說用這個試試看,可是不知道從哪裡下手

我貼的那個連結裡面有串接 SOAP 的教學

hm5829 iT邦新手 5 級 ‧ 2018-11-04 12:45:26 檢舉

謝謝我會試試/images/emoticon/emoticon02.gif

0
stanly0726
iT邦新手 5 級 ‧ 2018-11-18 02:04:54

請問大大,我如果想要把line傳給bot的圖片上傳到imgur,再把網址傳回line的話,該怎麼做
您的教學文裡好像沒有提到檔案處理的部分,爬文也都看不懂。
麻煩大大回答了,感恩。

這個部分水很深哦~ 我有空再寫成完整的文章

(我絕對不會說我現在才看到
真的很抱歉!!
後來自己爬文終於成功了

nienst iT邦新手 5 級 ‧ 2020-03-18 22:00:34 檢舉

最近也在嘗試這一塊 但都沒成功 大大可以分享一下嗎?

0
julianwang422
iT邦新手 5 級 ‧ 2018-12-09 02:14:54

感恩卡米,讚嘆卡米
雖然中途發生了許多錯誤
不過在一個禮拜的時間內把所有的問題都成功解決
並把全部的功能做出來了
卡米大講解得十分詳細
而且在文章你面多了許多小提醒還有一些梗
讓我在看文章的時候不會覺得無聊反而感到有趣
希望這個世界上能夠有更多的卡米大
造福社會,造福人群

感謝你的支持/images/emoticon/emoticon02.gif

/images/emoticon/emoticon12.gif

0
Pangolin
iT邦新手 5 級 ‧ 2019-04-05 19:36:42

太感謝卡米狗大大了,觀看您的文章讓我收穫不少,但我是用node.js作為後端的語言,根據您提供的程式碼下去做改寫,另外圖片的部分,我是直接省略掉,我是用line提供的api送圖片。

結果圖片

送訊息

const linebot = require('linebot');

if(meg.indexOf("天氣")!=-1){
    console.log(weather_img);
    event.reply({
        type: 'image',
        originalContentUrl: weather_img,
        previewImageUrl: weather_img
    }).then( (data)=>{
        console.log(event.message);
    }).catch( (err)=>{
        console.log('error');
    });
}

爬蟲

const request = require("request");
const cheerio = require("cheerio");

function get_weather_from_cwb(){
	request({
		url: "https://www.cwb.gov.tw/V7/js/HDRadar_1000_n_val.js",
		method: "GET"
	}, (error, response, body)=>{
		if(error || !body)	return;
		var $ = cheerio.load(body);
		var start_idx = body.indexOf('","')+3;
		var end_idx = body.indexOf('"),');
		weather_img ="https://www.cwb.gov.tw"+body.substring(start_idx,end_idx);
	});
    //每十分鐘自動更新一次
	timer = setInterval(get_weather_from_cwb, 600000);
}
1
acegen
iT邦新手 5 級 ‧ 2019-06-17 15:42:54

老師您好,我想請問一下,要怎讓LINEBOT同時傳圖片訊息與文字訊息?我看了後台錯誤都是顯示ERR:400

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

傳一個陣列過去

acegen iT邦新手 5 級 ‧ 2019-06-18 18:14:30 檢舉

有點不大懂老師的意思,老師方便說得更詳細嗎?
btw,看LINE文件上說,最多可傳5個訊息過去,我想讓文字為一個,圖片為一個https://ithelp.ithome.com.tw/upload/images/20190618/201135182NnoGq8Ri5.jpg
類似以上的格式

    message = {
      type: 'text',
      text: reply_text
    }

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

原本是這樣傳遞一則訊息,改為傳遞一個陣列

    message = [
        {
          type: 'text',
          text: reply_text
        },
        {
          type: 'text',
          text: reply_text
        }
    ]

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

就會變成一次顯示兩則訊息

acegen iT邦新手 5 級 ‧ 2019-06-24 14:15:51 檢舉

好的!感謝老師!目前完成了可以一次丟出圖+文了,非常感謝

0
stanly0726
iT邦新手 5 級 ‧ 2019-08-28 20:49:15

請問
我在我的機器人裡加了上傳圖片到imgur的功能
我是用messaging API 的 get content 功能來取得圖片的binary file
然後再用imgur的API上傳圖片取得網址

但我發現有一個問題
當用戶用line傳送GIF給機器人時
使用get content 功能卻只能取的不會動的jpg檔
請問這是LINE的問題還是有甚麼方法可以取得用戶傳的GIF動圖檔

謝謝大大

wow 我沒抓過圖呢

0
TooBe
iT邦新手 5 級 ‧ 2020-01-13 22:56:25

不好意思我有兩個問題
第一個是我自己寫了一個爬股票的函式可是我在測試程式碼的時候loop do的時候他不會跑可是heroku logs上卻顯示有做完?
程式的函式
https://ithelp.ithome.com.tw/upload/images/20200113/20123632aLU8jymRiC.png
herokulogs
https://ithelp.ithome.com.tw/upload/images/20200113/20123632k7GH1frQgg.png
第二個問題我在除錯的時候想說把要看的東西回傳回去可是發現他只會回傳最後一行?為什麼不是回傳received_text = stock_date這個
https://ithelp.ithome.com.tw/upload/images/20200113/20123632Oumgf3sUV8.png
程式webhook長這樣
https://ithelp.ithome.com.tw/upload/images/20200113/2012363217WWv8WmsR.png

在ruby,回傳值會是程式的最後一行執行的結果,或者你可以寫

return 某個回傳值

但是後方的程式就不執行

TooBe iT邦新手 5 級 ‧ 2020-01-15 13:13:43 檢舉

第一個問題是我執行了可是heroku上顯示跑完可是line沒傳東西回來

0
TooBe
iT邦新手 5 級 ‧ 2020-01-20 16:28:35

請問我這樣批次抓目標的程式碼是可以的嗎?
目標是圖上每一行的資料
https://ithelp.ithome.com.tw/upload/images/20200120/201236329jHPHFxwsy.png
程式碼:
https://ithelp.ithome.com.tw/upload/images/20200120/20123632KV9CZIF6VM.png
https://ithelp.ithome.com.tw/upload/images/20200120/20123632XuQQX8s4dD.png
我有另一個問題是因為他是隨著每天交易會做新增所以我的break那邊這樣子是如果抓到空值就break這樣打是對的嗎?

0
KotoYeo
iT邦新手 5 級 ‧ 2020-04-10 19:00:00

https://ithelp.ithome.com.tw/upload/images/20200410/20126240bRlOy2EJq2.pnghttps://ithelp.ithome.com.tw/upload/images/20200410/20126240VbBATpfI4Y.png

我的BOT突然不回我了,然後就出現這些問題
有人可以幫幫忙嗎?QQ求救,感謝!!!

他找不到 keyword_mapping2s_controller.rb 這個檔案

KotoYeo iT邦新手 5 級 ‧ 2020-04-10 19:25:30 檢舉

請問這要怎麼解決呀?

把檔案給他阿

0
微笑
iT邦研究生 5 級 ‧ 2020-05-01 16:38:40

感恩卡米,讚嘆卡米~

/images/emoticon/emoticon12.gif

0
nillis
iT邦新手 5 級 ‧ 2020-10-27 11:23:38

感恩卡米,讚嘆卡米~

今年才看到文章開始學著
安裝過程也不是太順利後來用的ruby 2.5 postgresql 1.0.0
也是跌跌撞撞一路爬文google終於完成

非常謝謝你的介紹詳細
幾乎沒有寫過相關東西的我終於完成
雖然還有些問題
主要是 不知道是版本還是設定上面的問題 heroku上面的db試了很多次都沒辦法新增 更改 暫時都是先把heroku上面的db刪除 再次重新建立才可以

總而言之
感謝
還有些東西想做
謝謝你的文章

0
Ken(Bigcandy)
iT邦大師 1 級 ‧ 2021-02-10 21:24:43

感恩卡米,讚嘆卡米
現在才看到,好帥的文章

我要留言

立即登入留言