2011年6月26日 星期日

用Ruby打造噗浪機器人

無意間發現tonytonyjan卡提諾論壇-軟體研討區發表的一篇打造噗浪機器人程式,在此轉貼供有興趣的專研。
瀏覽這一頁之前,建議先看過豈不走#應聲蟲機器人,有助於瞭解程式的運作。
這一頁展示各種利用TJPlurker製作機器人的技巧,僅供參考,當然也歡迎各位在下方分享意見及發問。

基本架構

所有機器人的製作遵循以下步驟:
  1. 初始化TJPlurker::Robot物件
  2. 初始化TJPlurker::Service物件
  3. 註冊Service到Robot
  4. 啟動Robot

繼承

首先介紹的方法比較直觀,就是繼承自TJPlurker::Service,並且override serve。

require 'tjplurk'
robot = TJPlurker::Robot.new("YOUR_API_KEY", "YOUR_ID", "YOUR_PASSWORD")
class MyService < TJPlurker::Service
    def serve tjp, data
        # 你的服務內容
    end
end

s = MyService.new
robot.add_service(s)
robot.start

參數也可以是類別:

robot.add_service(MyService)
# 等同robot.add_service(MyService.new)

將條件分離

寫機器人免不了判斷使用者輸入了什麼,然後再決定執行什麼,例如你要做一個只針對首PO,且文字內容有"哈哈"的噗做出"Haha"的回應,可以這麼寫:

class MyService < TJPlurker::Service
    def serve tjp, data
        return if data.type != "new_plurk"
        return unless data.content =~ /哈哈/
        tjp.response_add("Haha")
    end
end

或者你可以複寫TJPlurker::Service#decide,作為判斷之用,如果最後回傳的為true(neither nil nor false),那麼就該服務就會執行#action

class MyService < TJPlurker::Service
    def decide tjp, data
        return true if data.type == "new_plurk" && data.content =~ /哈哈/
    end
    def action tjp, data
        tjp.response_add("Haha")
    end
end

Block

Ruby有個最強大的特色就是Block,也可以利用它來製作機器人。

require 'tjplurker'
robot = TJPlurker::Robot.new("YOUR_API_KEY", "YOUR_ID", "YOUR_PASSWORD")

your_service = TJPlurker::Service.new{|tjplurker, data|
    # 你的服務內容
}

robot.add_service(your_service)
robot.start

進階架構

如果你要製作的是多功能的機器人,這裡提供一些參考。

以物件

將Service物件包在module裡面。

module Services
    @@service_a = TJPlurker::Service.new{|tjp, data|
        
    }
    @@service_b = TJPlurker::Service.new{|tjp, data|
    }
    
    @@service_c = TJPlurker::Service.new{|tjp, data|
    }
end
Services.class_variables.each{|var|
        robot.add_service(Services.class_variable_get(var))
}
robot.start

以類別

這個方法是用繼承和overriding達到一樣的效果,優點是個別服務可以有自己的成員、方法,適合比較複雜的服務,通常建議分成兩個檔案分離服務與機器人。

module Services
    class S1 < TJPlurker::Service
        def serve tjp, data
        end
    end
    class S2 < TJPlurker::Service
        def serve tjp, data
        end
    end
    class S3 < TJPlurker::Service
        def serve tjp, data
        end
    end
end

include Services
Services.constants.each{|const|
        robot.add_service(Services.const_get(const))
}
robot.start

最簡單的機器人

如果建構Service物件的時候不加任何參數,預設的服務是把所有在河道上聽到的東西印出來。

robot.add_service(TJPlurker::Service.new)
robot.start

這和以下的程式碼意義相同。

serv = TJPlurker::Service.new{|tjp, data|
    p data}
robot.add_service(serv)
robot.start

僅針對新噗/回應

有時候機器人只會針對新的噗有效(例如掰噗),有些則是可以在噗的回應中與使用者互動(例如每少女噗工廠的堆疊概念),這邊示範怎麼針對新噗或者回應做出相對應的服務。

service_echo = TJPlurker::Service.new{|tjplurker, data|
    if data.type == "new_plurk"
        tjplurker.response_add(data.plurk_id, "I heard a new Plurk.")
    else if data.type == "new_response"
        tjplurker.response_add(data.plurk_id, "I heard a new Response.")
    end
}

回話功能

如何作到掰噗和卡馬警衛的功能?這裡提供一個解,利用正規表達式去比對內容,決定要回什麼話,當然,簡單的作法是直接寫在程式碼中,否則使用資料庫會更合適。

service_response = TJPlurker::Service.new{|tjplurker, data|
    answer = ""
    case data.content
        when /早安/
            answer = "早安啊~"
        when /晚安/
            answer = "晚安,祝你有個好夢^^"
        when /作業/
            answer = "喔不,作業……"
    end
    tjplurker.response_add(data.plurk_id, answer)
}


如果想要有人使用回應功能指定對象時(例如@tonytonyjan:)才會回應(例如卡馬警衛就是用這個方法)。並不能這樣寫:

case data.content
    when /@tonytonyjan/
        tjp.response_add(data.plurk.id, "Are you calling me?")
end

因為這類的訊息,使用Plurk API所監聽到的內容會長這樣:

<a href="http://www.plurk.com/tonytonyjan" class="ex_link">tonytonyjan</a>:

Plurk是用網址來判斷的,所以只要將程式碼改成如下即可運作:

case data.content
    when /http:\/\/www.plurk.com\/tonytonyjan/
        tjp.response_add(data.plurk.id, "Are you calling me?")
end

記憶/堆疊

有時候你會希望使用者發噗觸發條件的時候,在該噗之後的回應裡,機器人都能與使用者互動,就像美少女噗工廠中堆疊的概念一樣,然後過一段時間之後,堆疊就會消失。類似應用可以用來製作猜數字、撿石頭、純聊天等功能,當然,設計大如美少女噗工廠的角色扮演遊戲也不是不可能。
使用方式和將條件分離一樣

class YourService < TJPlurker::ThreadService
    def decide tjp, data
    end

    def action tjp, data
    end

    def final tjp, data
    end
end

TJPlurker::ThreadService繼承自TJPlurker::Service,不同的地方在於:
  • ThreadService#decide只做用在首PO(data.type=="new_plurk"),如果回傳true(neither nil nor false),會產生一個Thread,在一定的時間內記得這這個討論串
  • ThreadService#action只作用在回覆(data.type=="new_response"),但不會作用在沒有通過#decide的噗裡面
  • ThreadService#final會在Thread關閉之後執行,你可以透過此告知使用者功能已經關閉了
如果不用類別的話,也可以這樣寫。

service = TJPlurker::ThreadService.new{|tjp, data|
    tjp.response_add(data.plurk_id, data.content)
}
robot.add_service(service)
robot.start

預設#decide是true,#final則是什麼都不做。