2018/1/13

第二十五天:卡米狗學說話

卡米狗的學說話指令,最早期的語法設計是`卡米狗學說話;關鍵字;回覆`。用兩個半形分號作為分隔符號。為什麼選擇用分號作為分隔符號呢?因為我們的分隔符號不能出現在關鍵字或回覆內,所以要挑一個比較少人用的符號。 我們必須讓學說話功能的優先順序高於關鍵字回覆,這樣才能確保學說話指令不會被關鍵字覆蓋。 # 修改主程式 主程式: ``` 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 ``` 第一行: ``` reply_text = learn(received_text) ``` `learn` 是一個待會要寫的新函數,如果使用者說出一句話剛好符合學說話語法,那麼就回應`好哦~好哦~`並儲存結果。如果使用者說出一句話不符合學說話語法,就傳回 `nil`。 `nil` 是代表空值的意思。 第二行: ``` reply_text = keyword_reply(received_text) if reply_text.nil? ``` 如果 `reply_text` 是空值的話才進行關鍵字回覆的判斷。這樣就能確保學說話指令優先於關鍵字回覆。 我們要判斷輸入的文字開頭是不是`卡米狗學說話;`,要做到這件事情,我們需要學一點字串操作。 # 字串操作 這是一個字串: ``` 'ABCDEF' => "ABCDEF" ``` ### 字串的切割 這是字串的第一個字: ``` 'ABCDEF'[0] => "A" ``` 這是字串的第二個字: ``` 'ABCDEF'[1] => "B" ``` 以此類推, A,B,C,D,E,F 分別對應到: 0,1,2,3,4,5 這是字串的倒數第一個字 ``` 'ABCDEF'[-1] => "F" ``` 這是字串的倒數第二個字 ``` 'ABCDEF'[-2] => "E" ``` 以此類推, F,E,D,C,B,A 分別對應到: -1,-2,-3,-4,-5,-6 另外,你可以透過 Range 取得一個區間。 ``` 'ABCDEF'[0..1] => "AB" ``` ``` 'ABCDEF'[0..3] => "ABCD" ``` ``` 'ABCDEF'[3..-1] => "DEF" ``` # 字串的查詢 想知道字串中的 `A` 出現在哪裡: ``` 'ABCDEF'.index('A') => 0 ``` 想知道字串中的 `B` 出現在哪裡: ``` 'ABCDEF'.index('B') => 1 ``` 想知道字串中的 `C` 出現在哪裡: ``` 'ABCDEF'.index('C') => 2 ``` 找不到的情形會傳回 `nil`: ``` 'ABCDEF'.index('G') => nil ``` # 字串的相等 判斷兩個字串是否相等: ``` 'A' == 'A' => true ``` ``` '卡米狗學說話' == '卡米狗學說話' => true ``` ``` 'A' == 'B' => false ``` 學會以上三個技巧,就能夠解決大部分的問題。 # 學說話 現在我們要開始寫學說話函數,從空函數開始。 ``` # 學說話 def learn(received_text) end ``` 利用以上三個技巧,我們可以先取得前面七個字,看看是不是等於`卡米狗學說話;`,如果是的話,在後面的字串中找到分號作為分隔點。 先檢查開頭的字是不是`卡米狗學說話;`: ``` def learn(received_text) #如果開頭不是 卡米狗學說話; 就跳出 return nil unless received_text[0..6] == '卡米狗學說話;' end ``` `unless` 是 `if` 的相反,`unless` 是`除非`的意思。 除非前面七個字是`卡米狗學說話;`,不然就傳回 `nil`。 再來就是取得剩下來的字,以及找到第二個分號。 ``` # 學說話 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? 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) # 學習紀錄表 keyword_mapping = { 'QQ' => '神曲支援:https://www.youtube.com/watch?v=T0LfHEwEXXw&feature=youtu.be&t=1m13s', '我難過' => '神曲支援:https://www.youtube.com/watch?v=T0LfHEwEXXw&feature=youtu.be&t=1m13s' } # 查表 keyword_mapping[received_text] end ``` 要改成從資料庫查詢,其實昨天已經寫好了: ``` # 關鍵字回覆 def keyword_reply(received_text) mapping = KeywordMapping.where(keyword: received_text).last if mapping.nil? nil else mapping.message end end ``` 將查詢結果存到 `mapping` 變數中,然後檢查有沒有查到東西,如果有才傳回。 這裡可以加入一點浪漫: ``` # 關鍵字回覆 def keyword_reply(received_text) KeywordMapping.where(keyword: received_text).last&.message end ``` 如果 `&.` 的前面是 `nil`,那他就不會做後面的事,直接傳回 `nil`。 到這裡算是開發完成,可以上傳程式碼了。 # 對一下程式碼 你的程式碼應該長得差不多像這樣: ``` 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? # 傳送訊息到 line response = reply_to_line(reply_text) # 回應 200 head :ok 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 ``` 上傳後,我們還有一些工作要做: # postgresql 版本的指定 如果你在 `heroku logs -t` 上面看到這個錯誤訊息: ``` 2018-01-12T18:30:39.687847+00:00 heroku[web.1]: Starting process with command `bin/rails server -p 18506 -e production` 2018-01-12T18:30:45.609275+00:00 app[web.1]: /app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.4/lib/active_record/connection_adapters/connection_specification.rb:188:in `rescue in spec': Specified 'postgresql' for database adapter, but the gem is not loaded. Add `gem 'pg'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord). (Gem::LoadError) ``` 請將你的 Gemfile 修改一下,原本是: ``` group :development, :test do gem 'sqlite3' end group :production do gem 'pg' end ``` 改為 ``` group :development, :test do gem 'sqlite3' end group :production do gem 'pg', '~> 0.21.0' end ``` 由於三天前 pg 發布了[新版本](https://bitbucket.org/ged/ruby-pg/commits/tag/v1.0.0),而新版本似乎有點問題,所以我們需要指定安裝[穩定的版本](https://bitbucket.org/ged/ruby-pg/commits/tag/v0.21.0)。如果我們不指定版本,就會安裝到有問題的最新版。 # 安裝 Heroku 上的資料庫 使用 `heroku addons:create heroku-postgresql:hobby-dev` 指令弄一台免費的資料庫來玩玩。 ``` D:\只要有心,人人都可以作卡米狗\ironman>heroku addons:create heroku-postgresql:hobby-dev Creating heroku-postgresql:hobby-dev on people-all-love-kamigo... free Database has been created and is available ! This database is empty. If upgrading, you can transfer ! data from another database with pg:copy Created postgresql-concave-22896 as DATABASE_URL Use heroku addons:docs heroku-postgresql to view documentation D:\只要有心,人人都可以作卡米狗\ironman> ``` # 進行在 Heroku 上的資料庫遷移 在我們的小黑框輸入 `heroku run rake db:migrate`: ``` D:\只要有心,人人都可以作卡米狗\ironman>heroku run rake db:migrate Running rake db:migrate on people-all-love-kamigo... up, run.8915 (Free) D, [2018-01-12T18:43:37.665151 #4] DEBUG -- : (1852.2ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL PRIMARY KEY) D, [2018-01-12T18:43:38.188458 #4] DEBUG -- : (491.4ms) CREATE TABLE "ar_internal_metadata" ("key" character varying NOT NULL PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) D, [2018-01-12T18:43:38.194442 #4] DEBUG -- : (2.3ms) SELECT pg_try_advisory_lock(8162367372296191845) D, [2018-01-12T18:43:39.009656 #4] DEBUG -- : (2.2ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC I, [2018-01-12T18:43:39.012007 #4] INFO -- : Migrating to CreateKeywordMappings (20180110181744) D, [2018-01-12T18:43:39.015455 #4] DEBUG -- : (0.8ms) BEGIN == 20180110181744 CreateKeywordMappings: migrating ============================ -- create_table(:keyword_mappings) D, [2018-01-12T18:43:39.833168 #4] DEBUG -- : (815.6ms) CREATE TABLE "keyword_mappings" ("id" bigserial primary key, "keyword" character varying, "message" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL) -> 0.8170s == 20180110181744 CreateKeywordMappings: migrated (0.8174s) =================== D, [2018-01-12T18:43:39.853181 #4] DEBUG -- : SQL (6.3ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20180110181744"]] D, [2018-01-12T18:43:39.861444 #4] DEBUG -- : (5.7ms) COMMIT D, [2018-01-12T18:43:39.880216 #4] DEBUG -- : ActiveRecord::InternalMetadata Load (2.8ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]] D, [2018-01-12T18:43:39.896978 #4] DEBUG -- : (1.1ms) BEGIN D, [2018-01-12T18:43:39.899766 #4] DEBUG -- : SQL (1.0ms) INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key" [["key", "environment"], ["value", "production"], ["created_at", "2018-01-12 18:43:39.897705"], ["updated_at", "2018-01-12 18:43:39.897705"]] D, [2018-01-12T18:43:39.902418 #4] DEBUG -- : (1.8ms) COMMIT D, [2018-01-12T18:43:39.903709 #4] DEBUG -- : (0.8ms) SELECT pg_advisory_unlock(8162367372296191845) D:\只要有心,人人都可以作卡米狗\ironman> ``` # 進行實測 ![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCC42Z8ZcN5KoIt5415XPVjw94yXDjb6-IwVnTVXVf6AbFQx6QY_ZDK1oVs-RAKsF9eWf5ryyuEphUJ0yEcB_8ClWwrM8PMMSVdYTt-uGD4gNSPiuUOnaJHPlVPVxYAltuV8GzJNgL3NE/s1600/1.jpg) 順利~不順利的人請在底下留言並附上不順利的截圖,謝謝。 # 本日重點 - 學習了字串操作 - 學習了 Heroku 上的資料庫建置 - 做出了學說話和觸發說話 明天講推齊功能。

沒有留言: