1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 // (c) 2008-2011 Moz (@syo68k)
4 // (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 // (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 // (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 // (c) 2011 Egtra (@egtra) <http://dev.activebasic.com/egtra/>
8 // (c) 2013 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
9 // All rights reserved.
11 // This file is part of OpenTween.
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General Public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 // You should have received a copy of the GNU General Public License along
24 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
28 using System.Diagnostics;
32 using System.Runtime.CompilerServices;
33 using System.Runtime.Serialization;
34 using System.Runtime.Serialization.Json;
36 using System.Text.RegularExpressions;
37 using System.Threading;
38 using System.Threading.Tasks;
41 using System.Xml.Linq;
43 using System.Reflection;
44 using System.Collections.Generic;
46 using System.Windows.Forms;
48 using OpenTween.Connection;
52 public class Twitter : IDisposable
54 #region Regexp from twitter-text-js
56 // The code in this region code block incorporates works covered by
57 // the following copyright and permission notices:
59 // Copyright 2011 Twitter, Inc.
61 // Licensed under the Apache License, Version 2.0 (the "License"); you
62 // may not use this work except in compliance with the License. You
63 // may obtain a copy of the License in the LICENSE file, or at:
65 // http://www.apache.org/licenses/LICENSE-2.0
67 // Unless required by applicable law or agreed to in writing, software
68 // distributed under the License is distributed on an "AS IS" BASIS,
69 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
70 // implied. See the License for the specific language governing
71 // permissions and limitations under the License.
74 private const string LATIN_ACCENTS = @"\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253\u0254\u0256\u0257\u0259\u025b\u0263\u0268\u026f\u0272\u0289\u028b\u02bb\u1e00-\u1eff";
75 private const string NON_LATIN_HASHTAG_CHARS = @"\u0400-\u04ff\u0500-\u0527\u1100-\u11ff\u3130-\u3185\uA960-\uA97F\uAC00-\uD7AF\uD7B0-\uD7FF";
76 //private const string CJ_HASHTAG_CHARACTERS = @"\u30A1-\u30FA\uFF66-\uFF9F\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\u3041-\u3096\u3400-\u4DBF\u4E00-\u9FFF\u20000-\u2A6DF\u2A700-\u2B73F\u2B740-\u2B81F\u2F800-\u2FA1F";
77 private const string CJ_HASHTAG_CHARACTERS = @"\u30A1-\u30FA\u30FC\u3005\uFF66-\uFF9F\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\u3041-\u309A\u3400-\u4DBF\p{IsCJKUnifiedIdeographs}";
78 private const string HASHTAG_BOUNDARY = @"^|$|\s|「|」|。|\.|!";
79 private const string HASHTAG_ALPHA = "[a-z_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
80 private const string HASHTAG_ALPHANUMERIC = "[a-z0-9_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
81 private const string HASHTAG_TERMINATOR = "[^a-z0-9_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
82 public const string HASHTAG = "(" + HASHTAG_BOUNDARY + ")(#|#)(" + HASHTAG_ALPHANUMERIC + "*" + HASHTAG_ALPHA + HASHTAG_ALPHANUMERIC + "*)(?=" + HASHTAG_TERMINATOR + "|" + HASHTAG_BOUNDARY + ")";
84 private const string url_valid_preceding_chars = @"(?:[^A-Za-z0-9@@$##\ufffe\ufeff\uffff\u202a-\u202e]|^)";
85 public const string url_invalid_without_protocol_preceding_chars = @"[-_./]$";
86 private const string url_invalid_domain_chars = @"\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$\u2000-\u200a\u0009-\u000d\u0020\u0085\u00a0\u1680\u180e\u2028\u2029\u202f\u205f\u3000\ufffe\ufeff\uffff\u202a-\u202e";
87 private const string url_valid_domain_chars = @"[^" + url_invalid_domain_chars + "]";
88 private const string url_valid_subdomain = @"(?:(?:" + url_valid_domain_chars + @"(?:[_-]|" + url_valid_domain_chars + @")*)?" + url_valid_domain_chars + @"\.)";
89 private const string url_valid_domain_name = @"(?:(?:" + url_valid_domain_chars + @"(?:-|" + url_valid_domain_chars + @")*)?" + url_valid_domain_chars + @"\.)";
90 private const string url_valid_GTLD = @"(?:(?:aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx)(?=[^0-9a-zA-Z]|$))";
91 private const string url_valid_CCTLD = @"(?:(?:ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)(?=[^0-9a-zA-Z]|$))";
92 private const string url_valid_punycode = @"(?:xn--[0-9a-z]+)";
93 private const string url_valid_domain = @"(?<domain>" + url_valid_subdomain + "*" + url_valid_domain_name + "(?:" + url_valid_GTLD + "|" + url_valid_CCTLD + ")|" + url_valid_punycode + ")";
94 public const string url_valid_ascii_domain = @"(?:(?:[a-z0-9" + LATIN_ACCENTS + @"]+)\.)+(?:" + url_valid_GTLD + "|" + url_valid_CCTLD + "|" + url_valid_punycode + ")";
95 public const string url_invalid_short_domain = "^" + url_valid_domain_name + url_valid_CCTLD + "$";
96 private const string url_valid_port_number = @"[0-9]+";
98 private const string url_valid_general_path_chars = @"[a-z0-9!*';:=+,.$/%#\[\]\-_~|&" + LATIN_ACCENTS + "]";
99 private const string url_balance_parens = @"(?:\(" + url_valid_general_path_chars + @"+\))";
100 private const string url_valid_path_ending_chars = @"(?:[+\-a-z0-9=_#/" + LATIN_ACCENTS + "]|" + url_balance_parens + ")";
101 private const string pth = "(?:" +
103 url_valid_general_path_chars + "*" +
104 "(?:" + url_balance_parens + url_valid_general_path_chars + "*)*" +
105 url_valid_path_ending_chars +
106 ")|(?:@" + url_valid_general_path_chars + "+/)" +
108 private const string qry = @"(?<query>\?[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|]*[a-z0-9_&=#/])?";
109 public const string rgUrl = @"(?<before>" + url_valid_preceding_chars + ")" +
110 "(?<url>(?<protocol>https?://)?" +
111 "(?<domain>" + url_valid_domain + ")" +
112 "(?::" + url_valid_port_number + ")?" +
113 "(?<path>/" + pth + "*)?" +
120 /// Twitter API のステータスページのURL
122 public const string ServiceAvailabilityStatusUrl = "https://status.io.watchmouse.com/7617";
125 /// ツイートへのパーマリンクURLを判定する正規表現
127 public static readonly Regex StatusUrlRegex = new Regex(@"https?://([^.]+\.)?twitter\.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)/status(es)?/(?<StatusId>[0-9]+)(/photo)?", RegexOptions.IgnoreCase);
130 /// FavstarやaclogなどTwitter関連サービスのパーマリンクURLからステータスIDを抽出する正規表現
132 public static readonly Regex ThirdPartyStatusUrlRegex = new Regex(@"https?://(?:[^.]+\.)?(?:
133 favstar\.fm/users/[a-zA-Z0-9_]+/status/ # Favstar
134 | favstar\.fm/t/ # Favstar (short)
135 | aclog\.koba789\.com/i/ # aclog
136 | frtrt\.net/solo_status\.php\?status= # RtRT
137 )(?<StatusId>[0-9]+)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
140 /// DM送信かどうかを判定する正規表現
142 public static readonly Regex DMSendTextRegex = new Regex(@"^DM? +(?<id>[a-zA-Z0-9_]+) +(?<body>.+)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
144 public TwitterConfiguration Configuration { get; private set; }
146 delegate void GetIconImageDelegate(PostClass post);
147 private readonly object LockObj = new object();
148 private List<long> followerId = new List<long>();
149 private bool _GetFollowerResult = false;
150 private long[] noRTId = new long[0];
151 private bool _GetNoRetweetResult = false;
153 private int _followersCount = 0;
154 private int _friendsCount = 0;
155 private int _statusesCount = 0;
156 private string _location = "";
157 private string _bio = "";
160 private string _uname;
162 private bool _restrictFavCheck;
164 private bool _readOwnPost;
165 private List<string> _hashList = new List<string>();
167 //max_idで古い発言を取得するために保持(lists分は個別タブで管理)
168 private long minHomeTimeline = long.MaxValue;
169 private long minMentions = long.MaxValue;
170 private long minDirectmessage = long.MaxValue;
171 private long minDirectmessageSent = long.MaxValue;
173 //private FavoriteQueue favQueue;
175 private HttpTwitter twCon = new HttpTwitter();
177 //private List<PostClass> _deletemessages = new List<PostClass>();
181 this.Configuration = TwitterConfiguration.DefaultConfiguration();
184 public TwitterApiAccessLevel AccessLevel
188 return MyCommon.TwitterApiInfo.AccessLevel;
192 protected void ResetApiStatus()
194 MyCommon.TwitterApiInfo.Reset();
197 public string Authenticate(string username, string password)
199 this.ResetApiStatus();
205 res = twCon.AuthUserAndPass(username, password, ref content);
209 return "Err:" + ex.Message;
212 var err = this.CheckStatusCode(res, content);
213 if (err != null) return err;
215 _uname = username.ToLower();
216 if (SettingCommon.Instance.UserstreamStartup) this.ReconnectUserStream();
220 public string StartAuthentication(ref string pinPageUrl)
223 this.ResetApiStatus();
226 var res = twCon.AuthGetRequestToken(ref pinPageUrl);
228 return "Err:Failed to access auth server.";
232 return "Err:" + "Failed to access auth server.";
238 public string Authenticate(string pinCode)
240 this.ResetApiStatus();
245 res = twCon.AuthGetAccessToken(pinCode);
249 return "Err:" + "Failed to access auth acc server.";
252 var err = this.CheckStatusCode(res, null);
253 if (err != null) return err;
255 _uname = Username.ToLower();
256 if (SettingCommon.Instance.UserstreamStartup) this.ReconnectUserStream();
260 public void ClearAuthInfo()
262 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
263 this.ResetApiStatus();
264 twCon.ClearAuthInfo();
267 public void VerifyCredentials()
273 res = twCon.VerifyCredentials(ref content);
280 if (res == HttpStatusCode.OK)
282 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
286 user = TwitterUser.ParseJson(content);
288 catch(SerializationException)
292 twCon.AuthenticatedUserId = user.Id;
296 public void Initialize(string token, string tokenSecret, string username, long userId)
299 if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(tokenSecret) || string.IsNullOrEmpty(username))
301 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
303 this.ResetApiStatus();
304 twCon.Initialize(token, tokenSecret, username, userId);
305 _uname = username.ToLower();
306 if (SettingCommon.Instance.UserstreamStartup) this.ReconnectUserStream();
309 public string PreProcessUrl(string orgData)
313 //var IDNConveter = new IdnMapping();
314 var href = "<a href=\"";
318 if (orgData.IndexOf(href, posl2, StringComparison.Ordinal) > -1)
322 posl1 = orgData.IndexOf(href, posl2, StringComparison.Ordinal);
323 posl1 += href.Length;
324 posl2 = orgData.IndexOf("\"", posl1, StringComparison.Ordinal);
325 urlStr = orgData.Substring(posl1, posl2 - posl1);
327 if (!urlStr.StartsWith("http://") && !urlStr.StartsWith("https://") && !urlStr.StartsWith("ftp://"))
332 var replacedUrl = MyCommon.IDNEncode(urlStr);
333 if (replacedUrl == null) continue;
334 if (replacedUrl == urlStr) continue;
336 orgData = orgData.Replace("<a href=\"" + urlStr, "<a href=\"" + replacedUrl);
347 private string GetPlainText(string orgData)
349 return WebUtility.HtmlDecode(Regex.Replace(orgData, "(?<tagStart><a [^>]+>)(?<text>[^<]+)(?<tagEnd></a>)", "${text}"));
352 // htmlの簡易サニタイズ(詳細表示に不要なタグの除去)
354 private string SanitizeHtml(string orgdata)
356 var retdata = orgdata;
358 retdata = Regex.Replace(retdata, "<(script|object|applet|image|frameset|fieldset|legend|style).*" +
359 "</(script|object|applet|image|frameset|fieldset|legend|style)>", "", RegexOptions.IgnoreCase);
361 retdata = Regex.Replace(retdata, "<(frame|link|iframe|img)>", "", RegexOptions.IgnoreCase);
366 private string AdjustHtml(string orgData)
368 var retStr = orgData;
369 //var m = Regex.Match(retStr, "<a [^>]+>[#|#](?<1>[a-zA-Z0-9_]+)</a>");
374 // _hashList.Add("#" + m.Groups(1).Value);
378 retStr = Regex.Replace(retStr, "<a [^>]*href=\"/", "<a href=\"https://twitter.com/");
379 retStr = retStr.Replace("<a href=", "<a target=\"_self\" href=");
380 retStr = Regex.Replace(retStr, @"(\r\n?|\n)", "<br>"); // CRLF, CR, LF は全て <br> に置換する
382 //半角スペースを置換(Thanks @anis774)
386 ret = EscapeSpace(ref retStr);
389 return SanitizeHtml(retStr);
392 private bool EscapeSpace(ref string html)
394 //半角スペースを置換(Thanks @anis774)
396 for (int i = 0; i < html.Length; i++)
407 if ((!isTag) && (html[i] == ' '))
409 html = html.Remove(i, 1);
410 html = html.Insert(i, " ");
417 private struct PostInfo
419 public string CreatedAt;
422 public string UserId;
423 public PostInfo(string Created, string IdStr, string txt, string uid)
430 public bool Equals(PostInfo dst)
432 if (this.CreatedAt == dst.CreatedAt && this.Id == dst.Id && this.Text == dst.Text && this.UserId == dst.UserId)
443 static private PostInfo _prev = new PostInfo("", "", "", "");
444 private bool IsPostRestricted(TwitterStatus status)
446 var _current = new PostInfo("", "", "", "");
448 _current.CreatedAt = status.CreatedAt;
449 _current.Id = status.IdStr;
450 if (status.Text == null)
456 _current.Text = status.Text;
458 _current.UserId = status.User.IdStr;
460 if (_current.Equals(_prev))
464 _prev.CreatedAt = _current.CreatedAt;
465 _prev.Id = _current.Id;
466 _prev.Text = _current.Text;
467 _prev.UserId = _current.UserId;
472 public string PostStatus(string postStr, long? reply_to, List<long> mediaIds = null)
474 if (MyCommon._endingFlag) return "";
476 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
478 if (mediaIds == null &&
479 Twitter.DMSendTextRegex.IsMatch(postStr))
481 return SendDirectMessage(postStr);
488 res = twCon.UpdateStatus(postStr, reply_to, mediaIds, ref content);
492 return "Err:" + ex.Message;
495 // 投稿に成功していても404が返ることがあるらしい: https://dev.twitter.com/discussions/1213
496 if (res == HttpStatusCode.NotFound) return "";
498 var err = this.CheckStatusCode(res, content);
499 if (err != null) return err;
501 TwitterStatus status;
504 status = TwitterStatus.ParseJson(content);
506 catch(SerializationException ex)
508 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
509 return "Err:Json Parse Error(DataContractJsonSerializer)";
513 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
514 return "Err:Invalid Json!";
516 _followersCount = status.User.FollowersCount;
517 _friendsCount = status.User.FriendsCount;
518 _statusesCount = status.User.StatusesCount;
519 _location = status.User.Location;
520 _bio = status.User.Description;
522 if (IsPostRestricted(status))
524 return "OK:Delaying?";
529 public string PostStatusWithMedia(string postStr, long? reply_to, FileInfo mediaFile)
531 if (MyCommon._endingFlag) return "";
533 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
539 res = twCon.UpdateStatusWithMedia(postStr, reply_to, mediaFile, ref content);
543 return "Err:" + ex.Message;
546 // 投稿に成功していても404が返ることがあるらしい: https://dev.twitter.com/discussions/1213
547 if (res == HttpStatusCode.NotFound) return "";
549 var err = this.CheckStatusCode(res, content);
550 if (err != null) return err;
552 TwitterStatus status;
555 status = TwitterStatus.ParseJson(content);
557 catch(SerializationException ex)
559 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
560 return "Err:Json Parse Error(DataContractJsonSerializer)";
564 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
565 return "Err:Invalid Json!";
567 _followersCount = status.User.FollowersCount;
568 _friendsCount = status.User.FriendsCount;
569 _statusesCount = status.User.StatusesCount;
570 _location = status.User.Location;
571 _bio = status.User.Description;
573 if (IsPostRestricted(status))
575 return "OK:Delaying?";
580 public string PostStatusWithMultipleMedia(string postStr, long? reply_to, List<FileInfo> mediaFiles)
582 if (MyCommon._endingFlag) return "";
584 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
586 if (Twitter.DMSendTextRegex.IsMatch(postStr))
588 return SendDirectMessage(postStr);
591 var mediaIds = new List<long>();
593 foreach (var mediaFile in mediaFiles)
595 long? mediaId = null;
596 var err = UploadMedia(mediaFile, ref mediaId);
597 if (!mediaId.HasValue || !string.IsNullOrEmpty(err)) return err;
598 mediaIds.Add(mediaId.Value);
601 if (mediaIds.Count == 0)
602 return "Err:Invalid Files!";
604 return PostStatus(postStr, reply_to, mediaIds);
607 public string UploadMedia(FileInfo mediaFile, ref long? mediaId)
609 if (MyCommon._endingFlag) return "";
611 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
617 res = twCon.UploadMedia(mediaFile, ref content);
621 return "Err:" + ex.Message;
624 var err = this.CheckStatusCode(res, content);
625 if (err != null) return err;
627 TwitterUploadMediaResult status;
630 status = TwitterUploadMediaResult.ParseJson(content);
632 catch (SerializationException ex)
634 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
635 return "Err:Json Parse Error(DataContractJsonSerializer)";
639 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
640 return "Err:Invalid Json!";
643 mediaId = status.MediaId;
647 public string SendDirectMessage(string postStr)
649 if (MyCommon._endingFlag) return "";
651 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
653 if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
655 return "Auth Err:try to re-authorization.";
658 var mc = Twitter.DMSendTextRegex.Match(postStr);
664 res = twCon.SendDirectMessage(mc.Groups["body"].Value, mc.Groups["id"].Value, ref content);
668 return "Err:" + ex.Message;
671 var err = this.CheckStatusCode(res, content);
672 if (err != null) return err;
674 TwitterDirectMessage status;
677 status = TwitterDirectMessage.ParseJson(content);
679 catch(SerializationException ex)
681 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
682 return "Err:Json Parse Error(DataContractJsonSerializer)";
686 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
687 return "Err:Invalid Json!";
689 _followersCount = status.Sender.FollowersCount;
690 _friendsCount = status.Sender.FriendsCount;
691 _statusesCount = status.Sender.StatusesCount;
692 _location = status.Sender.Location;
693 _bio = status.Sender.Description;
698 public string RemoveStatus(long id)
700 if (MyCommon._endingFlag) return "";
702 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
707 res = twCon.DestroyStatus(id);
711 return "Err:" + ex.Message;
714 return this.CheckStatusCode(res, null) ?? "";
717 public string PostRetweet(long id, bool read)
719 if (MyCommon._endingFlag) return "";
720 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
724 var post = TabInformations.GetInstance()[id];
727 return "Err:Target isn't found.";
729 if (TabInformations.GetInstance()[id].RetweetedId != null)
731 target = TabInformations.GetInstance()[id].RetweetedId.Value; //再RTの場合は元発言をRT
738 res = twCon.RetweetStatus(target, ref content);
742 return "Err:" + ex.Message;
745 var err = this.CheckStatusCode(res, content);
746 if (err != null) return err;
748 TwitterStatus status;
751 status = TwitterStatus.ParseJson(content);
753 catch(SerializationException ex)
755 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
756 return "Err:Json Parse Error(DataContractJsonSerializer)";
760 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
761 return "Err:Invalid Json!";
765 post = CreatePostsFromStatusData(status);
766 if (post == null) return "Invalid Json!";
771 if (TabInformations.GetInstance().ContainsKey(post.StatusId)) return "";
774 if (post.RetweetedId == null) return "Invalid Json!";
780 if (_readOwnPost) post.IsRead = true;
783 TabInformations.GetInstance().AddPost(post);
788 public string RemoveDirectMessage(long id, PostClass post)
790 if (MyCommon._endingFlag) return "";
792 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
794 if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
796 return "Auth Err:try to re-authorization.";
800 // _deletemessages.Add(post)
806 res = twCon.DestroyDirectMessage(id);
810 return "Err:" + ex.Message;
813 return this.CheckStatusCode(res, null) ?? "";
816 public string PostFollowCommand(string screenName)
818 if (MyCommon._endingFlag) return "";
820 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
826 res = twCon.CreateFriendships(screenName, ref content);
830 return "Err:" + ex.Message;
833 return this.CheckStatusCode(res, content) ?? "";
836 public string PostRemoveCommand(string screenName)
838 if (MyCommon._endingFlag) return "";
840 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
846 res = twCon.DestroyFriendships(screenName, ref content);
850 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
853 return this.CheckStatusCode(res, content) ?? "";
856 public string PostCreateBlock(string screenName)
858 if (MyCommon._endingFlag) return "";
860 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
866 res = twCon.CreateBlock(screenName, ref content);
870 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
873 return this.CheckStatusCode(res, content) ?? "";
876 public string PostDestroyBlock(string screenName)
878 if (MyCommon._endingFlag) return "";
880 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
886 res = twCon.DestroyBlock(screenName, ref content);
890 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
893 return this.CheckStatusCode(res, content) ?? "";
896 public string PostReportSpam(string screenName)
898 if (MyCommon._endingFlag) return "";
900 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
906 res = twCon.ReportSpam(screenName, ref content);
910 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
913 return this.CheckStatusCode(res, content) ?? "";
916 public string GetFriendshipInfo(string screenName, ref bool isFollowing, ref bool isFollowed)
918 if (MyCommon._endingFlag) return "";
920 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
926 res = twCon.ShowFriendships(_uname, screenName, ref content);
930 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
933 var err = this.CheckStatusCode(res, content);
934 if (err != null) return err;
938 var friendship = TwitterFriendship.ParseJson(content);
939 isFollowing = friendship.Relationship.Source.Following;
940 isFollowed = friendship.Relationship.Source.FollowedBy;
943 catch(SerializationException ex)
945 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
946 return "Err:Json Parse Error(DataContractJsonSerializer)";
950 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
951 return "Err:Invalid Json!";
955 public string GetUserInfo(string screenName, ref TwitterUser user)
957 if (MyCommon._endingFlag) return "";
959 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
967 res = twCon.ShowUserInfo(screenName, ref content);
971 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
974 var err = this.CheckStatusCode(res, content);
975 if (err != null) return err;
979 user = TwitterUser.ParseJson(content);
981 catch (SerializationException ex)
983 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
984 return "Err:Json Parse Error(DataContractJsonSerializer)";
988 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
989 return "Err:Invalid Json!";
994 public string GetStatus_Retweeted_Count(long StatusId, ref int retweeted_count)
996 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
998 if (MyCommon._endingFlag) return "";
1004 res = twCon.ShowStatuses(StatusId, ref content);
1006 catch (Exception ex)
1008 return "Err:" + ex.Message;
1011 var err = this.CheckStatusCode(res, content);
1012 if (err != null) return err;
1014 TwitterStatus status;
1017 status = TwitterStatus.ParseJson(content);
1019 catch (SerializationException ex)
1021 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1022 return "Json Parse Error(DataContractJsonSerializer)";
1024 catch (Exception ex)
1026 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1027 return "Invalid Json!";
1029 retweeted_count = status.RetweetCount;
1033 public string PostFavAdd(long id)
1035 if (MyCommon._endingFlag) return "";
1037 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1039 //if (this.favQueue == null) this.favQueue = new FavoriteQueue(this)
1041 //if (this.favQueue.Contains(id)) this.favQueue.Remove(id)
1047 res = twCon.CreateFavorites(id, ref content);
1051 //this.favQueue.Add(id)
1052 //return "Err:->FavoriteQueue:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
1053 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
1056 var err = this.CheckStatusCode(res, content);
1057 if (err != null) return err;
1059 if (!_restrictFavCheck) return "";
1061 //http://twitter.com/statuses/show/id.xml APIを発行して本文を取得
1065 res = twCon.ShowStatuses(id, ref content);
1069 return "Err:" + ex.Message;
1072 err = this.CheckStatusCode(res, content);
1073 if (err != null) return err;
1075 TwitterStatus status;
1078 status = TwitterStatus.ParseJson(content);
1080 catch (SerializationException ex)
1082 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1083 return "Err:Json Parse Error(DataContractJsonSerializer)";
1085 catch (Exception ex)
1087 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1088 return "Err:Invalid Json!";
1090 if (status.Favorited == true)
1096 return "NG(Restricted?)";
1100 public string PostFavRemove(long id)
1102 if (MyCommon._endingFlag) return "";
1104 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1106 //if (this.favQueue == null) this.favQueue = new FavoriteQueue(this)
1108 //if (this.favQueue.Contains(id))
1109 // this.favQueue.Remove(id)
1117 res = twCon.DestroyFavorites(id, ref content);
1121 return "Err:" + ex.Message;
1124 return this.CheckStatusCode(res, content) ?? "";
1127 public TwitterUser PostUpdateProfile(string name, string url, string location, string description)
1129 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
1130 throw new WebApiException("AccountState invalid");
1136 res = twCon.UpdateProfile(name, url, location, description, ref content);
1140 throw new WebApiException("Err:" + ex.Message, content, ex);
1143 var err = this.CheckStatusCode(res, content);
1145 throw new WebApiException(err, content);
1149 return TwitterUser.ParseJson(content);
1151 catch (SerializationException e)
1153 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
1154 MyCommon.TraceOut(ex);
1159 var ex = new WebApiException("Err:Invalid Json!", content, e);
1160 MyCommon.TraceOut(ex);
1165 public string PostUpdateProfileImage(string filename)
1167 if (MyCommon._endingFlag) return "";
1169 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1175 res = twCon.UpdateProfileImage(new FileInfo(filename), ref content);
1179 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
1182 return this.CheckStatusCode(res, content) ?? "";
1185 public string Username
1189 return twCon.AuthenticatedUsername;
1197 return twCon.AuthenticatedUserId;
1201 public string Password
1205 return twCon.Password;
1209 private static MyCommon.ACCOUNT_STATE _accountState = MyCommon.ACCOUNT_STATE.Valid;
1210 public static MyCommon.ACCOUNT_STATE AccountState
1214 return _accountState;
1218 _accountState = value;
1222 public bool RestrictFavCheck
1226 _restrictFavCheck = value;
1231 public string GetTweenBinary(string strVer)
1236 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/Tween" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1237 Path.Combine(MyCommon.settingPath, "TweenNew.exe")))
1239 return "Err:Download failed";
1242 if (!Directory.Exists(Path.Combine(MyCommon.settingPath, "en")))
1244 Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, "en"));
1246 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenResEn" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1247 Path.Combine(Path.Combine(MyCommon.settingPath, "en"), "Tween.resourcesNew.dll")))
1249 return "Err:Download failed";
1251 //その他言語圏のリソース。取得失敗しても継続
1254 if (!Thread.CurrentThread.CurrentUICulture.IsNeutralCulture)
1256 var idx = Thread.CurrentThread.CurrentUICulture.Name.LastIndexOf('-');
1259 curCul = Thread.CurrentThread.CurrentUICulture.Name.Substring(0, idx);
1263 curCul = Thread.CurrentThread.CurrentUICulture.Name;
1268 curCul = Thread.CurrentThread.CurrentUICulture.Name;
1270 if (!string.IsNullOrEmpty(curCul) && curCul != "en" && curCul != "ja")
1272 if (!Directory.Exists(Path.Combine(MyCommon.settingPath, curCul)))
1274 Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, curCul));
1276 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenRes" + curCul + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1277 Path.Combine(Path.Combine(MyCommon.settingPath, curCul), "Tween.resourcesNew.dll")))
1279 //return "Err:Download failed";
1284 if (!Thread.CurrentThread.CurrentCulture.IsNeutralCulture)
1286 var idx = Thread.CurrentThread.CurrentCulture.Name.LastIndexOf('-');
1289 curCul2 = Thread.CurrentThread.CurrentCulture.Name.Substring(0, idx);
1293 curCul2 = Thread.CurrentThread.CurrentCulture.Name;
1298 curCul2 = Thread.CurrentThread.CurrentCulture.Name;
1300 if (!string.IsNullOrEmpty(curCul2) && curCul2 != "en" && curCul2 != curCul)
1302 if (!Directory.Exists(Path.Combine(MyCommon.settingPath, curCul2)))
1304 Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, curCul2));
1306 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenRes" + curCul2 + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1307 Path.Combine(Path.Combine(MyCommon.settingPath, curCul2), "Tween.resourcesNew.dll")))
1309 //return "Err:Download failed";
1314 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenUp3.gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1315 Path.Combine(MyCommon.settingPath, "TweenUp3.exe")))
1317 return "Err:Download failed";
1320 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenDll" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1321 Path.Combine(MyCommon.settingPath, "TweenNew.XmlSerializers.dll")))
1323 return "Err:Download failed";
1329 return "Err:Download failed";
1334 public bool ReadOwnPost
1338 return _readOwnPost;
1342 _readOwnPost = value;
1346 public int FollowersCount
1350 return _followersCount;
1354 public int FriendsCount
1358 return _friendsCount;
1362 public int StatusesCount
1366 return _statusesCount;
1370 public string Location
1386 public string GetTimelineApi(bool read,
1387 MyCommon.WORKERTYPE gType,
1391 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1393 if (MyCommon._endingFlag) return "";
1397 var count = SettingCommon.Instance.CountApi;
1398 if (gType == MyCommon.WORKERTYPE.Reply) count = SettingCommon.Instance.CountApiReply;
1399 if (SettingCommon.Instance.UseAdditionalCount)
1401 if (more && SettingCommon.Instance.MoreCountApi != 0)
1403 count = SettingCommon.Instance.MoreCountApi;
1405 else if (startup && SettingCommon.Instance.FirstCountApi != 0 && gType == MyCommon.WORKERTYPE.Timeline)
1407 count = SettingCommon.Instance.FirstCountApi;
1412 if (gType == MyCommon.WORKERTYPE.Timeline)
1416 res = twCon.HomeTimeline(count, this.minHomeTimeline, null, ref content);
1420 res = twCon.HomeTimeline(count, null, null, ref content);
1427 res = twCon.Mentions(count, this.minMentions, null, ref content);
1431 res = twCon.Mentions(count, null, null, ref content);
1437 return "Err:" + ex.Message;
1440 var err = this.CheckStatusCode(res, content);
1441 if (err != null) return err;
1443 if (gType == MyCommon.WORKERTYPE.Timeline)
1445 return CreatePostsFromJson(content, gType, null, read, count, ref this.minHomeTimeline);
1449 return CreatePostsFromJson(content, gType, null, read, count, ref this.minMentions);
1453 public string GetUserTimelineApi(bool read,
1459 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1461 if (MyCommon._endingFlag) return "";
1466 if (count == 0) count = 20;
1469 if (string.IsNullOrEmpty(userName))
1471 var target = tab.User;
1472 if (string.IsNullOrEmpty(target)) return "";
1474 res = twCon.UserTimeline(null, target, count, null, null, ref content);
1480 res = twCon.UserTimeline(null, userName, count, tab.OldestId, null, ref content);
1484 res = twCon.UserTimeline(null, userName, count, null, null, ref content);
1490 return "Err:" + ex.Message;
1493 if (res == HttpStatusCode.Unauthorized)
1494 return "Err:@" + userName + "'s Tweets are protected.";
1496 var err = this.CheckStatusCode(res, content);
1497 if (err != null) return err;
1499 TwitterStatus[] items;
1502 items = TwitterStatus.ParseJsonArray(content);
1504 catch(SerializationException ex)
1506 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1507 return "Json Parse Error(DataContractJsonSerializer)";
1511 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1512 return "Invalid Json!";
1515 foreach (var status in items)
1517 var item = CreatePostsFromStatusData(status);
1518 if (item == null) continue;
1519 if (item.StatusId < tab.OldestId) tab.OldestId = item.StatusId;
1521 if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
1522 if (tab != null) item.RelTabName = tab.TabName;
1523 //非同期アイコン取得&StatusDictionaryに追加
1524 TabInformations.GetInstance().AddPost(item);
1530 public string GetStatusApi(bool read,
1534 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1536 if (MyCommon._endingFlag) return "";
1542 res = twCon.ShowStatuses(id, ref content);
1546 return "Err:" + ex.Message;
1549 if (res == HttpStatusCode.Forbidden)
1550 return "Err:protected user's tweet";
1552 var err = this.CheckStatusCode(res, content);
1553 if (err != null) return err;
1555 TwitterStatus status;
1558 status = TwitterStatus.ParseJson(content);
1560 catch(SerializationException ex)
1562 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1563 return "Json Parse Error(DataContractJsonSerializer)";
1567 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1568 return "Invalid Json!";
1571 var item = CreatePostsFromStatusData(status);
1572 if (item == null) return "Err:Can't create post";
1574 if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
1580 public string GetStatusApi(bool read,
1584 PostClass post = null;
1585 var r = this.GetStatusApi(read, id, ref post);
1587 if (string.IsNullOrEmpty(r))
1589 if (tab != null) post.RelTabName = tab.TabName;
1590 //非同期アイコン取得&StatusDictionaryに追加
1591 TabInformations.GetInstance().AddPost(post);
1597 private PostClass CreatePostsFromStatusData(TwitterStatus status)
1599 var post = new PostClass();
1600 TwitterEntities entities;
1602 post.StatusId = status.Id;
1603 if (status.RetweetedStatus != null)
1605 var retweeted = status.RetweetedStatus;
1607 post.CreatedAt = MyCommon.DateTimeParse(retweeted.CreatedAt);
1610 post.RetweetedId = retweeted.Id;
1612 post.TextFromApi = retweeted.Text;
1613 entities = retweeted.MergedEntities;
1614 //Source取得(htmlの場合は、中身を取り出し)
1615 post.Source = retweeted.Source;
1617 post.InReplyToStatusId = retweeted.InReplyToStatusId;
1618 post.InReplyToUser = retweeted.InReplyToScreenName;
1619 post.InReplyToUserId = status.InReplyToUserId;
1622 var tc = TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites);
1623 post.IsFav = tc.Contains(retweeted.Id);
1625 if (retweeted.Coordinates != null) post.PostGeo = new PostClass.StatusGeo { Lng = retweeted.Coordinates.Coordinates[0], Lat = retweeted.Coordinates.Coordinates[1] };
1628 var user = retweeted.User;
1630 if (user == null || user.ScreenName == null || status.User.ScreenName == null) return null;
1632 post.UserId = user.Id;
1633 post.ScreenName = user.ScreenName;
1634 post.Nickname = user.Name.Trim();
1635 post.ImageUrl = user.ProfileImageUrlHttps;
1636 post.IsProtect = user.Protected;
1639 post.RetweetedBy = status.User.ScreenName;
1640 post.RetweetedByUserId = status.User.Id;
1641 post.IsMe = post.RetweetedBy.ToLower().Equals(_uname);
1645 post.CreatedAt = MyCommon.DateTimeParse(status.CreatedAt);
1647 post.TextFromApi = status.Text;
1648 entities = status.MergedEntities;
1649 //Source取得(htmlの場合は、中身を取り出し)
1650 post.Source = status.Source;
1651 post.InReplyToStatusId = status.InReplyToStatusId;
1652 post.InReplyToUser = status.InReplyToScreenName;
1653 post.InReplyToUserId = status.InReplyToUserId;
1655 if (status.Coordinates != null) post.PostGeo = new PostClass.StatusGeo { Lng = status.Coordinates.Coordinates[0], Lat = status.Coordinates.Coordinates[1] };
1658 var user = status.User;
1660 if (user == null || user.ScreenName == null) return null;
1662 post.UserId = user.Id;
1663 post.ScreenName = user.ScreenName;
1664 post.Nickname = user.Name.Trim();
1665 post.ImageUrl = user.ProfileImageUrlHttps;
1666 post.IsProtect = user.Protected;
1667 post.IsMe = post.ScreenName.ToLower().Equals(_uname);
1670 var tc = TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites);
1671 post.IsFav = tc.Contains(post.StatusId) && TabInformations.GetInstance()[post.StatusId].IsFav;
1674 string textFromApi = post.TextFromApi;
1675 post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, entities, post.Media);
1676 post.TextFromApi = textFromApi;
1677 post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities);
1678 post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
1679 post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
1684 post.IsReply = post.ReplyToList.Contains(_uname);
1685 post.IsExcludeReply = false;
1693 if (followerId.Count > 0) post.IsOwl = !followerId.Contains(post.UserId);
1700 private string CreatePostsFromJson(string content, MyCommon.WORKERTYPE gType, TabClass tab, bool read, int count, ref long minimumId)
1702 TwitterStatus[] items;
1705 items = TwitterStatus.ParseJsonArray(content);
1707 catch(SerializationException ex)
1709 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1710 return "Json Parse Error(DataContractJsonSerializer)";;
1714 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1715 return "Invalid Json!";
1718 foreach (var status in items)
1720 PostClass post = null;
1721 post = CreatePostsFromStatusData(status);
1722 if (post == null) continue;
1724 if (minimumId > post.StatusId) minimumId = post.StatusId;
1730 if (TabInformations.GetInstance().ContainsKey(post.StatusId)) continue;
1734 if (TabInformations.GetInstance().ContainsKey(post.StatusId, tab.TabName)) continue;
1739 if (post.RetweetedByUserId != null && this.noRTId.Contains(post.RetweetedByUserId.Value)) continue;
1742 if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
1744 if (tab != null) post.RelTabName = tab.TabName;
1745 //非同期アイコン取得&StatusDictionaryに追加
1746 TabInformations.GetInstance().AddPost(post);
1752 private string CreatePostsFromSearchJson(string content, TabClass tab, bool read, int count, ref long minimumId, bool more)
1754 TwitterSearchResult items;
1757 items = TwitterSearchResult.ParseJson(content);
1759 catch (SerializationException ex)
1761 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1762 return "Json Parse Error(DataContractJsonSerializer)";
1764 catch (Exception ex)
1766 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1767 return "Invalid Json!";
1769 foreach (var result in items.Statuses)
1771 PostClass post = null;
1772 post = CreatePostsFromStatusData(result);
1776 // Search API は相変わらずぶっ壊れたデータを返すことがあるため、必要なデータが欠如しているものは取得し直す
1777 var ret = this.GetStatusApi(read, result.Id, ref post);
1778 if (!string.IsNullOrEmpty(ret)) continue;
1781 if (minimumId > post.StatusId) minimumId = post.StatusId;
1782 if (!more && post.StatusId > tab.SinceId) tab.SinceId = post.StatusId;
1788 if (TabInformations.GetInstance().ContainsKey(post.StatusId)) continue;
1792 if (TabInformations.GetInstance().ContainsKey(post.StatusId, tab.TabName)) continue;
1797 if ((post.IsMe && !read) && this._readOwnPost) post.IsRead = true;
1799 if (tab != null) post.RelTabName = tab.TabName;
1800 //非同期アイコン取得&StatusDictionaryに追加
1801 TabInformations.GetInstance().AddPost(post);
1807 public string GetListStatus(bool read,
1812 if (MyCommon._endingFlag) return "";
1817 if (SettingCommon.Instance.UseAdditionalCount)
1819 count = SettingCommon.Instance.ListCountApi;
1822 if (more && SettingCommon.Instance.MoreCountApi != 0)
1824 count = SettingCommon.Instance.MoreCountApi;
1826 else if (startup && SettingCommon.Instance.FirstCountApi != 0)
1828 count = SettingCommon.Instance.FirstCountApi;
1832 count = SettingCommon.Instance.CountApi;
1838 count = SettingCommon.Instance.CountApi;
1844 res = twCon.GetListsStatuses(tab.ListInfo.UserId, tab.ListInfo.Id, count, tab.OldestId, null, SettingCommon.Instance.IsListsIncludeRts, ref content);
1848 res = twCon.GetListsStatuses(tab.ListInfo.UserId, tab.ListInfo.Id, count, null, null, SettingCommon.Instance.IsListsIncludeRts, ref content);
1853 return "Err:" + ex.Message;
1856 var err = this.CheckStatusCode(res, content);
1857 if (err != null) return err;
1859 return CreatePostsFromJson(content, MyCommon.WORKERTYPE.List, tab, read, count, ref tab.OldestId);
1863 /// startStatusId からリプライ先の発言を辿る。発言は posts 以外からは検索しない。
1865 /// <returns>posts の中から検索されたリプライチェインの末端</returns>
1866 internal static PostClass FindTopOfReplyChain(IDictionary<Int64, PostClass> posts, Int64 startStatusId)
1868 if (!posts.ContainsKey(startStatusId))
1869 throw new ArgumentException("startStatusId (" + startStatusId + ") が posts の中から見つかりませんでした。");
1871 var nextPost = posts[startStatusId];
1872 while (nextPost.InReplyToStatusId != null)
1874 if (!posts.ContainsKey(nextPost.InReplyToStatusId.Value))
1876 nextPost = posts[nextPost.InReplyToStatusId.Value];
1882 public string GetRelatedResult(bool read, TabClass tab)
1885 var relPosts = new Dictionary<Int64, PostClass>();
1886 if (tab.RelationTargetPost.TextFromApi.Contains("@") && tab.RelationTargetPost.InReplyToStatusId == null)
1889 var p = TabInformations.GetInstance()[tab.RelationTargetPost.StatusId];
1890 if (p != null && p.InReplyToStatusId != null)
1892 tab.RelationTargetPost = p;
1896 rslt = this.GetStatusApi(read, tab.RelationTargetPost.StatusId, ref p);
1897 if (!string.IsNullOrEmpty(rslt)) return rslt;
1898 tab.RelationTargetPost = p;
1901 relPosts.Add(tab.RelationTargetPost.StatusId, tab.RelationTargetPost.Clone());
1903 // in_reply_to_status_id を使用してリプライチェインを辿る
1904 var nextPost = FindTopOfReplyChain(relPosts, tab.RelationTargetPost.StatusId);
1906 while (nextPost.InReplyToStatusId != null && loopCount++ <= 20)
1908 var inReplyToId = nextPost.InReplyToStatusId.Value;
1910 var inReplyToPost = TabInformations.GetInstance()[inReplyToId];
1911 if (inReplyToPost != null)
1913 inReplyToPost = inReplyToPost.Clone();
1917 var errorText = this.GetStatusApi(read, inReplyToId, ref inReplyToPost);
1918 if (!string.IsNullOrEmpty(errorText))
1925 relPosts.Add(inReplyToPost.StatusId, inReplyToPost);
1927 nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId);
1930 //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む
1931 var text = tab.RelationTargetPost.Text;
1932 var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>()
1933 .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>());
1934 foreach (var _match in ma)
1937 if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId))
1939 if (relPosts.ContainsKey(_statusId))
1943 var _post = TabInformations.GetInstance()[_statusId];
1946 this.GetStatusApi(read, _statusId, ref p);
1954 relPosts.Add(p.StatusId, p);
1958 relPosts.Values.ToList().ForEach(p =>
1960 if (p.IsMe && !read && this._readOwnPost)
1965 p.RelTabName = tab.TabName;
1966 TabInformations.GetInstance().AddPost(p);
1972 public string GetSearch(bool read,
1976 if (MyCommon._endingFlag) return "";
1981 long? sinceId = null;
1983 if (SettingCommon.Instance.UseAdditionalCount &&
1984 SettingCommon.Instance.SearchCountApi != 0)
1986 count = SettingCommon.Instance.SearchCountApi;
1990 count = SettingCommon.Instance.CountApi;
1994 maxId = tab.OldestId - 1;
1998 sinceId = tab.SinceId;
2003 // TODO:一時的に40>100件に 件数変更UI作成の必要あり
2004 res = twCon.Search(tab.SearchWords, tab.SearchLang, count, maxId, sinceId, ref content);
2008 return "Err:" + ex.Message;
2012 case HttpStatusCode.BadRequest:
2013 return "Invalid query";
2014 case HttpStatusCode.NotFound:
2015 return "Invalid query";
2016 case HttpStatusCode.PaymentRequired: //API Documentには420と書いてあるが、該当コードがないので402にしてある
2017 return "Search API Limit?";
2018 case HttpStatusCode.OK:
2021 return "Err:" + res.ToString() + "(" + MethodBase.GetCurrentMethod().Name + ")";
2024 if (!TabInformations.GetInstance().ContainsTab(tab)) return "";
2026 return this.CreatePostsFromSearchJson(content, tab, read, count, ref tab.OldestId, more);
2029 private string CreateDirectMessagesFromJson(string content, MyCommon.WORKERTYPE gType, bool read)
2031 TwitterDirectMessage[] item;
2034 if (gType == MyCommon.WORKERTYPE.UserStream)
2036 item = new[] { TwitterStreamEventDirectMessage.ParseJson(content).DirectMessage };
2040 item = TwitterDirectMessage.ParseJsonArray(content);
2043 catch(SerializationException ex)
2045 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2046 return "Json Parse Error(DataContractJsonSerializer)";
2050 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2051 return "Invalid Json!";
2054 foreach (var message in item)
2056 var post = new PostClass();
2059 post.StatusId = message.Id;
2060 if (gType != MyCommon.WORKERTYPE.UserStream)
2062 if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
2064 if (minDirectmessage > post.StatusId) minDirectmessage = post.StatusId;
2068 if (minDirectmessageSent > post.StatusId) minDirectmessageSent = post.StatusId;
2075 if (TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.DirectMessage).Contains(post.StatusId)) continue;
2079 post.CreatedAt = MyCommon.DateTimeParse(message.CreatedAt);
2081 var textFromApi = message.Text;
2083 post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, message.Entities, post.Media);
2084 post.TextFromApi = this.ReplaceTextFromApi(textFromApi, message.Entities);
2085 post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
2086 post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
2091 if (gType == MyCommon.WORKERTYPE.UserStream)
2093 if (twCon.AuthenticatedUsername.Equals(message.Recipient.ScreenName, StringComparison.CurrentCultureIgnoreCase))
2095 user = message.Sender;
2101 user = message.Recipient;
2108 if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
2110 user = message.Sender;
2116 user = message.Recipient;
2122 post.UserId = user.Id;
2123 post.ScreenName = user.ScreenName;
2124 post.Nickname = user.Name.Trim();
2125 post.ImageUrl = user.ProfileImageUrlHttps;
2126 post.IsProtect = user.Protected;
2130 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2131 MessageBox.Show("Parse Error(CreateDirectMessagesFromJson)");
2136 if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
2137 post.IsReply = false;
2138 post.IsExcludeReply = false;
2141 TabInformations.GetInstance().AddPost(post);
2148 public string GetDirectMessageApi(bool read,
2149 MyCommon.WORKERTYPE gType,
2152 if (MyCommon._endingFlag) return "";
2154 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2156 if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
2158 return "Auth Err:try to re-authorization.";
2166 if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
2170 res = twCon.DirectMessages(20, minDirectmessage, null, ref content);
2174 res = twCon.DirectMessages(20, null, null, ref content);
2181 res = twCon.DirectMessagesSent(20, minDirectmessageSent, null, ref content);
2185 res = twCon.DirectMessagesSent(20, null, null, ref content);
2191 return "Err:" + ex.Message;
2194 var err = this.CheckStatusCode(res, content);
2195 if (err != null) return err;
2197 return CreateDirectMessagesFromJson(content, gType, read);
2200 static int page_ = 1;
2201 public string GetFavoritesApi(bool read,
2202 MyCommon.WORKERTYPE gType,
2205 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2207 if (MyCommon._endingFlag) return "";
2209 var count = SettingCommon.Instance.CountApi;
2210 if (SettingCommon.Instance.UseAdditionalCount &&
2211 SettingCommon.Instance.FavoritesCountApi != 0)
2213 count = SettingCommon.Instance.FavoritesCountApi;
2216 // 前ページ取得の場合はページカウンタをインクリメント、それ以外の場合はページカウンタリセット
2230 res = twCon.Favorites(count, page_, ref content);
2234 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2237 var err = this.CheckStatusCode(res, content);
2238 if (err != null) return err;
2240 TwitterStatus[] item;
2243 item = TwitterStatus.ParseJsonArray(content);
2245 catch(SerializationException ex)
2247 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2248 return "Json Parse Error(DataContractJsonSerializer)";
2252 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2253 return "Invalid Json!";
2256 foreach (var status in item)
2258 var post = new PostClass();
2259 TwitterEntities entities;
2263 post.StatusId = status.Id;
2267 if (TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Contains(post.StatusId)) continue;
2270 if (status.RetweetedStatus != null)
2272 var retweeted = status.RetweetedStatus;
2273 post.CreatedAt = MyCommon.DateTimeParse(retweeted.CreatedAt);
2276 post.RetweetedId = post.StatusId;
2278 post.TextFromApi = retweeted.Text;
2279 entities = retweeted.MergedEntities;
2280 //Source取得(htmlの場合は、中身を取り出し)
2281 post.Source = retweeted.Source;
2283 post.InReplyToStatusId = retweeted.InReplyToStatusId;
2284 post.InReplyToUser = retweeted.InReplyToScreenName;
2285 post.InReplyToUserId = retweeted.InReplyToUserId;
2289 var user = retweeted.User;
2290 post.UserId = user.Id;
2291 post.ScreenName = user.ScreenName;
2292 post.Nickname = user.Name.Trim();
2293 post.ImageUrl = user.ProfileImageUrlHttps;
2294 post.IsProtect = user.Protected;
2297 post.RetweetedBy = status.User.ScreenName;
2298 post.IsMe = post.RetweetedBy.ToLower().Equals(_uname);
2302 post.CreatedAt = MyCommon.DateTimeParse(status.CreatedAt);
2305 post.TextFromApi = status.Text;
2306 entities = status.MergedEntities;
2307 //Source取得(htmlの場合は、中身を取り出し)
2308 post.Source = status.Source;
2309 post.InReplyToStatusId = status.InReplyToStatusId;
2310 post.InReplyToUser = status.InReplyToScreenName;
2311 post.InReplyToUserId = status.InReplyToUserId;
2316 var user = status.User;
2317 post.UserId = user.Id;
2318 post.ScreenName = user.ScreenName;
2319 post.Nickname = user.Name.Trim();
2320 post.ImageUrl = user.ProfileImageUrlHttps;
2321 post.IsProtect = user.Protected;
2322 post.IsMe = post.ScreenName.ToLower().Equals(_uname);
2325 string textFromApi = post.TextFromApi;
2326 post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, entities, post.Media);
2327 post.TextFromApi = textFromApi;
2328 post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities);
2329 post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
2330 post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
2335 post.IsReply = post.ReplyToList.Contains(_uname);
2336 post.IsExcludeReply = false;
2344 if (followerId.Count > 0) post.IsOwl = !followerId.Contains(post.UserId);
2351 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2355 TabInformations.GetInstance().AddPost(post);
2362 private string ReplaceTextFromApi(string text, TwitterEntities entities)
2364 if (entities != null)
2366 if (entities.Urls != null)
2368 foreach (var m in entities.Urls)
2370 if (!string.IsNullOrEmpty(m.DisplayUrl)) text = text.Replace(m.Url, m.DisplayUrl);
2373 if (entities.Media != null)
2375 foreach (var m in entities.Media)
2377 if (!string.IsNullOrEmpty(m.DisplayUrl)) text = text.Replace(m.Url, m.DisplayUrl);
2387 /// <exception cref="WebApiException"/>
2388 public void RefreshFollowerIds()
2390 if (MyCommon._endingFlag) return;
2393 var newFollowerIds = new List<long>();
2396 var ret = this.GetFollowerIdsApi(ref cursor);
2397 newFollowerIds.AddRange(ret.Ids);
2398 cursor = ret.NextCursor;
2399 } while (cursor != 0);
2401 this.followerId = newFollowerIds;
2402 TabInformations.GetInstance().RefreshOwl(this.followerId);
2404 this._GetFollowerResult = true;
2407 public bool GetFollowersSuccess
2411 return _GetFollowerResult;
2415 private TwitterIds GetFollowerIdsApi(ref long cursor)
2417 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2418 throw new WebApiException("AccountState invalid");
2424 res = twCon.FollowerIds(cursor, ref content);
2428 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2431 var err = this.CheckStatusCode(res, content);
2433 throw new WebApiException(err, content);
2437 var ret = TwitterIds.ParseJson(content);
2439 if (ret.Ids == null)
2441 var ex = new WebApiException("Err: ret.id == null (GetFollowerIdsApi)", content);
2442 MyCommon.ExceptionOut(ex);
2448 catch(SerializationException e)
2450 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2451 MyCommon.TraceOut(ex);
2456 var ex = new WebApiException("Err:Invalid Json!", content, e);
2457 MyCommon.TraceOut(ex);
2463 /// RT 非表示ユーザーを更新します
2465 /// <exception cref="WebApiException"/>
2466 public void RefreshNoRetweetIds()
2468 if (MyCommon._endingFlag) return;
2470 this.noRTId = this.NoRetweetIdsApi();
2472 this._GetNoRetweetResult = true;
2475 private long[] NoRetweetIdsApi()
2477 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2478 throw new WebApiException("AccountState invalid");
2484 res = twCon.NoRetweetIds(ref content);
2488 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2491 var err = this.CheckStatusCode(res, content);
2493 throw new WebApiException(err, content);
2497 return MyCommon.CreateDataFromJson<long[]>(content);
2499 catch(SerializationException e)
2501 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2502 MyCommon.TraceOut(ex);
2507 var ex = new WebApiException("Err:Invalid Json!", content, e);
2508 MyCommon.TraceOut(ex);
2513 public bool GetNoRetweetSuccess
2517 return _GetNoRetweetResult;
2522 /// t.co の文字列長などの設定情報を更新します
2524 /// <exception cref="WebApiException"/>
2525 public void RefreshConfiguration()
2527 this.Configuration = this.ConfigurationApi();
2530 private TwitterConfiguration ConfigurationApi()
2536 res = twCon.GetConfiguration(ref content);
2540 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2543 var err = this.CheckStatusCode(res, content);
2545 throw new WebApiException(err, content);
2549 return TwitterConfiguration.ParseJson(content);
2551 catch(SerializationException e)
2553 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2554 MyCommon.TraceOut(ex);
2559 var ex = new WebApiException("Err:Invalid Json!", content, e);
2560 MyCommon.TraceOut(ex);
2565 public string GetListsApi()
2567 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2570 IEnumerable<ListElement> lists;
2575 res = twCon.GetLists(this.Username, ref content);
2577 catch (Exception ex)
2579 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2582 var err = this.CheckStatusCode(res, content);
2583 if (err != null) return err;
2587 lists = TwitterList.ParseJsonArray(content)
2588 .Select(x => new ListElement(x, this));
2590 catch (SerializationException ex)
2592 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2593 return "Err:Json Parse Error(DataContractJsonSerializer)";
2595 catch (Exception ex)
2597 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2598 return "Err:Invalid Json!";
2603 res = twCon.GetListsSubscriptions(this.Username, ref content);
2605 catch (Exception ex)
2607 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2610 err = this.CheckStatusCode(res, content);
2611 if (err != null) return err;
2615 lists = lists.Concat(TwitterList.ParseJsonArray(content)
2616 .Select(x => new ListElement(x, this)));
2618 catch (SerializationException ex)
2620 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2621 return "Err:Json Parse Error(DataContractJsonSerializer)";
2623 catch (Exception ex)
2625 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2626 return "Err:Invalid Json!";
2629 TabInformations.GetInstance().SubscribableLists = lists.ToList();
2633 public string DeleteList(string list_id)
2640 res = twCon.DeleteListID(this.Username, list_id, ref content);
2644 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2647 var err = this.CheckStatusCode(res, content);
2648 if (err != null) return err;
2653 public string EditList(string list_id, string new_name, bool isPrivate, string description, ref ListElement list)
2660 res = twCon.UpdateListID(this.Username, list_id, new_name, isPrivate, description, ref content);
2664 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2667 var err = this.CheckStatusCode(res, content);
2668 if (err != null) return err;
2672 var le = TwitterList.ParseJson(content);
2673 var newList = new ListElement(le, this);
2674 list.Description = newList.Description;
2675 list.Id = newList.Id;
2676 list.IsPublic = newList.IsPublic;
2677 list.MemberCount = newList.MemberCount;
2678 list.Name = newList.Name;
2679 list.SubscriberCount = newList.SubscriberCount;
2680 list.Slug = newList.Slug;
2681 list.Nickname = newList.Nickname;
2682 list.Username = newList.Username;
2683 list.UserId = newList.UserId;
2686 catch(SerializationException ex)
2688 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2689 return "Err:Json Parse Error(DataContractJsonSerializer)";
2693 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2694 return "Err:Invalid Json!";
2699 public string GetListMembers(string list_id, List<UserInfo> lists, ref long cursor)
2701 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2707 res = twCon.GetListMembers(this.Username, list_id, cursor, ref content);
2711 return "Err:" + ex.Message;
2714 var err = this.CheckStatusCode(res, content);
2715 if (err != null) return err;
2719 var users = TwitterUsers.ParseJson(content);
2720 Array.ForEach<TwitterUser>(
2722 u => lists.Add(new UserInfo(u)));
2723 cursor = users.NextCursor;
2726 catch(SerializationException ex)
2728 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2729 return "Err:Json Parse Error(DataContractJsonSerializer)";
2733 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2734 return "Err:Invalid Json!";
2738 public string CreateListApi(string listName, bool isPrivate, string description)
2740 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2746 res = twCon.CreateLists(listName, isPrivate, description, ref content);
2750 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2753 var err = this.CheckStatusCode(res, content);
2754 if (err != null) return err;
2758 var le = TwitterList.ParseJson(content);
2759 TabInformations.GetInstance().SubscribableLists.Add(new ListElement(le, this));
2762 catch(SerializationException ex)
2764 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2765 return "Err:Json Parse Error(DataContractJsonSerializer)";
2769 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2770 return "Err:Invalid Json!";
2774 public string ContainsUserAtList(string listId, string user, ref bool value)
2778 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2785 res = this.twCon.ShowListMember(listId, user, ref content);
2789 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2792 if (res == HttpStatusCode.NotFound)
2798 var err = this.CheckStatusCode(res, content);
2799 if (err != null) return err;
2803 TwitterUser.ParseJson(content);
2814 public string AddUserToList(string listId, string user)
2821 res = twCon.CreateListMembers(listId, user, ref content);
2825 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2828 var err = this.CheckStatusCode(res, content);
2829 if (err != null) return err;
2834 public string RemoveUserToList(string listId, string user)
2841 res = twCon.DeleteListMembers(listId, user, ref content);
2845 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2848 var err = this.CheckStatusCode(res, content);
2849 if (err != null) return err;
2856 public int fromIndex { get; set; }
2857 public int toIndex { get; set; }
2858 public range(int fromIndex, int toIndex)
2860 this.fromIndex = fromIndex;
2861 this.toIndex = toIndex;
2864 public async Task<string> CreateHtmlAnchorAsync(string Text, List<string> AtList, Dictionary<string, string> media)
2866 if (Text == null) return null;
2867 var retStr = Text.Replace(">", "<<<<<tweenだいなり>>>>>").Replace("<", "<<<<<tweenしょうなり>>>>>");
2869 //const string url_valid_domain = "(?<domain>(?:[^\p{P}\s][\.\-_](?=[^\p{P}\s])|[^\p{P}\s]){1,}\.[a-z]{2,}(?::[0-9]+)?)"
2870 //const string url_valid_general_path_chars = "[a-z0-9!*';:=+$/%#\[\]\-_,~]"
2871 //const string url_balance_parens = "(?:\(" + url_valid_general_path_chars + "+\))"
2872 //const string url_valid_url_path_ending_chars = "(?:[a-z0-9=_#/\-\+]+|" + url_balance_parens + ")"
2873 //const string pth = "(?:" + url_balance_parens +
2874 // "|@" + url_valid_general_path_chars + "+/" +
2875 // "|[.,]?" + url_valid_general_path_chars + "+" +
2877 //const string pth2 = "(/(?:" +
2878 // pth + "+" + url_valid_url_path_ending_chars + "|" +
2879 // pth + "+" + url_valid_url_path_ending_chars + "?|" +
2880 // url_valid_url_path_ending_chars +
2882 //const string qry = "(?<query>\?[a-z0-9!*'();:&=+$/%#\[\]\-_.,~]*[a-z0-9_&=#])?"
2883 //const string rgUrl = "(?<before>(?:[^\""':!=#]|^|\:/))" +
2884 // "(?<url>(?<protocol>https?://)" +
2885 // url_valid_domain +
2889 //const string rgUrl = "(?<before>(?:[^\""':!=#]|^|\:/))" +
2890 // "(?<url>(?<protocol>https?://|www\.)" +
2891 // url_valid_domain +
2896 retStr = await new Regex(rgUrl, RegexOptions.IgnoreCase).ReplaceAsync(retStr, async mu =>
2898 var sb = new StringBuilder(mu.Result("${before}<a href=\""));
2899 //if (mu.Result("${protocol}").StartsWith("w", StringComparison.OrdinalIgnoreCase))
2900 // sb.Append("http://");
2902 var url = mu.Result("${url}");
2903 var title = await ShortUrl.Instance.ExpandUrlAsync(url);
2904 sb.Append(url + "\" title=\"" + MyCommon.ConvertToReadableUrl(title) + "\">").Append(url).Append("</a>");
2905 if (media != null && !media.ContainsKey(url)) media.Add(url, title);
2906 return sb.ToString();
2910 retStr = Regex.Replace(retStr,
2911 @"(^|[^a-zA-Z0-9_/])([@@]+)([a-zA-Z0-9_]{1,20}/[a-zA-Z][a-zA-Z0-9\p{IsLatin-1Supplement}\-]{0,79})",
2912 "$1$2<a href=\"/$3\">$3</a>");
2914 var m = Regex.Match(retStr, "(^|[^a-zA-Z0-9_])[@@]([a-zA-Z0-9_]{1,20})");
2917 if (!AtList.Contains(m.Result("$2").ToLower())) AtList.Add(m.Result("$2").ToLower());
2921 retStr = Regex.Replace(retStr,
2922 "(^|[^a-zA-Z0-9_/])([@@])([a-zA-Z0-9_]{1,20})",
2923 "$1$2<a href=\"/$3\">$3</a>");
2926 var anchorRange = new List<range>();
2927 for (int i = 0; i < retStr.Length; i++)
2929 var index = retStr.IndexOf("<a ", i);
2930 if (index > -1 && index < retStr.Length)
2933 var toIndex = retStr.IndexOf("</a>", index);
2936 anchorRange.Add(new range(index, toIndex + 3));
2941 //retStr = Regex.Replace(retStr,
2942 // "(^|[^a-zA-Z0-9/&])([##])([0-9a-zA-Z_]*[a-zA-Z_]+[a-zA-Z0-9_\xc0-\xd6\xd8-\xf6\xf8-\xff]*)",
2943 // new MatchEvaluator(Function(mh As Match)
2944 // foreach (var rng in anchorRange)
2946 // if (mh.Index >= rng.fromIndex &&
2947 // mh.Index <= rng.toIndex) return mh.Result("$0");
2949 // if (IsNumeric(mh.Result("$3"))) return mh.Result("$0");
2952 // _hashList.Add("#" + mh.Result("$3"))
2954 // return mh.Result("$1") + "<a href=\"" + _protocol + "twitter.com/search?q=%23" + mh.Result("$3") + "\">" + mh.Result("$2$3") + "</a>";
2956 // RegexOptions.IgnoreCase)
2957 retStr = Regex.Replace(retStr,
2959 new MatchEvaluator(mh =>
2961 foreach (var rng in anchorRange)
2963 if (mh.Index >= rng.fromIndex &&
2964 mh.Index <= rng.toIndex) return mh.Result("$0");
2968 _hashList.Add("#" + mh.Result("$3"));
2970 return mh.Result("$1") + "<a href=\"https://twitter.com/search?q=%23" + mh.Result("$3") + "\">" + mh.Result("$2$3") + "</a>";
2972 RegexOptions.IgnoreCase);
2975 retStr = Regex.Replace(retStr, "(^|[^a-zA-Z0-9_/&##@@>=.~])(sm|nm)([0-9]{1,10})", "$1<a href=\"http://www.nicovideo.jp/watch/$2$3\">$2$3</a>");
2977 retStr = retStr.Replace("<<<<<tweenだいなり>>>>>", ">").Replace("<<<<<tweenしょうなり>>>>>", "<");
2979 //retStr = AdjustHtml(ShortUrl.Resolve(PreProcessUrl(retStr), true)) //IDN置換、短縮Uri解決、@リンクを相対→絶対にしてtarget属性付与
2980 retStr = AdjustHtml(PreProcessUrl(retStr)); //IDN置換、短縮Uri解決、@リンクを相対→絶対にしてtarget属性付与
2984 public async Task<string> CreateHtmlAnchorAsync(string text, List<string> AtList, TwitterEntities entities, List<string> media)
2986 if (entities != null)
2988 if (entities.Urls != null)
2990 foreach (var ent in entities.Urls)
2992 ent.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(ent.ExpandedUrl)
2993 .ConfigureAwait(false);
2995 if (media != null && !media.Contains(ent.ExpandedUrl))
2996 media.Add(ent.ExpandedUrl);
2999 if (entities.Hashtags != null)
3003 this._hashList.AddRange(entities.Hashtags.Select(x => "#" + x.Text));
3006 if (entities.UserMentions != null)
3008 foreach (var ent in entities.UserMentions)
3010 var screenName = ent.ScreenName.ToLower();
3011 if (!AtList.Contains(screenName))
3012 AtList.Add(screenName);
3015 if (entities.Media != null)
3019 foreach (var ent in entities.Media)
3021 if (!media.Contains(ent.MediaUrl))
3022 media.Add(ent.MediaUrl);
3028 text = TweetFormatter.AutoLinkHtml(text, entities);
3030 text = Regex.Replace(text, "(^|[^a-zA-Z0-9_/&##@@>=.~])(sm|nm)([0-9]{1,10})", "$1<a href=\"http://www.nicovideo.jp/watch/$2$3\">$2$3</a>");
3031 text = PreProcessUrl(text); //IDN置換
3037 public string CreateHtmlAnchor(string text, List<string> AtList, TwitterEntities entities, List<string> media)
3039 return this.CreateHtmlAnchorAsync(text, AtList, entities, media).Result;
3043 private void CreateSource(PostClass post)
3045 if (string.IsNullOrEmpty(post.Source)) return;
3047 if (post.Source.StartsWith("<"))
3049 if (!post.Source.Contains("</a>"))
3051 post.Source += "</a>";
3053 var mS = Regex.Match(post.Source, ">(?<source>.+)<");
3056 post.SourceHtml = ShortUrl.Instance.ExpandUrlHtml(PreProcessUrl(post.Source));
3057 post.Source = WebUtility.HtmlDecode(mS.Result("${source}"));
3062 post.SourceHtml = "";
3067 if (post.Source == "web")
3069 post.SourceHtml = Properties.Resources.WebSourceString;
3071 else if (post.Source == "Keitai Mail")
3073 post.SourceHtml = Properties.Resources.KeitaiMailSourceString;
3077 post.SourceHtml = post.Source;
3082 public TwitterApiStatus GetInfoApi()
3084 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return null;
3086 if (MyCommon._endingFlag) return null;
3092 res = twCon.RateLimitStatus(ref content);
3096 this.ResetApiStatus();
3100 var err = this.CheckStatusCode(res, content);
3101 if (err != null) return null;
3105 MyCommon.TwitterApiInfo.UpdateFromJson(content);
3106 return MyCommon.TwitterApiInfo;
3108 catch (Exception ex)
3110 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
3111 MyCommon.TwitterApiInfo.Reset();
3117 /// ブロック中のユーザーを更新します
3119 /// <exception cref="WebApiException"/>
3120 public void RefreshBlockIds()
3122 if (MyCommon._endingFlag) return;
3125 var newBlockIds = new List<long>();
3128 var ret = this.GetBlockIdsApi(cursor);
3129 newBlockIds.AddRange(ret.Ids);
3130 cursor = ret.NextCursor;
3131 } while (cursor != 0);
3133 newBlockIds.Remove(this.UserId); // 元のソースにあったので一応残しておく
3135 TabInformations.GetInstance().BlockIds = newBlockIds;
3138 public TwitterIds GetBlockIdsApi(long cursor)
3140 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
3141 throw new WebApiException("AccountState invalid");
3147 res = twCon.GetBlockUserIds(ref content, cursor);
3151 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
3154 var err = this.CheckStatusCode(res, content);
3156 throw new WebApiException(err, content);
3160 return TwitterIds.ParseJson(content);
3162 catch(SerializationException e)
3164 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
3165 MyCommon.TraceOut(ex);
3170 var ex = new WebApiException("Err:Invalid Json!", content, e);
3171 MyCommon.TraceOut(ex);
3177 /// ミュート中のユーザーIDを更新します
3179 /// <exception cref="WebApiException"/>
3180 public async Task RefreshMuteUserIdsAsync()
3182 if (MyCommon._endingFlag) return;
3184 var ids = await TwitterIds.GetAllItemsAsync(this.GetMuteUserIdsApiAsync)
3185 .ConfigureAwait(false);
3187 TabInformations.GetInstance().MuteUserIds = ids.ToList();
3190 public async Task<TwitterIds> GetMuteUserIdsApiAsync(long cursor)
3196 var res = await Task.Run(() => twCon.GetMuteUserIds(ref content, cursor))
3197 .ConfigureAwait(false);
3199 var err = this.CheckStatusCode(res, content);
3201 throw new WebApiException(err, content);
3203 return TwitterIds.ParseJson(content);
3205 catch (WebException ex)
3207 var ex2 = new WebApiException("Err:" + ex.Message, ex);
3208 MyCommon.TraceOut(ex2);
3211 catch (SerializationException ex)
3213 var ex2 = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, ex);
3214 MyCommon.TraceOut(ex2);
3219 public string[] GetHashList()
3224 hashArray = _hashList.ToArray();
3230 public string AccessToken
3234 return twCon.AccessToken;
3238 public string AccessTokenSecret
3242 return twCon.AccessTokenSecret;
3246 private string CheckStatusCode(HttpStatusCode httpStatus, string responseText,
3247 [CallerMemberName] string callerMethodName = "")
3249 if (httpStatus == HttpStatusCode.OK)
3251 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3255 // 404エラーの挙動が変なので無視: https://dev.twitter.com/discussions/1213
3256 if (httpStatus == HttpStatusCode.NotFound) return null;
3258 if (string.IsNullOrWhiteSpace(responseText))
3260 if (httpStatus == HttpStatusCode.Unauthorized)
3261 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3263 return "Err:" + httpStatus + "(" + callerMethodName + ")";
3268 var errors = TwitterError.ParseJson(responseText).Errors;
3269 if (errors == null || !errors.Any())
3271 return "Err:" + httpStatus + "(" + callerMethodName + ")";
3274 foreach (var error in errors)
3276 if (error.Code == TwitterErrorCode.InvalidToken ||
3277 error.Code == TwitterErrorCode.SuspendedAccount)
3279 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3283 return "Err:" + string.Join(",", errors.Select(x => x.ToString())) + "(" + callerMethodName + ")";
3285 catch (SerializationException) { }
3287 return "Err:" + httpStatus + "(" + callerMethodName + ")";
3290 #region "UserStream"
3291 private string trackWord_ = "";
3292 public string TrackWord
3303 private bool allAtReply_ = false;
3304 public bool AllAtReply
3312 allAtReply_ = value;
3316 public event EventHandler NewPostFromStream;
3317 public event EventHandler UserStreamStarted;
3318 public event EventHandler UserStreamStopped;
3319 public event EventHandler<PostDeletedEventArgs> PostDeleted;
3320 public event EventHandler<UserStreamEventReceivedEventArgs> UserStreamEventReceived;
3321 private DateTime _lastUserstreamDataReceived;
3322 private TwitterUserstream userStream;
3324 public class FormattedEvent
3326 public MyCommon.EVENTTYPE Eventtype { get; set; }
3327 public DateTime CreatedAt { get; set; }
3328 public string Event { get; set; }
3329 public string Username { get; set; }
3330 public string Target { get; set; }
3331 public Int64 Id { get; set; }
3332 public bool IsMe { get; set; }
3335 public List<FormattedEvent> storedEvent_ = new List<FormattedEvent>();
3336 public List<FormattedEvent> StoredEvent
3340 return storedEvent_;
3344 storedEvent_ = value;
3348 private readonly IReadOnlyDictionary<string, MyCommon.EVENTTYPE> eventTable = new Dictionary<string, MyCommon.EVENTTYPE>
3350 { "favorite", MyCommon.EVENTTYPE.Favorite },
3351 { "unfavorite", MyCommon.EVENTTYPE.Unfavorite },
3352 { "follow", MyCommon.EVENTTYPE.Follow },
3353 { "list_member_added", MyCommon.EVENTTYPE.ListMemberAdded },
3354 { "list_member_removed", MyCommon.EVENTTYPE.ListMemberRemoved },
3355 { "block", MyCommon.EVENTTYPE.Block },
3356 { "unblock", MyCommon.EVENTTYPE.Unblock },
3357 { "user_update", MyCommon.EVENTTYPE.UserUpdate },
3358 { "deleted", MyCommon.EVENTTYPE.Deleted },
3359 { "list_created", MyCommon.EVENTTYPE.ListCreated },
3360 { "list_destroyed", MyCommon.EVENTTYPE.ListDestroyed },
3361 { "list_updated", MyCommon.EVENTTYPE.ListUpdated },
3362 { "unfollow", MyCommon.EVENTTYPE.Unfollow },
3363 { "list_user_subscribed", MyCommon.EVENTTYPE.ListUserSubscribed },
3364 { "list_user_unsubscribed", MyCommon.EVENTTYPE.ListUserUnsubscribed },
3365 { "mute", MyCommon.EVENTTYPE.Mute },
3366 { "unmute", MyCommon.EVENTTYPE.Unmute }
3369 public bool IsUserstreamDataReceived
3373 return DateTime.Now.Subtract(this._lastUserstreamDataReceived).TotalSeconds < 31;
3377 private void userStream_StatusArrived(string line)
3379 this._lastUserstreamDataReceived = DateTime.Now;
3380 if (string.IsNullOrEmpty(line)) return;
3382 if (line.First() != '{' || line.Last() != '}')
3384 MyCommon.TraceOut("Invalid JSON (StatusArrived):" + Environment.NewLine + line);
3392 using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(line), XmlDictionaryReaderQuotas.Max))
3394 var xElm = XElement.Load(jsonReader);
3395 if (xElm.Element("friends") != null)
3397 Debug.WriteLine("friends");
3400 else if (xElm.Element("delete") != null)
3402 Debug.WriteLine("delete");
3404 if (xElm.Element("delete").Element("direct_message") != null &&
3405 xElm.Element("delete").Element("direct_message").Element("id") != null)
3408 long.TryParse(xElm.Element("delete").Element("direct_message").Element("id").Value, out id);
3410 if (this.PostDeleted != null)
3411 this.PostDeleted(this, new PostDeletedEventArgs(id));
3413 else if (xElm.Element("delete").Element("status") != null &&
3414 xElm.Element("delete").Element("status").Element("id") != null)
3417 long.TryParse(xElm.Element("delete").Element("status").Element("id").Value, out id);
3419 if (this.PostDeleted != null)
3420 this.PostDeleted(this, new PostDeletedEventArgs(id));
3424 MyCommon.TraceOut("delete:" + line);
3427 for (int i = this.StoredEvent.Count - 1; i >= 0; i--)
3429 var sEvt = this.StoredEvent[i];
3430 if (sEvt.Id == id && (sEvt.Event == "favorite" || sEvt.Event == "unfavorite"))
3432 this.StoredEvent.RemoveAt(i);
3437 else if (xElm.Element("limit") != null)
3439 Debug.WriteLine(line);
3442 else if (xElm.Element("event") != null)
3444 Debug.WriteLine("event: " + xElm.Element("event").Value);
3445 CreateEventFromJson(line);
3448 else if (xElm.Element("direct_message") != null)
3450 Debug.WriteLine("direct_message");
3453 else if (xElm.Element("scrub_geo") != null)
3457 TabInformations.GetInstance().ScrubGeoReserve(long.Parse(xElm.Element("scrub_geo").Element("user_id").Value),
3458 long.Parse(xElm.Element("scrub_geo").Element("up_to_status_id").Value));
3462 MyCommon.TraceOut("scrub_geo:" + line);
3470 CreateDirectMessagesFromJson(line, MyCommon.WORKERTYPE.UserStream, false);
3475 CreatePostsFromJson("[" + line + "]", MyCommon.WORKERTYPE.Timeline, null, false, 0, ref dummy);
3478 catch(NullReferenceException)
3480 MyCommon.TraceOut("NullRef StatusArrived: " + line);
3483 if (this.NewPostFromStream != null)
3484 this.NewPostFromStream(this, EventArgs.Empty);
3487 private void CreateEventFromJson(string content)
3489 TwitterStreamEvent eventData = null;
3492 eventData = TwitterStreamEvent.ParseJson(content);
3494 catch(SerializationException ex)
3496 MyCommon.TraceOut(ex, "Event Serialize Exception!" + Environment.NewLine + content);
3500 MyCommon.TraceOut(ex, "Event Exception!" + Environment.NewLine + content);
3503 var evt = new FormattedEvent();
3504 evt.CreatedAt = MyCommon.DateTimeParse(eventData.CreatedAt);
3505 evt.Event = eventData.Event;
3506 evt.Username = eventData.Source.ScreenName;
3507 evt.IsMe = evt.Username.ToLower().Equals(this.Username.ToLower());
3509 MyCommon.EVENTTYPE eventType;
3510 eventTable.TryGetValue(eventData.Event, out eventType);
3511 evt.Eventtype = eventType;
3513 switch (eventData.Event)
3515 case "access_revoked":
3516 case "access_unrevoked":
3518 case "user_suspend":
3521 if (eventData.Target.ScreenName.ToLower().Equals(_uname))
3523 if (!this.followerId.Contains(eventData.Source.Id)) this.followerId.Add(eventData.Source.Id);
3527 return; //Block後のUndoをすると、SourceとTargetが逆転したfollowイベントが帰ってくるため。
3532 evt.Target = "@" + eventData.Target.ScreenName;
3536 var tweetEvent = TwitterStreamEvent<TwitterStatus>.ParseJson(content);
3537 evt.Target = "@" + tweetEvent.TargetObject.User.ScreenName + ":" + WebUtility.HtmlDecode(tweetEvent.TargetObject.Text);
3538 evt.Id = tweetEvent.TargetObject.Id;
3539 if (SettingCommon.Instance.IsRemoveSameEvent)
3541 if (StoredEvent.Any(ev =>
3543 return ev.Username == evt.Username && ev.Eventtype == evt.Eventtype && ev.Target == evt.Target;
3546 if (TabInformations.GetInstance().ContainsKey(tweetEvent.TargetObject.Id))
3548 var post = TabInformations.GetInstance()[tweetEvent.TargetObject.Id];
3549 if (eventData.Event == "favorite")
3551 if (evt.Username.ToLower().Equals(_uname))
3554 TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, false);
3558 post.FavoritedCount++;
3559 if (!TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Contains(post.StatusId))
3561 if (SettingCommon.Instance.FavEventUnread && post.IsRead)
3563 post.IsRead = false;
3565 TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, false);
3569 if (SettingCommon.Instance.FavEventUnread)
3571 TabInformations.GetInstance().SetRead(false, TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).TabName, TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).IndexOf(post.StatusId));
3578 if (evt.Username.ToLower().Equals(_uname))
3584 post.FavoritedCount--;
3585 if (post.FavoritedCount < 0) post.FavoritedCount = 0;
3590 case "list_member_added":
3591 case "list_member_removed":
3592 case "list_created":
3593 case "list_destroyed":
3594 case "list_updated":
3595 case "list_user_subscribed":
3596 case "list_user_unsubscribed":
3597 var listEvent = TwitterStreamEvent<TwitterList>.ParseJson(content);
3598 evt.Target = listEvent.TargetObject.FullName;
3601 if (!TabInformations.GetInstance().BlockIds.Contains(eventData.Target.Id)) TabInformations.GetInstance().BlockIds.Add(eventData.Target.Id);
3605 if (TabInformations.GetInstance().BlockIds.Contains(eventData.Target.Id)) TabInformations.GetInstance().BlockIds.Remove(eventData.Target.Id);
3614 evt.Target = "@" + eventData.Target.ScreenName;
3615 if (!TabInformations.GetInstance().MuteUserIds.Contains(eventData.Target.Id))
3617 TabInformations.GetInstance().MuteUserIds.Add(eventData.Target.Id);
3621 evt.Target = "@" + eventData.Target.ScreenName;
3622 if (TabInformations.GetInstance().MuteUserIds.Contains(eventData.Target.Id))
3624 TabInformations.GetInstance().MuteUserIds.Remove(eventData.Target.Id);
3629 MyCommon.TraceOut("Unknown Event:" + evt.Event + Environment.NewLine + content);
3632 this.StoredEvent.Insert(0, evt);
3634 if (this.UserStreamEventReceived != null)
3635 this.UserStreamEventReceived(this, new UserStreamEventReceivedEventArgs(evt));
3638 private void userStream_Started()
3640 if (this.UserStreamStarted != null)
3641 this.UserStreamStarted(this, EventArgs.Empty);
3644 private void userStream_Stopped()
3646 if (this.UserStreamStopped != null)
3647 this.UserStreamStopped(this, EventArgs.Empty);
3650 public bool UserStreamEnabled
3654 return userStream == null ? false : userStream.Enabled;
3658 public void StartUserStream()
3660 if (userStream != null)
3664 userStream = new TwitterUserstream(twCon);
3665 userStream.StatusArrived += userStream_StatusArrived;
3666 userStream.Started += userStream_Started;
3667 userStream.Stopped += userStream_Stopped;
3668 userStream.Start(this.AllAtReply, this.TrackWord);
3671 public void StopUserStream()
3673 if (userStream != null) userStream.Dispose();
3675 if (!MyCommon._endingFlag)
3677 if (this.UserStreamStopped != null)
3678 this.UserStreamStopped(this, EventArgs.Empty);
3682 public void ReconnectUserStream()
3684 if (userStream != null)
3686 this.StartUserStream();
3690 private class TwitterUserstream : IDisposable
3692 public event Action<string> StatusArrived;
3693 public event Action Stopped;
3694 public event Action Started;
3695 private HttpTwitter twCon;
3697 private Thread _streamThread;
3698 private bool _streamActive;
3700 private bool _allAtreplies = false;
3701 private string _trackwords = "";
3703 public TwitterUserstream(HttpTwitter twitterConnection)
3705 twCon = (HttpTwitter)twitterConnection.Clone();
3708 public void Start(bool allAtReplies, string trackwords)
3710 this.AllAtReplies = allAtReplies;
3711 this.TrackWords = trackwords;
3712 _streamActive = true;
3713 if (_streamThread != null && _streamThread.IsAlive) return;
3714 _streamThread = new Thread(UserStreamLoop);
3715 _streamThread.Name = "UserStreamReceiver";
3716 _streamThread.IsBackground = true;
3717 _streamThread.Start();
3724 return _streamActive;
3728 public bool AllAtReplies
3732 return _allAtreplies;
3736 _allAtreplies = value;
3740 public string TrackWords
3748 _trackwords = value;
3752 private void UserStreamLoop()
3758 StreamReader sr = null;
3761 if (!MyCommon.IsNetworkAvailable())
3767 if (Started != null)
3771 var res = twCon.UserStream(ref st, _allAtreplies, _trackwords, Networking.GetUserAgentString());
3775 case HttpStatusCode.OK:
3776 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3778 case HttpStatusCode.Unauthorized:
3779 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3787 //MyCommon.TraceOut("Stop:stream is null")
3791 sr = new StreamReader(st);
3793 while (_streamActive && !sr.EndOfStream && Twitter.AccountState == MyCommon.ACCOUNT_STATE.Valid)
3795 if (StatusArrived != null)
3797 StatusArrived(sr.ReadLine());
3799 //this.LastTime = Now;
3802 if (sr.EndOfStream || Twitter.AccountState == MyCommon.ACCOUNT_STATE.Invalid)
3805 //MyCommon.TraceOut("Stop:EndOfStream")
3810 catch(WebException ex)
3812 if (ex.Status == WebExceptionStatus.Timeout)
3814 sleepSec = 30; //MyCommon.TraceOut("Stop:Timeout")
3816 else if (ex.Response != null && (int)((HttpWebResponse)ex.Response).StatusCode == 420)
3818 //MyCommon.TraceOut("Stop:Connection Limit")
3824 //MyCommon.TraceOut("Stop:WebException " + ex.Status.ToString())
3827 catch(ThreadAbortException)
3834 //MyCommon.TraceOut("Stop:IOException with Active." + Environment.NewLine + ex.Message)
3836 catch(ArgumentException ex)
3838 //System.ArgumentException: ストリームを読み取れませんでした。
3839 //サーバー側もしくは通信経路上で切断された場合?タイムアウト頻発後発生
3841 MyCommon.TraceOut(ex, "Stop:ArgumentException");
3845 MyCommon.TraceOut("Stop:Exception." + Environment.NewLine + ex.Message);
3846 MyCommon.ExceptionOut(ex);
3853 if (Stopped != null)
3858 twCon.RequestAbort();
3859 if (sr != null) sr.Close();
3863 while (_streamActive && ms < sleepSec * 1000)
3871 } while (this._streamActive);
3875 if (Stopped != null)
3880 MyCommon.TraceOut("Stop:EndLoop");
3883 #region "IDisposable Support"
3884 private bool disposedValue; // 重複する呼び出しを検出するには
3887 protected virtual void Dispose(bool disposing)
3889 if (!this.disposedValue)
3893 _streamActive = false;
3894 if (_streamThread != null && _streamThread.IsAlive)
3896 _streamThread.Abort();
3900 this.disposedValue = true;
3903 //protected Overrides void Finalize()
3905 // // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3907 // MyBase.Finalize()
3910 // このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
3911 public void Dispose()
3913 // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3915 GC.SuppressFinalize(this);
3922 #region "IDisposable Support"
3923 private bool disposedValue; // 重複する呼び出しを検出するには
3926 protected virtual void Dispose(bool disposing)
3928 if (!this.disposedValue)
3932 this.StopUserStream();
3935 this.disposedValue = true;
3938 //protected Overrides void Finalize()
3940 // // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3942 // MyBase.Finalize()
3945 // このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
3946 public void Dispose()
3948 // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3950 GC.SuppressFinalize(this);
3955 public class PostDeletedEventArgs : EventArgs
3957 public long StatusId
3959 get { return this.statusId; }
3962 private readonly long statusId;
3964 public PostDeletedEventArgs(long statusId)
3966 this.statusId = statusId;
3970 public class UserStreamEventReceivedEventArgs : EventArgs
3972 public Twitter.FormattedEvent EventData
3974 get { return this.eventData; }
3977 private readonly Twitter.FormattedEvent eventData;
3979 public UserStreamEventReceivedEventArgs(Twitter.FormattedEvent eventData)
3981 this.eventData = eventData;