[VS2010] 雲端應用開發:在雲端上的噗浪機器人 (Part 3)

[VS2010] 雲端應用開發:在雲端上的噗浪機器人 (Part 3)

前兩回我們已經將 Plurk Bot 的基本功能都實作出來了,包含自動張貼訊息以及自動加入好友的能力,這次我們來加入能夠自動應答 (auto-answer) 的功能。

 

筆者相信只要有在玩噗浪的玩家,都會多多少少加入幾個機器人到自己的好友,這些機器人都會有一種功能,就是在對機器人發出特定規則的訊息時,機器人會自動回應你要的訊息,像是針對 Youtube 搜尋影片並回應連結的機器人 [邦妮妹妹]:

 

image

 

或是會在發新噗時自動在噗中加入回應的 [女僕小C] :

 

image

 

都有這樣的能力,它們會在訂閱者 (subscriber) 發出特定訊息 (或是發新訊息) 時,將機器人自己的訊息,可能是應答,可能是一些簡單的句子等,自動加入訂閱者的噗浪中作為回應,讓使用者會覺得有人在跟他們對話一樣,但事實上對話的是機器人,而不是人類。通常這是機器人應該要有的基本功能,而且要視機器人的功能需求來決定要回應的語句類型,或是是否要求使用者利用特定的格式或發言類型 (qualifier) 來發出訊息,如此機器人才得以在設定的行為下給予回應。

 

因此,若要在機器人中加入自動應答功能,必須要滿足幾個條件:

 

1. 設計好機器人對答的規則,這個可以是很簡單的關鍵字,詞,也可以難到使用語意分析 (semantic analysis) 來抓取訊息的真正意義。

2. 在機器人中加入抓取噗浪訊息 (plurk message) 的功能,以決定是否有新的訊息被加入。

3. 加入回應能力,以讓機器人得以回應指定的訊息。

 

 

例如在本文所實作的機器人中,筆者定義了這樣的規則:

 

 

 

1. 必須要用 "問" (qualifier =“asks”) 來發訊息。
2. 訊息格式必須是 “股票成交價:[股票代號]”,股票代號為台灣證券交易所登錄的上市公司股票代碼。

 

接著,在機器人中抓取來自噗浪的訊息,噗浪提供了這一組 API:

 

  • API/Timeline/getPlurks:由噗浪中抓取訊息,預設為抓取 20 個噗。
  • API/Timeline/getPlurk:由噗浪中抓取指定噗代碼 (plurk_id) 的噗浪訊息。
  • API/Responses/get:由噗浪中抓取指定噗的回應,可以設定 offset 值以抓取不同的回應清單。
  • API/Responses/responseAdd:在指定的噗中張貼回應訊息。

 

API/Timeline/getPlurks() 以及 API/Responses/get() 的回傳值為 JSON 格式,可以利用 Codeplex.com 上的 Json.NET 函式庫來解析並取出必要的欄位,這點筆者已經在 Plurk API .NET Component 處理掉了,並包裝成一個 Plurk 物件:

 

