[VS2010] 雲端應用開發:在雲端上的噗浪機器人 (Part 2)
繼前一篇簡單的噗浪機器人自動報時功能後,我們再來繼續加入新的功能。
大多數的噗浪機器人,都會要求網友加入它為朋友,除了可以讓網友可以看到由機器人發送的訊息外,機器人也可以管理朋友,或是自動針對網友的訊息作出回應,因為畢竟任何人都不希望在自己的河道上看到莫名奇妙的訊息吧。而加入機器人為朋友這個動作,某種程度也是由網友自行授權給機器人可以存取你的個人資訊。而機器人只要做到將朋友的要求儲存起來即可。
在 Plurk 中,當網友發出加入朋友要求時,Plurk 會對被要求加入好友的使用者發出 Alert,一般在 Plurk 的使用者介面上,會顯示在通知那一區。
當然,在機器人中,不可能透過瀏覽器去做處理加入好友的通知的工作,因此 Plurk API 提供了兩個 API:
1. /API/Alert/getActive:取得來自於任何的 Plurk 通知。
2. /API/Alert/addAsFriend:加入指定的使用者至好友清單(粉絲的話則是 addAsFan)。
3. /API/Alert/addAllAsFriends:加入所有發送加入好友要求的使用者到好友清單(粉絲的話則是 addAllAsFan)。
以機器人的案例來說,/API/Alert/addAllAsFriends 最符合我們的需求,只要發出這個 API,Plurk 就會自動將所有發出加入好友要求的使用者全部加入機器人的好友清單,這對機器人來說是再方便不過的工具函數,在筆者的 Plurk API 元件中,這個 API 被包裝到 PlurkApiClient.User.AddAllToFriends() 函式中,只要呼叫這個函式,就可以如同呼叫 /API/Alert/addAllAsFriends API 相同的能力。
所以,第二階段的程式碼,要實作的是下列的功能:
1. 自動處理由使用者傳入的加入好友的要求,並自動加入到好友名單中。
2. 將好友資料儲存到資料庫中,在本例中的資料庫是 Table Storage (不是 SQL Azure),因此會需要加入對 Table Storage 的存取 (若對 Table Storage 不夠了解,可參考[VS2010] Visual Studio 2010 與 Windows Azure: 認識 Table Storage以及邊做邊學 Windows Azure 應用程式開發基礎 Part 2:開發 BLOB、Table 與 Queue 應用程式兩份文章)。
首先,自動處理加入好友要求的功能比較簡單,只要使用下列的呼叫即可:
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("PlurkBot entry point called", "Information");
while (true)
{
try
{
if (DateTime.Now.Minute == 0)
{
Trace.WriteLine("Plurk Bot Do Action.");
DateTime currentTime = DateTime.Now.ToUniversalTime().AddHours(8);
PlurkApiClient.User user = new PlurkApiClient.User("[YOUR_PLURK_ACCOUNT]", "[YOUR_PLURK_PASSWORD]");
user.PostPlurk(string.Format("噗浪機器人自雲端報時,現在時間:{0} 點整", currentTime.Hour),
PlurkApiClient.PlurkQualifier.FreeStyle);
user = null;
Trace.WriteLine("Plurk Bot Do Action Completed.");
}
else if (DateTime.Now.Minute % 10 == 0)
{
PlurkApiClient.User user = new PlurkApiClient.User("[YOUR_PLURK_ACCOUNT]", "[YOUR_PLURK_PASSWORD]");
user.AddAllToFriends();
user = null;
}
else
Trace.WriteLine(string.Format("Current time: {0}", DateTime.Now.ToString()));
Thread.Sleep(60000);
}
catch (Exception e)
{
Trace.WriteLine(string.Format("Plurk Bot throw a exception: {0}", e.Message));
}
}
}
再來,為了要將好友資料儲存到 Table Storage,我們會需要一個 TableServiceEntity 宣告的資料結構,以及 TableServiceContext 的資料存取實作,以本例來說,我們只要儲存好友的使用者代碼 (user id),顯示名稱 (display name) 以及暱稱 (nick name),所以我們會有下列的 TableServiceEntity 的定義:
public class PlurkFriend : TableServiceEntity
{
public int PlurkUserID { get; set; }
public string PlurkUserDisplayName { get; set; }
public string PlurkUserAccount { get; set; }
public PlurkFriend()
{
base.PartitionKey = "PlurkFriends";
base.RowKey = Guid.NewGuid().ToString();
}
}
而利用它的 TableServiceContext 則如下:
public class PlurkFriendContext : TableServiceContext
{
public PlurkFriendContext(string baseAddress, StorageCredentials credentials)
: base(baseAddress, credentials)
{
}
public void Add(int PlurkUserID, string DisplayName, string Account)
{
PlurkFriend friend = new PlurkFriend() { PlurkUserID = PlurkUserID, PlurkUserDisplayName = DisplayName, PlurkUserAccount = Account };
base.AddObject("PlurkFriends", friend);
base.SaveChanges();
}
public void Update(int PlurkUserID, string DisplayName, string Account)
{
PlurkFriend friend = this.GetByPlurkID(PlurkUserID);
friend.PlurkUserDisplayName = DisplayName;
friend.PlurkUserAccount = Account;
base.UpdateObject(friend);
base.SaveChanges();
}
public void Delete(int PlurkUserID)
{
PlurkFriend friend = this.GetByPlurkID(PlurkUserID);
base.DeleteObject(friend);
base.SaveChanges();
}
public PlurkFriend GetByPlurkID(int PlurkUserID)
{
return (from plurks in this.CreateQuery<PlurkFriend>("PlurkFriends")
where plurks.PlurkUserID == PlurkUserID
select plurks).First();
}
public IQueryable<PlurkFriend> SearchFriendsByDisplayName(string DisplayName)
{
return from plurks in this.CreateQuery<PlurkFriend>("PlurkFriends")
where plurks.PlurkUserDisplayName.IndexOf(DisplayName) >= 0
select plurks;
}
public IQueryable<PlurkFriend> SearchFriendsByAccountName(string AccountName)
{
return from plurks in this.CreateQuery<PlurkFriend>("PlurkFriends")
where plurks.PlurkUserAccount.IndexOf(AccountName) >= 0
select plurks;
}
public IQueryable<PlurkFriend> PlurkFriends
{
get { return base.CreateQuery<PlurkFriend>("PlurkFriends"); }
}
}
然後,在 WorkerRole.cs 中的 OnStart 方法實作中加入下列的程式碼:
var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
CloudTableClient.CreateTablesFromModel(
typeof(PlurkFriendContext), account.TableEndpoint.AbsoluteUri, account.Credentials);
最後,修改一下在 Run() 中的程式碼:
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("PlurkBot entry point called", "Information");
while (true)
{
try
{
if (DateTime.Now.Minute == 0)
{
Trace.WriteLine("Plurk Bot Do Action.");
DateTime currentTime = DateTime.Now.ToUniversalTime().AddHours(8);
PlurkApiClient.User user = new PlurkApiClient.User("[YOUR_PLURK_ACCOUNT]", "[YOUR_PLURK_PASSWORD]");
user.PostPlurk(string.Format("噗浪機器人自雲端報時,現在時間:{0} 點整", currentTime.Hour),
PlurkApiClient.PlurkQualifier.FreeStyle);
user = null;
Trace.WriteLine("Plurk Bot Do Action Completed.");
}
else if (DateTime.Now.Minute % 10 == 0)
{
PlurkApiClient.User user = new PlurkApiClient.User("[YOUR_PLURK_ACCOUNT]", "[YOUR_PLURK_PASSWORD]");
user.AddAllToFriends();
var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
PlurkApiClient.UserInfo[] friends = this._plurkUser.GetFriends();
if (friends != null && friends.Length > 0)
{
PlurkBotData.PlurkFriendContext context =
new PlurkBotData.PlurkFriendContext(account.TableEndpoint.AbsoluteUri, account.Credentials);
int addedUserCount = 0, updateUserCount = 0;
foreach (PlurkApiClient.UserInfo friend in friends)
{
PlurkBotData.PlurkFriend friendData = context.GetByPlurkID(friend.UserID);
if (friendData == null)
{
context.Add(friend.UserID, friend.DisplayName, friend.NickName);
addedUserCount++;
}
else
{
context.Update(friend.UserID, friend.DisplayName, friend.NickName);
updateUserCount++;
}
friendData = null;
}
Trace.WriteLine(string.Format("Plurk was handled friend count, add: {0}, update: {1}", addedUserCount, updateUserCount));
context = null;
}
friends = null;
user = null;
}
else
Trace.WriteLine(string.Format("Current time: {0}", DateTime.Now.ToString()));
Thread.Sleep(60000);
}
catch (Exception e)
{
Trace.WriteLine(string.Format("Plurk Bot throw a exception: {0}", e.Message));
}
}
}
第二階段的程式碼就完成了,當機器人在執行時,每 10 分鐘會自動將提交加入好友要求的使用者加入好友清單,並且將好友資料加入 Table Storage 中。
為了簡化之後的程式呼叫,筆者將所有機器人行為的程式碼整理到 PlurkBotBehavior 類別中,程式碼如下:
public class PlurkBotBehavior
{
private string _plurkUserAccount = "[YOUR_PLURK_ACCOUNT]";
private string _plurkPassword = "[YOUR_PLURK_PASSWORD]";
private PlurkApiClient.User _plurkUser = null;
public PlurkBotBehavior()
{
this._plurkUser = new PlurkApiClient.User(this._plurkUserAccount, this._plurkPassword);
}
public void ReportHour()
{
DateTime currentTime = DateTime.Now.ToUniversalTime().AddHours(8);
this._plurkUser.PostPlurk(string.Format("噗浪機器人自雲端報時,現在時間:{0} 點整", currentTime.Hour),
PlurkApiClient.PlurkQualifier.FreeStyle);
}
public void ReportTime()
{
DateTime currentTime = DateTime.Now.ToUniversalTime().AddHours(8);
this._plurkUser.PostPlurk(string.Format("噗浪機器人自雲端為您報時,現在時間:{0} 時 {1} 分 {2} 秒",
currentTime.Hour, currentTime.Minute, currentTime.Second), PlurkApiClient.PlurkQualifier.Says);
}
public void AcceptAllFans()
{
this._plurkUser.AddAllUserToFans();
}
public void AcceptAllFriends()
{
this._plurkUser.AddAllUserToFriends();
}
public void RefreshFriendTable()
{
var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
PlurkApiClient.UserInfo[] friends = this._plurkUser.GetFriends();
if (friends != null && friends.Length > 0)
{
PlurkBotData.PlurkFriendContext context =
new PlurkBotData.PlurkFriendContext(account.TableEndpoint.AbsoluteUri, account.Credentials);
int addedUserCount = 0, updateUserCount = 0;
foreach (PlurkApiClient.UserInfo friend in friends)
{
PlurkBotData.PlurkFriend friendData = context.GetByPlurkID(friend.UserID);
if (friendData == null)
{
context.Add(friend.UserID, friend.DisplayName, friend.NickName);
addedUserCount++;
}
else
{
context.Update(friend.UserID, friend.DisplayName, friend.NickName);
updateUserCount++;
}
friendData = null;
}
Trace.WriteLine(string.Format("Plurk was handled friend count, add: {0}, update: {1}", addedUserCount, updateUserCount));
context = null;
}
friends = null;
}
}
而 WorkerRole.cs 的 Run() 程式碼也整併為:
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("PlurkBot entry point called", "Information");
while (true)
{
PlurkBotBehavior behavior = null;
try
{
if (DateTime.Now.Minute == 0)
{
Trace.WriteLine("Plurk Bot Post Time.");
behavior = new PlurkBotBehavior();
behavior.ReportHour();
behavior = null;
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.
behavior = new PlurkBotBehavior();
behavior.AcceptAllFriends();
behavior.RefreshFriendTable();
behavior = null;
Trace.WriteLine("Plurk Bot Handling Friends Completed.");
}
Thread.Sleep(60000);
Trace.WriteLine(string.Format("Current time: {0}", DateTime.Now.ToString()));
}
catch (Exception e)
{
Trace.WriteLine(string.Format("Plurk Bot throw a exception: {0}", e.Message));
}
behavior = null;
}
}
以上的程式所需的 Plurk API Component,可在 http://plurkdotnet.codeplex.com/releases/view/42304 下載。