Twitterクライアント作ってます。
だいぶのびちゃったけど今月中には公開する予定。
接続用クラスとかパーサ書いたから張っておこう
パーサはとりあえずXPath使わずに書いてみた。パフォーマンス見てXPathのが良かったら切り替える予定。
TwitterConnector.cs
using System; using System.Collections.Generic; using System.Text; using System.Net; using System.IO; namespace Smartter { class TwitterConnector { string _id = ""; string _pass = ""; string _clientName = ""; public string Id { get { return _id; } set { _id = value; } } public string Pass { get { return _pass; } set { _pass = value; } } public TwitterConnector(string id,string pass) { _id = id; _pass = pass; } public TwitterConnector(string id,string pass,string clientName):this(id,pass) { _clientName = clientName; } /// <summary> /// 接続できるか確認する。 /// </summary> /// <returns></returns> public bool CanConnect() { WebRequest req = CreateWebRequest("http://twitter.com/account/rate_limit_status.xml"); req.Method = "GET"; try { HttpWebResponse wr = (HttpWebResponse)req.GetResponse(); HttpStatusCode ht = wr.StatusCode; wr.Close(); return ht == HttpStatusCode.OK; } catch { return false; } } public bool GetPublicTimeline(Action<string> xmlParseAction) { try { Get("http://twitter.com/statuses/friends_timeline.xml",xmlParseAction); } catch { return false; } return true; } public bool GetReplies(Action<string> xmlParseAction) { try { Get("http://twitter.com/statuses/replies.xml",xmlParseAction); } catch { return false; } return true; } public bool GetDirectMessage(Action<string> xmlParseAction) { try { Get("http://twitter.com/direct_messages.xml",xmlParseAction); } catch { return false; } return true; } /// <summary> /// アドレスにアクセスし、取得したデータに対して処理を行う /// </summary> /// <param name="uri"></param> /// <param name="act"></param> private void Get(string uri,Action<string> xmlParseAction) { WebRequest req = CreateWebRequest(uri); req.Method = "GET"; req.BeginGetResponse(delegate(IAsyncResult r) { WebResponse rps = req.EndGetResponse(r); using(StreamReader sr = new StreamReader(rps.GetResponseStream(),Encoding.UTF8)) { xmlParseAction(sr.ReadToEnd()); } },req); } /// <summary> /// 発言する /// </summary> /// <param name="status">発言内容</param> /// <returns>結果</returns> public bool UpdateStatus(string status) { byte[] postValue = CreatePostValue(status); WebRequest req = CreateWebRequest("http://twitter.com/statuses/update.xml"); req.Method = "POST"; req.ContentLength = postValue.Length; try { using(Stream strm = req.GetRequestStream()) { strm.Write(postValue,0,postValue.Length); } } catch(Exception ex) { Logger.Write(ex.Message); //接続エラー return false; } HttpWebResponse result = null; try { result = (HttpWebResponse)req.GetResponse(); return result.StatusCode == HttpStatusCode.OK; } catch(WebException ex) { //認証のエラー Logger.Write(ex.Message); return false; } catch(Exception ex) { //それ以外のエラー Logger.Write(ex.Message); return false; } } /// <summary> /// urlからリクエストを作成する /// </summary> /// <param name="uri"></param> /// <returns></returns> private WebRequest CreateWebRequest(string uri) { WebRequest req = HttpWebRequest.Create(uri); req.ContentType = "application/x-www-form-urlencoded"; req.Headers.Add(CreateAuthString()); return req; } /// <summary> /// Basic認証用の文字列を作成する /// </summary> /// <returns></returns> private string CreateAuthString() { return "Authorization: Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(_id + ":" + _pass)); } /// <summary> /// 発言する内容を作成する /// </summary> /// <param name="status"></param> /// <returns></returns> private byte[] CreatePostValue(string status) { return Encoding.ASCII.GetBytes("source=" + Uri.EscapeUriString(_clientName) + "&status=" + Uri.EscapeUriString(status)); } } }
TwitterXmlParser.cs
using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Xml; namespace Smartter { static class TwitterXmlParser { public delegate void Parse(string xmlString,Action<string[]> lineAction); /// <summary> /// タイムラインを解析して1行ごとに処理を行う /// </summary> /// <param name="s"></param> /// <param name="lineAct"></param> internal static void ParseTimeLine(string s,Action<string[]> lineAction) { StringReader sr = new StringReader(s); XmlTextReader xtr = new XmlTextReader(sr); while(xtr.Read()) { if(!IsStatusElement(xtr)) { continue; } lineAction(ReadStatusData(xtr)); } xtr.Close(); sr.Close(); } /// <summary> /// 一つの発言(<status>から</status>まで)を解析する /// </summary> /// <param name="xtr"></param> /// <returns>解析結果の文字列配列</returns> private static string[] ReadStatusData(XmlTextReader xtr) { //1つの発言を解析 //ID = 0, //Name = 1, //ScreenName = 2, //PostData = 3, //CreateAt = 4 //UserID = 5 String[] s = new string[6]; while(xtr.Read()) { if(xtr.NodeType == XmlNodeType.EndElement && xtr.Name == "status") { return s; } if(xtr.NodeType != XmlNodeType.Element) { continue; } switch(xtr.Name) { case "created_at"://発言時間 s[4] = GetNextValue(xtr); break; case "id"://POST番号 s[0] = GetNextValue(xtr); break; case "text"://post s[3] = GetNextValue(xtr); break; case "user": while(xtr.Read()) { if(xtr.NodeType == XmlNodeType.EndElement && xtr.Name == "user") { break; } if(xtr.NodeType != XmlNodeType.Element) { continue; } switch(xtr.Name) { case "id"://userid s[5] = GetNextValue(xtr); break; case "name"://表示名 s[1] = GetNextValue(xtr); break; case "screen_name"://アカウント s[2] = GetNextValue(xtr); break; } } break; } } //ここにはこないはず return s; } /// <summary> /// 一つ読んで次の値を返す /// </summary> /// <param name="xtr"></param> /// <returns></returns> private static string GetNextValue(XmlTextReader xtr) { xtr.Read(); return xtr.Value; } /// <summary> /// Statusエレメントか調査する /// </summary> /// <param name="xtr"></param> /// <returns></returns> private static bool IsStatusElement(XmlTextReader xtr) { return xtr.NodeType == XmlNodeType.Element && xtr.Name == "status"; } /// <summary> /// メッセージを解析してメッセージを配列に /// </summary> /// <param name="s"></param> /// <param name="lineAct"></param> internal static void ParseDirectMessage(string s,Action<string[]> lineAct) { StringReader sr = new StringReader(s); XmlTextReader xtr = new XmlTextReader(sr); while(xtr.Read()) { if(xtr.NodeType == XmlNodeType.Element || xtr.Name != "direct_message") { continue; } lineAct(ReadDirectMessageData(xtr)); } xtr.Close(); sr.Close(); } private static string[] ReadDirectMessageData(XmlTextReader xtr) { //ID = 0, //Name = 1, //ScreenName = 2, //DmData = 3, //CreateAt = 4 //SenderUserID = 5 String[] s = new string[6]; while(xtr.Read()) { if(xtr.NodeType == XmlNodeType.EndElement && xtr.Name == "direct_message") { return s; } if(xtr.NodeType != XmlNodeType.Element) { continue; } switch(xtr.Name) { case "created_at"://発言時間 s[4] = GetNextValue(xtr); break; case "sender_id"://POST番号 s[5] = GetNextValue(xtr); break; case "id"://POST番号 s[0] = GetNextValue(xtr); break; case "text"://post s[3] = GetNextValue(xtr); break; case "sender": while(xtr.Read()) { if(xtr.NodeType == XmlNodeType.EndElement && xtr.Name == "sender") { break; } if(xtr.NodeType != XmlNodeType.Element) { continue; } switch(xtr.Name) { case "name"://表示名 s[1] = GetNextValue(xtr); break; case "screen_name"://アカウント s[2] = GetNextValue(xtr); break; } } return s; } } //ここにはこないはず return s; } } }
呼び出すときはこんな感じ。
private void ReadTwitterData() { TwitterConnector t = new TwitterConnector("id","pass","appName"); t.GetPublicTimeline(delegate(string st) { Invoke(CreateParseAction(lvPublicTimeline,TwitterXmlParser.ParseTimeLine),st); }); t.GetReplies(delegate(string st) { Invoke(CreateParseAction(lvRep,TwitterXmlParser.ParseTimeLine),st); }); t.GetDirectMessage(delegate(string st) { Invoke(CreateParseAction(lvDm,TwitterXmlParser.ParseDirectMessage),st); }); } private static Action<string> CreateParseAction(ListView lv,TwitterXmlParser.Parse parse) { int saigo = lv.Items.Count == 0 ? 0 : Convert.ToInt32(lv.Items[0].Text); Action<string> mainAction = delegate(string s) { lv.BeginUpdate(); parse(s,delegate(string[] ss) { if(saigo < Convert.ToInt32(ss[0])) { lv.Items.Add(new ListViewItem(ss)); } }); lv.EndUpdate(); }; return mainAction; }
デリゲートつかってるから、若干見づらいかもしれないけど
接続部分、XMLの解析、表示部分のコードを完全にわけたかったのと、非同期実行したかったのでこんな感じになりました。
メソッド名とか変数名はリファクタリングの必要あるし、毎回idとpassもって認証しにいってるので、クッキー持ちまわせる幹事に変更はしたいかなぁ