public class Plurk
{
    public int ResponsesSeen { get; private set; }
    public string Qualifier { get; private set; }
    public int PlurkID { get; private set; }
    public int ResponsesCount { get; private set; }
    public string LimitTo { get; private set; }
    public PlurkNoComments NoComments { get; private set; }
    public PlurkUnreadFlag UnreadState { get; private set; }
    public string Language { get; private set; }
    public string ContentRaw { get; private set; }
    public int UserID { get; private set; }
    public PlurkType Type { get; private set; }
    public string Content { get; private set; }
    public string QualifierTranslated { get; private set; }
    public string Posted { get; private set; }
    public int OwnerID { get; private set; }
    public Plurk(string PlurkData)
    {
        JObject plurkData = JObject.Parse(PlurkData);
        /*
         * {"responses_seen": 0, "qualifier": "thinks", "plurk_id": 90812, "response_count": 0, "limited_to": null,
         * "no_comments": 0, "is_unread": 1, "lang": "en", "content_raw": "test me out", "user_id": 1, "plurk_type": 0,
         * "content": "test me out", "qualifier_translated": "thinks", "posted": "Fri, 05 Jun 2009 23:07:13 GMT", "owner_id": 1}
        */

        if (plurkData["user_id"] != null && !string.IsNullOrEmpty(plurkData["user_id"].ToString()))
            this.UserID = Convert.ToInt32(plurkData["user_id"].ToString());
        if (plurkData["owner_id"] != null && !string.IsNullOrEmpty(plurkData["owner_id"].ToString()))
            this.OwnerID = Convert.ToInt32(plurkData["owner_id"].ToString());
        if (plurkData["plurk_id"] != null && !string.IsNullOrEmpty(plurkData["plurk_id"].ToString()))
            this.PlurkID = Convert.ToInt32(plurkData["plurk_id"].ToString());
        if (plurkData["response_count"] != null && !string.IsNullOrEmpty(plurkData["response_count"].ToString()))
            this.ResponsesCount = Convert.ToInt32(plurkData["response_count"].ToString());
        if (plurkData["responses_seen"] != null && !string.IsNullOrEmpty(plurkData["responses_seen"].ToString()))
            this.ResponsesSeen = Convert.ToInt32(plurkData["responses_seen"].ToString());
        if (plurkData["qualifier"] != null && !string.IsNullOrEmpty(plurkData["qualifier"].ToString()))
            this.Qualifier = plurkData["qualifier"].ToString().Replace("\"", "");
        if (plurkData["qualifier_translated"] != null && !string.IsNullOrEmpty(plurkData["qualifier_translated"].ToString()))
            this.QualifierTranslated = plurkData["qualifier_translated"].ToString().Replace("\"", "");
        if (plurkData["content"] != null && !string.IsNullOrEmpty(plurkData["content"].ToString()))
            this.Content = plurkData["content"].ToString().Replace("\"", "");
        if (plurkData["content_raw"] != null && !string.IsNullOrEmpty(plurkData["content_raw"].ToString()))
            this.ContentRaw = plurkData["content_raw"].ToString().Replace("\"", "");
        if (plurkData["limited_to"] != null && !string.IsNullOrEmpty(plurkData["limited_to"].ToString())
            && plurkData["limited_to"].ToString().Replace("\"", "") != "null")
            this.LimitTo = plurkData["limited_to"].ToString().Replace("\"", "");
        if (plurkData["lang"] != null && !string.IsNullOrEmpty(plurkData["lang"].ToString()))
            this.Language = plurkData["lang"].ToString().Replace("\"", "");
        if (plurkData["no_comments"] != null && !string.IsNullOrEmpty(plurkData["no_comments"].ToString()))
            this.NoComments = (PlurkNoComments)Convert.ToInt32(plurkData["no_comments"].ToString());
        if (plurkData["plurk_type"] != null && !string.IsNullOrEmpty(plurkData["plurk_type"].ToString()))
            this.Type = (PlurkType)Convert.ToInt32(plurkData["plurk_type"].ToString());
        if (plurkData["is_unread"] != null && !string.IsNullOrEmpty(plurkData["is_unread"].ToString()))
            this.UnreadState = (PlurkUnreadFlag)Convert.ToInt32(plurkData["is_unread"].ToString());

        plurkData = null;
    }

    public Plurk(int UserID, int OwnerID, int PlurkID, string Qualifier, string QualifierTranslated, string Content, string ContentRaw, string Posted,
        string LimitTo, string Language, int ResponseCount, int ResponseSeen, PlurkNoComments NoComments, PlurkType Type, PlurkUnreadFlag UnreadFlag)
    {
        this.UserID = UserID;
        this.OwnerID = OwnerID;
        this.PlurkID = PlurkID;
        this.Qualifier = Qualifier;
        this.QualifierTranslated = QualifierTranslated;
        this.Content = Content;
        this.ContentRaw = ContentRaw;
        this.Posted = Posted;
        this.LimitTo = LimitTo;
        this.Language = Language;
        this.ResponsesCount = ResponsesCount;
        this.ResponsesSeen = ResponsesSeen;
        this.NoComments = NoComments;
        this.Type = Type;
        this.UnreadState = UnreadState;
    }

    public Plurk[] GetResponses()
    {
        JObject plurkData = JObject.Parse(CoreAPIs.Response.GetPlurkResponses(this.PlurkID, 0));

        // check response has exist.
        if ((plurkData.First.Next as JProperty).Name == "response_seen" && (plurkData.First.Next as JProperty).Value.ToString() == "-1")
            return null;

        List<Plurk> plurkList = new List<Plurk>();

        var responseQuery = from r in plurkData.First.Next.Next.First.Children()
                            select r;

        foreach (var plurkToken in responseQuery)
            plurkList.Add(new Plurk(plurkToken.ToString()));

        return plurkList.ToArray();
    }

    public bool HasResponseByUser(int UserID)
    {
        JObject plurkData = JObject.Parse(CoreAPIs.Response.GetPlurkResponses(this.PlurkID, 0));

        var responseQuery = (from r in plurkData.First.Next.Next.First.Children()
                            where Convert.ToInt32(r["user_id"].ToString()) == UserID
                            select r).Count();

        return (responseQuery != 0) ? true : false;
    }

    public void AddResponse(string Content, string Qualifier)
    {
        CoreAPIs.Response.AddPlurkResponses(this.PlurkID, Content, Qualifier);
    }
}

 

所以在機器人中綜合上述的規則,在機器人中即可撰寫下列的程式碼:

 

public void ResponseAskStockPrice()
{
    PlurkApiClient.Plurk[] plurks = this._plurkUser.GetPlurks();

    foreach (PlurkApiClient.Plurk plurk in plurks)
    {
        if (plurk.Qualifier == "asks") // 檢查 qualifier 是否為 asks
        {
            if (plurk.Content.IndexOf("股票成交價:") >= 0) // 檢查訊息內容是否有指定的字串
            {
                if (!plurk.HasResponseByUser(this._plurkUser.UserInfo.UserID)) // 檢查機器人是否有回應過,若有則不再回應。
                {
                    // 下載 Yahoo 奇摩股市資料,參考自:http://msdn.microsoft.com/zh-tw/ee787055.aspx 
                    WebClient client = new WebClient();
                    MemoryStream ms = new MemoryStream(
                        client.DownloadData("
http://tw.stock.yahoo.com/q/q?s=" + plurk.Content.Split(':')[1]));

                    // 使用預設編碼讀入 HTML
                    HtmlDocument doc = new HtmlDocument();
                    doc.Load(ms, Encoding.Default);

                    // 裝載第一層查詢結果
                    HtmlDocument docStockContext = new HtmlDocument();

                    docStockContext.LoadHtml(doc.DocumentNode.SelectSingleNode(
                        "/html[1]/body[1]/center[1]/table[2]/tr[1]/td[1]/table[1]").InnerHtml);

                    // 取得個股標頭
                    HtmlNodeCollection nodeHeaders = docStockContext.DocumentNode.SelectNodes("./tr[1]/th");
                    // 取得個股數值
                    string[] values = docStockContext.DocumentNode.SelectSingleNode("./tr[2]").InnerText.Trim().Split('\n');
                    int i = 0;
                    string result = null;

                    // 輸出資料
                    foreach (HtmlNode nodeHeader in nodeHeaders)
                    {
                        if (nodeHeader.InnerText.Trim() == "成交")
                        {
                            result = values[i];
                            break;
                        }
                        i++;
                    }

                    doc = null;
                    docStockContext = null;
                    client = null;
                    ms.Close();

                    if (!string.IsNullOrEmpty(result))
                    {
                        plurk.AddResponse(
                            "這支股號的成交價是:" + Convert.ToDouble(result).ToString("$###,##0.00"),
                            PlurkApiClient.PlurkQualifier.Says);
                    }
                    else
                    {
                        plurk.AddResponse("不好意思~查不到這支股票的成交價。", PlurkApiClient.PlurkQualifier.Says);
                    }
                }
            }
        }
    }
}

 

最後,將這支行為函數加入 Worker 的迴圈中就可以了:

 

if (DateTime.Now.Minute == 0)
{
    Trace.WriteLine("Plurk Bot Post Time.");

    behavior.ReportHour();

    Trace.WriteLine("Plurk Bot Post Time Completed.");
}
else if (DateTime.Now.Minute % 10 == 0)
{
    Trace.WriteLine("Plurk Bot Handling Friends.");

    // each 10 minutes, check friend request (accept all) and synchronize user list into database.
    //...
    Trace.WriteLine("Plurk Bot Handling Friends Completed.");
}
else // each 30 seconds do once.
{
    Trace.WriteLine("Plurk Bot Handling Report Behavior.");
    behavior.ResponseAskTime();
   
behavior.ResponseAskStockPrice();
    Trace.WriteLine("Plurk Bot Handling Report Behavior Completed.");
}

Thread.Sleep(30000);

 

本程式的執行結果是:

 

image

 

image

 

筆者還會繼續在這個雲端噗浪機器人中加入新的東西,我們下回見。

 

參考資料:

Plurk API Documentation