OSDN Git Service

5/28 (PDT, UTC-7) に追加予定の extended_entities に対応
[opentween/open-tween.git] / OpenTween / Twitter.cs
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.
10 //
11 // This file is part of OpenTween.
12 //
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)
16 // any later version.
17 //
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
21 // for more details.
22 //
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.
27
28 using System.Diagnostics;
29 using System.IO;
30 using System.Linq;
31 using System.Net;
32 using System.Runtime.CompilerServices;
33 using System.Runtime.Serialization;
34 using System.Runtime.Serialization.Json;
35 using System.Text;
36 using System.Text.RegularExpressions;
37 using System.Threading;
38 using System.Threading.Tasks;
39 using System.Web;
40 using System.Xml;
41 using System.Xml.Linq;
42 using System;
43 using System.Reflection;
44 using System.Collections.Generic;
45 using System.Drawing;
46 using System.Windows.Forms;
47 using OpenTween.Api;
48
49 namespace OpenTween
50 {
51     public class Twitter : IDisposable
52     {
53         #region Regexp from twitter-text-js
54
55         // The code in this region code block incorporates works covered by
56         // the following copyright and permission notices:
57         //
58         //   Copyright 2011 Twitter, Inc.
59         //
60         //   Licensed under the Apache License, Version 2.0 (the "License"); you
61         //   may not use this work except in compliance with the License. You
62         //   may obtain a copy of the License in the LICENSE file, or at:
63         //
64         //   http://www.apache.org/licenses/LICENSE-2.0
65         //
66         //   Unless required by applicable law or agreed to in writing, software
67         //   distributed under the License is distributed on an "AS IS" BASIS,
68         //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
69         //   implied. See the License for the specific language governing
70         //   permissions and limitations under the License.
71
72         //Hashtag用正規表現
73         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";
74         private const string NON_LATIN_HASHTAG_CHARS = @"\u0400-\u04ff\u0500-\u0527\u1100-\u11ff\u3130-\u3185\uA960-\uA97F\uAC00-\uD7AF\uD7B0-\uD7FF";
75         //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";
76         private const string CJ_HASHTAG_CHARACTERS = @"\u30A1-\u30FA\u30FC\u3005\uFF66-\uFF9F\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\u3041-\u309A\u3400-\u4DBF\p{IsCJKUnifiedIdeographs}";
77         private const string HASHTAG_BOUNDARY = @"^|$|\s|「|」|。|\.|!";
78         private const string HASHTAG_ALPHA = "[a-z_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
79         private const string HASHTAG_ALPHANUMERIC = "[a-z0-9_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
80         private const string HASHTAG_TERMINATOR = "[^a-z0-9_" + LATIN_ACCENTS + NON_LATIN_HASHTAG_CHARS + CJ_HASHTAG_CHARACTERS + "]";
81         public const string HASHTAG = "(" + HASHTAG_BOUNDARY + ")(#|#)(" + HASHTAG_ALPHANUMERIC + "*" + HASHTAG_ALPHA + HASHTAG_ALPHANUMERIC + "*)(?=" + HASHTAG_TERMINATOR + "|" + HASHTAG_BOUNDARY + ")";
82         //URL正規表現
83         private const string url_valid_preceding_chars = @"(?:[^A-Za-z0-9@@$##\ufffe\ufeff\uffff\u202a-\u202e]|^)";
84         public const string url_invalid_without_protocol_preceding_chars = @"[-_./]$";
85         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";
86         private const string url_valid_domain_chars = @"[^" + url_invalid_domain_chars + "]";
87         private const string url_valid_subdomain = @"(?:(?:" + url_valid_domain_chars + @"(?:[_-]|" + url_valid_domain_chars + @")*)?" + url_valid_domain_chars + @"\.)";
88         private const string url_valid_domain_name = @"(?:(?:" + url_valid_domain_chars + @"(?:-|" + url_valid_domain_chars + @")*)?" + url_valid_domain_chars + @"\.)";
89         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]|$))";
90         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]|$))";
91         private const string url_valid_punycode = @"(?:xn--[0-9a-z]+)";
92         private const string url_valid_domain = @"(?<domain>" + url_valid_subdomain + "*" + url_valid_domain_name + "(?:" + url_valid_GTLD + "|" + url_valid_CCTLD + ")|" + url_valid_punycode + ")";
93         public const string url_valid_ascii_domain = @"(?:(?:[a-z0-9" + LATIN_ACCENTS + @"]+)\.)+(?:" + url_valid_GTLD + "|" + url_valid_CCTLD + "|" + url_valid_punycode + ")";
94         public const string url_invalid_short_domain = "^" + url_valid_domain_name + url_valid_CCTLD + "$";
95         private const string url_valid_port_number = @"[0-9]+";
96
97         private const string url_valid_general_path_chars = @"[a-z0-9!*';:=+,.$/%#\[\]\-_~|&" + LATIN_ACCENTS + "]";
98         private const string url_balance_parens = @"(?:\(" + url_valid_general_path_chars + @"+\))";
99         private const string url_valid_path_ending_chars = @"(?:[+\-a-z0-9=_#/" + LATIN_ACCENTS + "]|" + url_balance_parens + ")";
100         private const string pth = "(?:" +
101             "(?:" +
102                 url_valid_general_path_chars + "*" +
103                 "(?:" + url_balance_parens + url_valid_general_path_chars + "*)*" +
104                 url_valid_path_ending_chars +
105                 ")|(?:@" + url_valid_general_path_chars + "+/)" +
106             ")";
107         private const string qry = @"(?<query>\?[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|]*[a-z0-9_&=#/])?";
108         public const string rgUrl = @"(?<before>" + url_valid_preceding_chars + ")" +
109                                     "(?<url>(?<protocol>https?://)?" +
110                                     "(?<domain>" + url_valid_domain + ")" +
111                                     "(?::" + url_valid_port_number + ")?" +
112                                     "(?<path>/" + pth + "*)?" +
113                                     qry +
114                                     ")";
115
116         #endregion
117
118         /// <summary>
119         /// Twitter API のステータスページのURL
120         /// </summary>
121         public const string ServiceAvailabilityStatusUrl = "https://status.io.watchmouse.com/7617";
122
123         /// <summary>
124         /// ツイートへのパーマリンクURLを判定する正規表現
125         /// </summary>
126         public static readonly Regex StatusUrlRegex = new Regex(@"https?://([^.]+\.)?twitter\.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)/status(es)?/(?<StatusId>[0-9]+)(/photo)?", RegexOptions.IgnoreCase);
127
128         /// <summary>
129         /// FavstarやaclogなどTwitter関連サービスのパーマリンクURLからステータスIDを抽出する正規表現
130         /// </summary>
131         public static readonly Regex ThirdPartyStatusUrlRegex = new Regex(@"https?://(?:[^.]+\.)?(?:
132   favstar\.fm/users/[a-zA-Z0-9_]+/status/       # Favstar
133 | favstar\.fm/t/                                # Favstar (short)
134 | aclog\.koba789\.com/i/                        # aclog
135 | frtrt\.net/solo_status\.php\?status=          # RtRT
136 )(?<StatusId>[0-9]+)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
137
138         delegate void GetIconImageDelegate(PostClass post);
139         private readonly object LockObj = new object();
140         private List<long> followerId = new List<long>();
141         private bool _GetFollowerResult = false;
142         private long[] noRTId = new long[0];
143         private bool _GetNoRetweetResult = false;
144
145         private int _followersCount = 0;
146         private int _friendsCount = 0;
147         private int _statusesCount = 0;
148         private string _location = "";
149         private string _bio = "";
150
151         //プロパティからアクセスされる共通情報
152         private string _uname;
153
154         private bool _tinyUrlResolve;
155         private bool _restrictFavCheck;
156
157         private bool _readOwnPost;
158         private List<string> _hashList = new List<string>();
159
160         //max_idで古い発言を取得するために保持(lists分は個別タブで管理)
161         private long minHomeTimeline = long.MaxValue;
162         private long minMentions = long.MaxValue;
163         private long minDirectmessage = long.MaxValue;
164         private long minDirectmessageSent = long.MaxValue;
165
166         //private FavoriteQueue favQueue;
167
168         private HttpTwitter twCon = new HttpTwitter();
169
170         //private List<PostClass> _deletemessages = new List<PostClass>();
171
172         public TwitterApiAccessLevel AccessLevel
173         {
174             get
175             {
176                 return MyCommon.TwitterApiInfo.AccessLevel;
177             }
178         }
179
180         protected void ResetApiStatus()
181         {
182             MyCommon.TwitterApiInfo.Reset();
183         }
184
185         public string Authenticate(string username, string password)
186         {
187             this.ResetApiStatus();
188
189             HttpStatusCode res;
190             var content = "";
191             try
192             {
193                 res = twCon.AuthUserAndPass(username, password, ref content);
194             }
195             catch(Exception ex)
196             {
197                 return "Err:" + ex.Message;
198             }
199
200             var err = this.CheckStatusCode(res, content);
201             if (err != null) return err;
202
203             _uname = username.ToLower();
204             if (AppendSettingDialog.Instance.UserstreamStartup) this.ReconnectUserStream();
205             return "";
206         }
207
208         public string StartAuthentication(ref string pinPageUrl)
209         {
210             //OAuth PIN Flow
211             bool res;
212
213             this.ResetApiStatus();
214             try
215             {
216                 res = twCon.AuthGetRequestToken(ref pinPageUrl);
217             }
218             catch(Exception)
219             {
220                 return "Err:" + "Failed to access auth server.";
221             }
222
223             return "";
224         }
225
226         public string Authenticate(string pinCode)
227         {
228             this.ResetApiStatus();
229
230             HttpStatusCode res;
231             try
232             {
233                 res = twCon.AuthGetAccessToken(pinCode);
234             }
235             catch(Exception)
236             {
237                 return "Err:" + "Failed to access auth acc server.";
238             }
239
240             var err = this.CheckStatusCode(res, null);
241             if (err != null) return err;
242
243             _uname = Username.ToLower();
244             if (AppendSettingDialog.Instance.UserstreamStartup) this.ReconnectUserStream();
245             return "";
246         }
247
248         public void ClearAuthInfo()
249         {
250             Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
251             this.ResetApiStatus();
252             twCon.ClearAuthInfo();
253         }
254
255         public void VerifyCredentials()
256         {
257             HttpStatusCode res;
258             var content = "";
259             try
260             {
261                 res = twCon.VerifyCredentials(ref content);
262             }
263             catch(Exception)
264             {
265                 return;
266             }
267
268             if (res == HttpStatusCode.OK)
269             {
270                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
271                 TwitterUser user;
272                 try
273                 {
274                     user = TwitterUser.ParseJson(content);
275                 }
276                 catch(SerializationException)
277                 {
278                     return;
279                 }
280                 twCon.AuthenticatedUserId = user.Id;
281             }
282         }
283
284         public void Initialize(string token, string tokenSecret, string username, long userId)
285         {
286             //OAuth認証
287             if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(tokenSecret) || string.IsNullOrEmpty(username))
288             {
289                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
290             }
291             this.ResetApiStatus();
292             twCon.Initialize(token, tokenSecret, username, userId);
293             _uname = username.ToLower();
294             if (AppendSettingDialog.Instance.UserstreamStartup) this.ReconnectUserStream();
295         }
296
297         public string PreProcessUrl(string orgData)
298         {
299             int posl1;
300             var posl2 = 0;
301             //var IDNConveter = new IdnMapping();
302             var href = "<a href=\"";
303
304             while (true)
305             {
306                 if (orgData.IndexOf(href, posl2, StringComparison.Ordinal) > -1)
307                 {
308                     var urlStr = "";
309                     // IDN展開
310                     posl1 = orgData.IndexOf(href, posl2, StringComparison.Ordinal);
311                     posl1 += href.Length;
312                     posl2 = orgData.IndexOf("\"", posl1, StringComparison.Ordinal);
313                     urlStr = orgData.Substring(posl1, posl2 - posl1);
314
315                     if (!urlStr.StartsWith("http://") && !urlStr.StartsWith("https://") && !urlStr.StartsWith("ftp://"))
316                     {
317                         continue;
318                     }
319
320                     var replacedUrl = MyCommon.IDNEncode(urlStr);
321                     if (replacedUrl == null) continue;
322                     if (replacedUrl == urlStr) continue;
323
324                     orgData = orgData.Replace("<a href=\"" + urlStr, "<a href=\"" + replacedUrl);
325                     posl2 = 0;
326                 }
327                 else
328                 {
329                     break;
330                 }
331             }
332             return orgData;
333         }
334
335         private string GetPlainText(string orgData)
336         {
337             return WebUtility.HtmlDecode(Regex.Replace(orgData, "(?<tagStart><a [^>]+>)(?<text>[^<]+)(?<tagEnd></a>)", "${text}"));
338         }
339
340         // htmlの簡易サニタイズ(詳細表示に不要なタグの除去)
341
342         private string SanitizeHtml(string orgdata)
343         {
344             var retdata = orgdata;
345
346             retdata = Regex.Replace(retdata, "<(script|object|applet|image|frameset|fieldset|legend|style).*" +
347                 "</(script|object|applet|image|frameset|fieldset|legend|style)>", "", RegexOptions.IgnoreCase);
348
349             retdata = Regex.Replace(retdata, "<(frame|link|iframe|img)>", "", RegexOptions.IgnoreCase);
350
351             return retdata;
352         }
353
354         private string AdjustHtml(string orgData)
355         {
356             var retStr = orgData;
357             //var m = Regex.Match(retStr, "<a [^>]+>[#|#](?<1>[a-zA-Z0-9_]+)</a>");
358             //while (m.Success)
359             //{
360             //    lock (LockObj)
361             //    {
362             //        _hashList.Add("#" + m.Groups(1).Value);
363             //    }
364             //    m = m.NextMatch;
365             //}
366             retStr = Regex.Replace(retStr, "<a [^>]*href=\"/", "<a href=\"https://twitter.com/");
367             retStr = retStr.Replace("<a href=", "<a target=\"_self\" href=");
368             retStr = Regex.Replace(retStr, @"(\r\n?|\n)", "<br>"); // CRLF, CR, LF は全て <br> に置換する
369
370             //半角スペースを置換(Thanks @anis774)
371             var ret = false;
372             do
373             {
374                 ret = EscapeSpace(ref retStr);
375             } while (!ret);
376
377             return SanitizeHtml(retStr);
378         }
379
380         private bool EscapeSpace(ref string html)
381         {
382             //半角スペースを置換(Thanks @anis774)
383             var isTag = false;
384             for (int i = 0; i < html.Length; i++)
385             {
386                 if (html[i] == '<')
387                 {
388                     isTag = true;
389                 }
390                 if (html[i] == '>')
391                 {
392                     isTag = false;
393                 }
394
395                 if ((!isTag) && (html[i] == ' '))
396                 {
397                     html = html.Remove(i, 1);
398                     html = html.Insert(i, "&nbsp;");
399                     return false;
400                 }
401             }
402             return true;
403         }
404
405         private struct PostInfo
406         {
407             public string CreatedAt;
408             public string Id;
409             public string Text;
410             public string UserId;
411             public PostInfo(string Created, string IdStr, string txt, string uid)
412             {
413                 CreatedAt = Created;
414                 Id = IdStr;
415                 Text = txt;
416                 UserId = uid;
417             }
418             public bool Equals(PostInfo dst)
419             {
420                 if (this.CreatedAt == dst.CreatedAt && this.Id == dst.Id && this.Text == dst.Text && this.UserId == dst.UserId)
421                 {
422                     return true;
423                 }
424                 else
425                 {
426                     return false;
427                 }
428             }
429         }
430
431         static private PostInfo _prev = new PostInfo("", "", "", "");
432         private bool IsPostRestricted(TwitterStatus status)
433         {
434             var _current = new PostInfo("", "", "", "");
435
436             _current.CreatedAt = status.CreatedAt;
437             _current.Id = status.IdStr;
438             if (status.Text == null)
439             {
440                 _current.Text = "";
441             }
442             else
443             {
444                 _current.Text = status.Text;
445             }
446             _current.UserId = status.User.IdStr;
447
448             if (_current.Equals(_prev))
449             {
450                 return true;
451             }
452             _prev.CreatedAt = _current.CreatedAt;
453             _prev.Id = _current.Id;
454             _prev.Text = _current.Text;
455             _prev.UserId = _current.UserId;
456
457             return false;
458         }
459
460         public string PostStatus(string postStr, long? reply_to)
461         {
462             if (MyCommon._endingFlag) return "";
463
464             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
465
466             if (Regex.Match(postStr, "^DM? +(?<id>[a-zA-Z0-9_]+) +(?<body>.+)", RegexOptions.IgnoreCase | RegexOptions.Singleline).Success)
467             {
468                 return SendDirectMessage(postStr);
469             }
470
471             HttpStatusCode res;
472             var content = "";
473             try
474             {
475                 res = twCon.UpdateStatus(postStr, reply_to, ref content);
476             }
477             catch(Exception ex)
478             {
479                 return "Err:" + ex.Message;
480             }
481
482             // 投稿に成功していても404が返ることがあるらしい: https://dev.twitter.com/discussions/1213
483             if (res == HttpStatusCode.NotFound) return "";
484
485             var err = this.CheckStatusCode(res, content);
486             if (err != null) return err;
487
488             TwitterStatus status;
489             try
490             {
491                 status = TwitterStatus.ParseJson(content);
492             }
493             catch(SerializationException ex)
494             {
495                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
496                 return "Err:Json Parse Error(DataContractJsonSerializer)";
497             }
498             catch(Exception ex)
499             {
500                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
501                 return "Err:Invalid Json!";
502             }
503             _followersCount = status.User.FollowersCount;
504             _friendsCount = status.User.FriendsCount;
505             _statusesCount = status.User.StatusesCount;
506             _location = status.User.Location;
507             _bio = status.User.Description;
508
509             if (IsPostRestricted(status))
510             {
511                 return "OK:Delaying?";
512             }
513             return "";
514         }
515
516         public string PostStatusWithMedia(string postStr, long? reply_to, FileInfo mediaFile)
517         {
518             if (MyCommon._endingFlag) return "";
519
520             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
521
522             HttpStatusCode res;
523             var content = "";
524             try
525             {
526                 res = twCon.UpdateStatusWithMedia(postStr, reply_to, mediaFile, ref content);
527             }
528             catch(Exception ex)
529             {
530                 return "Err:" + ex.Message;
531             }
532
533             // 投稿に成功していても404が返ることがあるらしい: https://dev.twitter.com/discussions/1213
534             if (res == HttpStatusCode.NotFound) return "";
535
536             var err = this.CheckStatusCode(res, content);
537             if (err != null) return err;
538
539             TwitterStatus status;
540             try
541             {
542                 status = TwitterStatus.ParseJson(content);
543             }
544             catch(SerializationException ex)
545             {
546                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
547                 return "Err:Json Parse Error(DataContractJsonSerializer)";
548             }
549             catch(Exception ex)
550             {
551                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
552                 return "Err:Invalid Json!";
553             }
554             _followersCount = status.User.FollowersCount;
555             _friendsCount = status.User.FriendsCount;
556             _statusesCount = status.User.StatusesCount;
557             _location = status.User.Location;
558             _bio = status.User.Description;
559
560             if (IsPostRestricted(status))
561             {
562                 return "OK:Delaying?";
563             }
564             return "";
565         }
566
567         public string SendDirectMessage(string postStr)
568         {
569             if (MyCommon._endingFlag) return "";
570
571             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
572
573             if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
574             {
575                 return "Auth Err:try to re-authorization.";
576             }
577
578             var mc = Regex.Match(postStr, "^DM? +(?<id>[a-zA-Z0-9_]+) +(?<body>.+)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
579
580             HttpStatusCode res;
581             var content = "";
582             try
583             {
584                 res = twCon.SendDirectMessage(mc.Groups["body"].Value, mc.Groups["id"].Value, ref content);
585             }
586             catch(Exception ex)
587             {
588                 return "Err:" + ex.Message;
589             }
590
591             var err = this.CheckStatusCode(res, content);
592             if (err != null) return err;
593
594             TwitterDirectMessage status;
595             try
596             {
597                 status = TwitterDirectMessage.ParseJson(content);
598             }
599             catch(SerializationException ex)
600             {
601                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
602                 return "Err:Json Parse Error(DataContractJsonSerializer)";
603             }
604             catch(Exception ex)
605             {
606                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
607                 return "Err:Invalid Json!";
608             }
609             _followersCount = status.Sender.FollowersCount;
610             _friendsCount = status.Sender.FriendsCount;
611             _statusesCount = status.Sender.StatusesCount;
612             _location = status.Sender.Location;
613             _bio = status.Sender.Description;
614
615             return "";
616         }
617
618         public string RemoveStatus(long id)
619         {
620             if (MyCommon._endingFlag) return "";
621
622             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
623
624             HttpStatusCode res;
625             try
626             {
627                 res = twCon.DestroyStatus(id);
628             }
629             catch(Exception ex)
630             {
631                 return "Err:" + ex.Message;
632             }
633
634             return this.CheckStatusCode(res, null) ?? "";
635         }
636
637         public string PostRetweet(long id, bool read)
638         {
639             if (MyCommon._endingFlag) return "";
640             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
641
642             //データ部分の生成
643             var target = id;
644             var post = TabInformations.GetInstance()[id];
645             if (post == null)
646             {
647                 return "Err:Target isn't found.";
648             }
649             if (TabInformations.GetInstance()[id].RetweetedId != null)
650             {
651                 target = TabInformations.GetInstance()[id].RetweetedId.Value; //再RTの場合は元発言をRT
652             }
653
654             HttpStatusCode res;
655             var content = "";
656             try
657             {
658                 res = twCon.RetweetStatus(target, ref content);
659             }
660             catch(Exception ex)
661             {
662                 return "Err:" + ex.Message;
663             }
664
665             var err = this.CheckStatusCode(res, content);
666             if (err != null) return err;
667
668             TwitterStatus status;
669             try
670             {
671                 status = TwitterStatus.ParseJson(content);
672             }
673             catch(SerializationException ex)
674             {
675                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
676                 return "Err:Json Parse Error(DataContractJsonSerializer)";
677             }
678             catch(Exception ex)
679             {
680                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
681                 return "Err:Invalid Json!";
682             }
683
684             //ReTweetしたものをTLに追加
685             post = CreatePostsFromStatusData(status);
686             if (post == null) return "Invalid Json!";
687
688             //二重取得回避
689             lock (LockObj)
690             {
691                 if (TabInformations.GetInstance().ContainsKey(post.StatusId)) return "";
692             }
693             //Retweet判定
694             if (post.RetweetedId == null) return "Invalid Json!";
695             //ユーザー情報
696             post.IsMe = true;
697
698             post.IsRead = read;
699             post.IsOwl = false;
700             if (_readOwnPost) post.IsRead = true;
701             post.IsDm = false;
702
703             TabInformations.GetInstance().AddPost(post);
704
705             return "";
706         }
707
708         public string RemoveDirectMessage(long id, PostClass post)
709         {
710             if (MyCommon._endingFlag) return "";
711
712             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
713
714             if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
715             {
716                 return "Auth Err:try to re-authorization.";
717             }
718
719             //if (post.IsMe)
720             //    _deletemessages.Add(post)
721             //}
722
723             HttpStatusCode res;
724             try
725             {
726                 res = twCon.DestroyDirectMessage(id);
727             }
728             catch(Exception ex)
729             {
730                 return "Err:" + ex.Message;
731             }
732
733             return this.CheckStatusCode(res, null) ?? "";
734         }
735
736         public string PostFollowCommand(string screenName)
737         {
738             if (MyCommon._endingFlag) return "";
739
740             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
741
742             HttpStatusCode res;
743             var content = "";
744             try
745             {
746                 res = twCon.CreateFriendships(screenName, ref content);
747             }
748             catch(Exception ex)
749             {
750                 return "Err:" + ex.Message;
751             }
752
753             return this.CheckStatusCode(res, content) ?? "";
754         }
755
756         public string PostRemoveCommand(string screenName)
757         {
758             if (MyCommon._endingFlag) return "";
759
760             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
761
762             HttpStatusCode res;
763             var content = "";
764             try
765             {
766                 res = twCon.DestroyFriendships(screenName, ref content);
767             }
768             catch(Exception ex)
769             {
770                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
771             }
772
773             return this.CheckStatusCode(res, content) ?? "";
774         }
775
776         public string PostCreateBlock(string screenName)
777         {
778             if (MyCommon._endingFlag) return "";
779
780             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
781
782             HttpStatusCode res;
783             var content = "";
784             try
785             {
786                 res = twCon.CreateBlock(screenName, ref content);
787             }
788             catch(Exception ex)
789             {
790                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
791             }
792
793             return this.CheckStatusCode(res, content) ?? "";
794         }
795
796         public string PostDestroyBlock(string screenName)
797         {
798             if (MyCommon._endingFlag) return "";
799
800             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
801
802             HttpStatusCode res;
803             var content = "";
804             try
805             {
806                 res = twCon.DestroyBlock(screenName, ref content);
807             }
808             catch(Exception ex)
809             {
810                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
811             }
812
813             return this.CheckStatusCode(res, content) ?? "";
814         }
815
816         public string PostReportSpam(string screenName)
817         {
818             if (MyCommon._endingFlag) return "";
819
820             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
821
822             HttpStatusCode res;
823             var content = "";
824             try
825             {
826                 res = twCon.ReportSpam(screenName, ref content);
827             }
828             catch(Exception ex)
829             {
830                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
831             }
832
833             return this.CheckStatusCode(res, content) ?? "";
834         }
835
836         public string GetFriendshipInfo(string screenName, ref bool isFollowing, ref bool isFollowed)
837         {
838             if (MyCommon._endingFlag) return "";
839
840             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
841
842             HttpStatusCode res;
843             var content = "";
844             try
845             {
846                 res = twCon.ShowFriendships(_uname, screenName, ref content);
847             }
848             catch(Exception ex)
849             {
850                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
851             }
852
853             var err = this.CheckStatusCode(res, content);
854             if (err != null) return err;
855
856             try
857             {
858                 var friendship = TwitterFriendship.ParseJson(content);
859                 isFollowing = friendship.Relationship.Source.Following;
860                 isFollowed = friendship.Relationship.Source.FollowedBy;
861                 return "";
862             }
863             catch(SerializationException ex)
864             {
865                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
866                 return "Err:Json Parse Error(DataContractJsonSerializer)";
867             }
868             catch(Exception ex)
869             {
870                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
871                 return "Err:Invalid Json!";
872             }
873         }
874
875         public string GetUserInfo(string screenName, ref TwitterUser user)
876         {
877             if (MyCommon._endingFlag) return "";
878
879             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
880
881             user = null;
882
883             HttpStatusCode res;
884             var content = "";
885             try
886             {
887                 res = twCon.ShowUserInfo(screenName, ref content);
888             }
889             catch(Exception ex)
890             {
891                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
892             }
893
894             var err = this.CheckStatusCode(res, content);
895             if (err != null) return err;
896
897             try
898             {
899                 user = TwitterUser.ParseJson(content);
900             }
901             catch (SerializationException ex)
902             {
903                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
904                 return "Err:Json Parse Error(DataContractJsonSerializer)";
905             }
906             catch (Exception ex)
907             {
908                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
909                 return "Err:Invalid Json!";
910             }
911             return "";
912         }
913
914         public string GetStatus_Retweeted_Count(long StatusId, ref int retweeted_count)
915         {
916             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
917
918             if (MyCommon._endingFlag) return "";
919
920             HttpStatusCode res;
921             var content = "";
922             try
923             {
924                 res = twCon.ShowStatuses(StatusId, ref content);
925             }
926             catch (Exception ex)
927             {
928                 return "Err:" + ex.Message;
929             }
930
931             var err = this.CheckStatusCode(res, content);
932             if (err != null) return err;
933
934             TwitterStatus status;
935             try
936             {
937                 status = TwitterStatus.ParseJson(content);
938             }
939             catch (SerializationException ex)
940             {
941                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
942                 return "Json Parse Error(DataContractJsonSerializer)";
943             }
944             catch (Exception ex)
945             {
946                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
947                 return "Invalid Json!";
948             }
949             retweeted_count = status.RetweetCount;
950             return "";
951         }
952
953         public string PostFavAdd(long id)
954         {
955             if (MyCommon._endingFlag) return "";
956
957             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
958
959             //if (this.favQueue == null) this.favQueue = new FavoriteQueue(this)
960
961             //if (this.favQueue.Contains(id)) this.favQueue.Remove(id)
962
963             HttpStatusCode res;
964             var content = "";
965             try
966             {
967                 res = twCon.CreateFavorites(id, ref content);
968             }
969             catch(Exception ex)
970             {
971                 //this.favQueue.Add(id)
972                 //return "Err:->FavoriteQueue:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
973                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
974             }
975
976             var err = this.CheckStatusCode(res, content);
977             if (err != null) return err;
978
979             if (!_restrictFavCheck) return "";
980
981             //http://twitter.com/statuses/show/id.xml APIを発行して本文を取得
982
983             try
984             {
985                 res = twCon.ShowStatuses(id, ref content);
986             }
987             catch(Exception ex)
988             {
989                 return "Err:" + ex.Message;
990             }
991
992             err = this.CheckStatusCode(res, content);
993             if (err != null) return err;
994
995             TwitterStatus status;
996             try
997             {
998                 status = TwitterStatus.ParseJson(content);
999             }
1000             catch (SerializationException ex)
1001             {
1002                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1003                 return "Err:Json Parse Error(DataContractJsonSerializer)";
1004             }
1005             catch (Exception ex)
1006             {
1007                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1008                 return "Err:Invalid Json!";
1009             }
1010             if (status.Favorited == true)
1011             {
1012                 return "";
1013             }
1014             else
1015             {
1016                 return "NG(Restricted?)";
1017             }
1018         }
1019
1020         public string PostFavRemove(long id)
1021         {
1022             if (MyCommon._endingFlag) return "";
1023
1024             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1025
1026             //if (this.favQueue == null) this.favQueue = new FavoriteQueue(this)
1027
1028             //if (this.favQueue.Contains(id))
1029             //    this.favQueue.Remove(id)
1030             //    return "";
1031             //}
1032
1033             HttpStatusCode res;
1034             var content = "";
1035             try
1036             {
1037                 res = twCon.DestroyFavorites(id, ref content);
1038             }
1039             catch(Exception ex)
1040             {
1041                 return "Err:" + ex.Message;
1042             }
1043
1044             return this.CheckStatusCode(res, content) ?? "";
1045         }
1046
1047         public TwitterUser PostUpdateProfile(string name, string url, string location, string description)
1048         {
1049             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
1050                 throw new WebApiException("AccountState invalid");
1051
1052             HttpStatusCode res;
1053             var content = "";
1054             try
1055             {
1056                 res = twCon.UpdateProfile(name, url, location, description, ref content);
1057             }
1058             catch(Exception ex)
1059             {
1060                 throw new WebApiException("Err:" + ex.Message, content, ex);
1061             }
1062
1063             var err = this.CheckStatusCode(res, content);
1064             if (err != null)
1065                 throw new WebApiException(err, content);
1066
1067             try
1068             {
1069                 return TwitterUser.ParseJson(content);
1070             }
1071             catch (SerializationException e)
1072             {
1073                 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
1074                 MyCommon.TraceOut(ex);
1075                 throw ex;
1076             }
1077             catch (Exception e)
1078             {
1079                 var ex = new WebApiException("Err:Invalid Json!", content, e);
1080                 MyCommon.TraceOut(ex);
1081                 throw ex;
1082             }
1083         }
1084
1085         public string PostUpdateProfileImage(string filename)
1086         {
1087             if (MyCommon._endingFlag) return "";
1088
1089             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1090
1091             HttpStatusCode res;
1092             var content = "";
1093             try
1094             {
1095                 res = twCon.UpdateProfileImage(new FileInfo(filename), ref content);
1096             }
1097             catch(Exception ex)
1098             {
1099                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
1100             }
1101
1102             return this.CheckStatusCode(res, content) ?? "";
1103         }
1104
1105         public string Username
1106         {
1107             get
1108             {
1109                 return twCon.AuthenticatedUsername;
1110             }
1111         }
1112
1113         public long UserId
1114         {
1115             get
1116             {
1117                 return twCon.AuthenticatedUserId;
1118             }
1119         }
1120
1121         public string Password
1122         {
1123             get
1124             {
1125                 return twCon.Password;
1126             }
1127         }
1128
1129         private static MyCommon.ACCOUNT_STATE _accountState = MyCommon.ACCOUNT_STATE.Valid;
1130         public static MyCommon.ACCOUNT_STATE AccountState
1131         {
1132             get
1133             {
1134                 return _accountState;
1135             }
1136             set
1137             {
1138                 _accountState = value;
1139             }
1140         }
1141
1142         public bool TinyUrlResolve
1143         {
1144             set
1145             {
1146                 _tinyUrlResolve = value;
1147             }
1148         }
1149
1150         public bool RestrictFavCheck
1151         {
1152             set
1153             {
1154                 _restrictFavCheck = value;
1155             }
1156         }
1157
1158 #region "バージョンアップ"
1159         public string GetTweenBinary(string strVer)
1160         {
1161             try
1162             {
1163                 //本体
1164                 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/Tween" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1165                                                     Path.Combine(MyCommon.settingPath, "TweenNew.exe")))
1166                 {
1167                     return "Err:Download failed";
1168                 }
1169                 //英語リソース
1170                 if (!Directory.Exists(Path.Combine(MyCommon.settingPath, "en")))
1171                 {
1172                     Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, "en"));
1173                 }
1174                 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenResEn" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1175                                                     Path.Combine(Path.Combine(MyCommon.settingPath, "en"), "Tween.resourcesNew.dll")))
1176                 {
1177                     return "Err:Download failed";
1178                 }
1179                 //その他言語圏のリソース。取得失敗しても継続
1180                 //UIの言語圏のリソース
1181                 var curCul = "";
1182                 if (!Thread.CurrentThread.CurrentUICulture.IsNeutralCulture)
1183                 {
1184                     var idx = Thread.CurrentThread.CurrentUICulture.Name.LastIndexOf('-');
1185                     if (idx > -1)
1186                     {
1187                         curCul = Thread.CurrentThread.CurrentUICulture.Name.Substring(0, idx);
1188                     }
1189                     else
1190                     {
1191                         curCul = Thread.CurrentThread.CurrentUICulture.Name;
1192                     }
1193                 }
1194                 else
1195                 {
1196                     curCul = Thread.CurrentThread.CurrentUICulture.Name;
1197                 }
1198                 if (!string.IsNullOrEmpty(curCul) && curCul != "en" && curCul != "ja")
1199                 {
1200                     if (!Directory.Exists(Path.Combine(MyCommon.settingPath, curCul)))
1201                     {
1202                         Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, curCul));
1203                     }
1204                     if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenRes" + curCul + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1205                                                         Path.Combine(Path.Combine(MyCommon.settingPath, curCul), "Tween.resourcesNew.dll")))
1206                     {
1207                         //return "Err:Download failed";
1208                     }
1209                 }
1210                 //スレッドの言語圏のリソース
1211                 string curCul2;
1212                 if (!Thread.CurrentThread.CurrentCulture.IsNeutralCulture)
1213                 {
1214                     var idx = Thread.CurrentThread.CurrentCulture.Name.LastIndexOf('-');
1215                     if (idx > -1)
1216                     {
1217                         curCul2 = Thread.CurrentThread.CurrentCulture.Name.Substring(0, idx);
1218                     }
1219                     else
1220                     {
1221                         curCul2 = Thread.CurrentThread.CurrentCulture.Name;
1222                     }
1223                 }
1224                 else
1225                 {
1226                     curCul2 = Thread.CurrentThread.CurrentCulture.Name;
1227                 }
1228                 if (!string.IsNullOrEmpty(curCul2) && curCul2 != "en" && curCul2 != curCul)
1229                 {
1230                     if (!Directory.Exists(Path.Combine(MyCommon.settingPath, curCul2)))
1231                     {
1232                         Directory.CreateDirectory(Path.Combine(MyCommon.settingPath, curCul2));
1233                     }
1234                     if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenRes" + curCul2 + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1235                                                     Path.Combine(Path.Combine(MyCommon.settingPath, curCul2), "Tween.resourcesNew.dll")))
1236                     {
1237                         //return "Err:Download failed";
1238                     }
1239                 }
1240
1241                 //アップデータ
1242                 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenUp3.gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1243                                                     Path.Combine(MyCommon.settingPath, "TweenUp3.exe")))
1244                 {
1245                     return "Err:Download failed";
1246                 }
1247                 //シリアライザDLL
1248                 if (!(new HttpVarious()).GetDataToFile("http://tween.sourceforge.jp/TweenDll" + strVer + ".gz?" + DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount.ToString(),
1249                                                     Path.Combine(MyCommon.settingPath, "TweenNew.XmlSerializers.dll")))
1250                 {
1251                     return "Err:Download failed";
1252                 }
1253                 return "";
1254             }
1255             catch(Exception)
1256             {
1257                 return "Err:Download failed";
1258             }
1259         }
1260 #endregion
1261
1262         public bool ReadOwnPost
1263         {
1264             get
1265             {
1266                 return _readOwnPost;
1267             }
1268             set
1269             {
1270                 _readOwnPost = value;
1271             }
1272         }
1273
1274         public int FollowersCount
1275         {
1276             get
1277             {
1278                 return _followersCount;
1279             }
1280         }
1281
1282         public int FriendsCount
1283         {
1284             get
1285             {
1286                 return _friendsCount;
1287             }
1288         }
1289
1290         public int StatusesCount
1291         {
1292             get
1293             {
1294                 return _statusesCount;
1295             }
1296         }
1297
1298         public string Location
1299         {
1300             get
1301             {
1302                 return _location;
1303             }
1304         }
1305
1306         public string Bio
1307         {
1308             get
1309             {
1310                 return _bio;
1311             }
1312         }
1313
1314         public string GetTimelineApi(bool read,
1315                                 MyCommon.WORKERTYPE gType,
1316                                 bool more,
1317                                 bool startup)
1318         {
1319             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1320
1321             if (MyCommon._endingFlag) return "";
1322
1323             HttpStatusCode res;
1324             var content = "";
1325             var count = AppendSettingDialog.Instance.CountApi;
1326             if (gType == MyCommon.WORKERTYPE.Reply) count = AppendSettingDialog.Instance.CountApiReply;
1327             if (AppendSettingDialog.Instance.UseAdditionalCount)
1328             {
1329                 if (more && AppendSettingDialog.Instance.MoreCountApi != 0)
1330                 {
1331                     count = AppendSettingDialog.Instance.MoreCountApi;
1332                 }
1333                 else if (startup && AppendSettingDialog.Instance.FirstCountApi != 0 && gType == MyCommon.WORKERTYPE.Timeline)
1334                 {
1335                     count = AppendSettingDialog.Instance.FirstCountApi;
1336                 }
1337             }
1338             try
1339             {
1340                 if (gType == MyCommon.WORKERTYPE.Timeline)
1341                 {
1342                     if (more)
1343                     {
1344                         res = twCon.HomeTimeline(count, this.minHomeTimeline, null, ref content);
1345                     }
1346                     else
1347                     {
1348                         res = twCon.HomeTimeline(count, null, null, ref content);
1349                     }
1350                 }
1351                 else
1352                 {
1353                     if (more)
1354                     {
1355                         res = twCon.Mentions(count, this.minMentions, null, ref content);
1356                     }
1357                     else
1358                     {
1359                         res = twCon.Mentions(count, null, null, ref content);
1360                     }
1361                 }
1362             }
1363             catch(Exception ex)
1364             {
1365                 return "Err:" + ex.Message;
1366             }
1367
1368             var err = this.CheckStatusCode(res, content);
1369             if (err != null) return err;
1370
1371             if (gType == MyCommon.WORKERTYPE.Timeline)
1372             {
1373                 return CreatePostsFromJson(content, gType, null, read, count, ref this.minHomeTimeline);
1374             }
1375             else
1376             {
1377                 return CreatePostsFromJson(content, gType, null, read, count, ref this.minMentions);
1378             }
1379         }
1380
1381         public string GetUserTimelineApi(bool read,
1382                                          int count,
1383                                          string userName,
1384                                          TabClass tab,
1385                                          bool more)
1386         {
1387             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1388
1389             if (MyCommon._endingFlag) return "";
1390
1391             HttpStatusCode res;
1392             var content = "";
1393
1394             if (count == 0) count = 20;
1395             try
1396             {
1397                 if (string.IsNullOrEmpty(userName))
1398                 {
1399                     var target = tab.User;
1400                     if (string.IsNullOrEmpty(target)) return "";
1401                     userName = target;
1402                     res = twCon.UserTimeline(null, target, count, null, null, ref content);
1403                 }
1404                 else
1405                 {
1406                     if (more)
1407                     {
1408                         res = twCon.UserTimeline(null, userName, count, tab.OldestId, null, ref content);
1409                     }
1410                     else
1411                     {
1412                         res = twCon.UserTimeline(null, userName, count, null, null, ref content);
1413                     }
1414                 }
1415             }
1416             catch(Exception ex)
1417             {
1418                 return "Err:" + ex.Message;
1419             }
1420
1421             if (res == HttpStatusCode.Unauthorized)
1422                 return "Err:@" + userName + "'s Tweets are protected.";
1423
1424             var err = this.CheckStatusCode(res, content);
1425             if (err != null) return err;
1426
1427             TwitterStatus[] items;
1428             try
1429             {
1430                 items = TwitterStatus.ParseJsonArray(content);
1431             }
1432             catch(SerializationException ex)
1433             {
1434                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1435                 return "Json Parse Error(DataContractJsonSerializer)";
1436             }
1437             catch(Exception ex)
1438             {
1439                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1440                 return "Invalid Json!";
1441             }
1442
1443             foreach (var status in items)
1444             {
1445                 var item = CreatePostsFromStatusData(status);
1446                 if (item == null) continue;
1447                 if (item.StatusId < tab.OldestId) tab.OldestId = item.StatusId;
1448                 item.IsRead = read;
1449                 if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
1450                 if (tab != null) item.RelTabName = tab.TabName;
1451                 //非同期アイコン取得&StatusDictionaryに追加
1452                 TabInformations.GetInstance().AddPost(item);
1453             }
1454
1455             return "";
1456         }
1457
1458         public string GetStatusApi(bool read,
1459                                    Int64 id,
1460                                    ref PostClass post)
1461         {
1462             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
1463
1464             if (MyCommon._endingFlag) return "";
1465
1466             HttpStatusCode res;
1467             var content = "";
1468             try
1469             {
1470                 res = twCon.ShowStatuses(id, ref content);
1471             }
1472             catch(Exception ex)
1473             {
1474                 return "Err:" + ex.Message;
1475             }
1476
1477             if (res == HttpStatusCode.Forbidden)
1478                 return "Err:protected user's tweet";
1479
1480             var err = this.CheckStatusCode(res, content);
1481             if (err != null) return err;
1482
1483             TwitterStatus status;
1484             try
1485             {
1486                 status = TwitterStatus.ParseJson(content);
1487             }
1488             catch(SerializationException ex)
1489             {
1490                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1491                 return "Json Parse Error(DataContractJsonSerializer)";
1492             }
1493             catch(Exception ex)
1494             {
1495                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1496                 return "Invalid Json!";
1497             }
1498
1499             var item = CreatePostsFromStatusData(status);
1500             if (item == null) return "Err:Can't create post";
1501             item.IsRead = read;
1502             if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
1503
1504             post = item;
1505             return "";
1506         }
1507
1508         public string GetStatusApi(bool read,
1509                                    Int64 id,
1510                                    TabClass tab)
1511         {
1512             PostClass post = null;
1513             var r = this.GetStatusApi(read, id, ref post);
1514
1515             if (string.IsNullOrEmpty(r))
1516             {
1517                 if (tab != null) post.RelTabName = tab.TabName;
1518                 //非同期アイコン取得&StatusDictionaryに追加
1519                 TabInformations.GetInstance().AddPost(post);
1520             }
1521
1522             return r;
1523         }
1524
1525         private PostClass CreatePostsFromStatusData(TwitterStatus status)
1526         {
1527             var post = new PostClass();
1528             TwitterEntities entities;
1529
1530             post.StatusId = status.Id;
1531             if (status.RetweetedStatus != null)
1532             {
1533                 var retweeted = status.RetweetedStatus;
1534
1535                 post.CreatedAt = MyCommon.DateTimeParse(retweeted.CreatedAt);
1536
1537                 //Id
1538                 post.RetweetedId = retweeted.Id;
1539                 //本文
1540                 post.TextFromApi = retweeted.Text;
1541                 entities = retweeted.MergedEntities;
1542                 //Source取得(htmlの場合は、中身を取り出し)
1543                 post.Source = retweeted.Source;
1544                 //Reply先
1545                 post.InReplyToStatusId = retweeted.InReplyToStatusId;
1546                 post.InReplyToUser = retweeted.InReplyToScreenName;
1547                 post.InReplyToUserId = status.InReplyToUserId;
1548
1549                 //幻覚fav対策
1550                 var tc = TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites);
1551                 post.IsFav = tc.Contains(retweeted.Id);
1552
1553                 if (retweeted.Coordinates != null) post.PostGeo = new PostClass.StatusGeo { Lng = retweeted.Coordinates.Coordinates[0], Lat = retweeted.Coordinates.Coordinates[1] };
1554
1555                 //以下、ユーザー情報
1556                 var user = retweeted.User;
1557
1558                 if (user == null || user.ScreenName == null || status.User.ScreenName == null) return null;
1559
1560                 post.UserId = user.Id;
1561                 post.ScreenName = user.ScreenName;
1562                 post.Nickname = user.Name.Trim();
1563                 post.ImageUrl = user.ProfileImageUrlHttps;
1564                 post.IsProtect = user.Protected;
1565
1566                 //Retweetした人
1567                 post.RetweetedBy = status.User.ScreenName;
1568                 post.RetweetedByUserId = status.User.Id;
1569                 post.IsMe = post.RetweetedBy.ToLower().Equals(_uname);
1570             }
1571             else
1572             {
1573                 post.CreatedAt = MyCommon.DateTimeParse(status.CreatedAt);
1574                 //本文
1575                 post.TextFromApi = status.Text;
1576                 entities = status.MergedEntities;
1577                 //Source取得(htmlの場合は、中身を取り出し)
1578                 post.Source = status.Source;
1579                 post.InReplyToStatusId = status.InReplyToStatusId;
1580                 post.InReplyToUser = status.InReplyToScreenName;
1581                 post.InReplyToUserId = status.InReplyToUserId;
1582
1583                 if (status.Coordinates != null) post.PostGeo = new PostClass.StatusGeo { Lng = status.Coordinates.Coordinates[0], Lat = status.Coordinates.Coordinates[1] };
1584
1585                 //以下、ユーザー情報
1586                 var user = status.User;
1587
1588                 if (user == null || user.ScreenName == null) return null;
1589
1590                 post.UserId = user.Id;
1591                 post.ScreenName = user.ScreenName;
1592                 post.Nickname = user.Name.Trim();
1593                 post.ImageUrl = user.ProfileImageUrlHttps;
1594                 post.IsProtect = user.Protected;
1595                 post.IsMe = post.ScreenName.ToLower().Equals(_uname);
1596
1597                 //幻覚fav対策
1598                 var tc = TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites);
1599                 post.IsFav = tc.Contains(post.StatusId) && TabInformations.GetInstance()[post.StatusId].IsFav;
1600             }
1601             //HTMLに整形
1602             string textFromApi = post.TextFromApi;
1603             post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, entities, post.Media);
1604             post.TextFromApi = textFromApi;
1605             post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities);
1606             post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
1607             post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
1608
1609             //Source整形
1610             CreateSource(post);
1611
1612             post.IsReply = post.ReplyToList.Contains(_uname);
1613             post.IsExcludeReply = false;
1614
1615             if (post.IsMe)
1616             {
1617                 post.IsOwl = false;
1618             }
1619             else
1620             {
1621                 if (followerId.Count > 0) post.IsOwl = !followerId.Contains(post.UserId);
1622             }
1623
1624             post.IsDm = false;
1625             return post;
1626         }
1627
1628         private string CreatePostsFromJson(string content, MyCommon.WORKERTYPE gType, TabClass tab, bool read, int count, ref long minimumId)
1629         {
1630             TwitterStatus[] items;
1631             try
1632             {
1633                 items = TwitterStatus.ParseJsonArray(content);
1634             }
1635             catch(SerializationException ex)
1636             {
1637                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1638                 return "Json Parse Error(DataContractJsonSerializer)";;
1639             }
1640             catch(Exception ex)
1641             {
1642                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1643                 return "Invalid Json!";
1644             }
1645
1646             foreach (var status in items)
1647             {
1648                 PostClass post = null;
1649                 post = CreatePostsFromStatusData(status);
1650                 if (post == null) continue;
1651
1652                 if (minimumId > post.StatusId) minimumId = post.StatusId;
1653                 //二重取得回避
1654                 lock (LockObj)
1655                 {
1656                     if (tab == null)
1657                     {
1658                         if (TabInformations.GetInstance().ContainsKey(post.StatusId)) continue;
1659                     }
1660                     else
1661                     {
1662                         if (TabInformations.GetInstance().ContainsKey(post.StatusId, tab.TabName)) continue;
1663                     }
1664                 }
1665
1666                 //RT禁止ユーザーによるもの
1667                 if (post.RetweetedByUserId != null && this.noRTId.Contains(post.RetweetedByUserId.Value)) continue;
1668
1669                 post.IsRead = read;
1670                 if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
1671
1672                 if (tab != null) post.RelTabName = tab.TabName;
1673                 //非同期アイコン取得&StatusDictionaryに追加
1674                 TabInformations.GetInstance().AddPost(post);
1675             }
1676
1677             return "";
1678         }
1679
1680         private string CreatePostsFromSearchJson(string content, TabClass tab, bool read, int count, ref long minimumId, bool more)
1681         {
1682             TwitterSearchResult items;
1683             try
1684             {
1685                 items = TwitterSearchResult.ParseJson(content);
1686             }
1687             catch (SerializationException ex)
1688             {
1689                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1690                 return "Json Parse Error(DataContractJsonSerializer)";
1691             }
1692             catch (Exception ex)
1693             {
1694                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1695                 return "Invalid Json!";
1696             }
1697             foreach (var result in items.Statuses)
1698             {
1699                 PostClass post = null;
1700                 post = CreatePostsFromStatusData(result);
1701
1702                 if (post == null)
1703                 {
1704                     // Search API は相変わらずぶっ壊れたデータを返すことがあるため、必要なデータが欠如しているものは取得し直す
1705                     var ret = this.GetStatusApi(read, result.Id, ref post);
1706                     if (!string.IsNullOrEmpty(ret)) continue;
1707                 }
1708
1709                 if (minimumId > post.StatusId) minimumId = post.StatusId;
1710                 if (!more && post.StatusId > tab.SinceId) tab.SinceId = post.StatusId;
1711                 //二重取得回避
1712                 lock (LockObj)
1713                 {
1714                     if (tab == null)
1715                     {
1716                         if (TabInformations.GetInstance().ContainsKey(post.StatusId)) continue;
1717                     }
1718                     else
1719                     {
1720                         if (TabInformations.GetInstance().ContainsKey(post.StatusId, tab.TabName)) continue;
1721                     }
1722                 }
1723
1724                 post.IsRead = read;
1725                 if ((post.IsMe && !read) && this._readOwnPost) post.IsRead = true;
1726
1727                 if (tab != null) post.RelTabName = tab.TabName;
1728                 //非同期アイコン取得&StatusDictionaryに追加
1729                 TabInformations.GetInstance().AddPost(post);
1730             }
1731
1732             return "";
1733         }
1734
1735         public string GetListStatus(bool read,
1736                                 TabClass tab,
1737                                 bool more,
1738                                 bool startup)
1739         {
1740             if (MyCommon._endingFlag) return "";
1741
1742             HttpStatusCode res;
1743             var content = "";
1744             int count;
1745             if (AppendSettingDialog.Instance.UseAdditionalCount)
1746             {
1747                 count = AppendSettingDialog.Instance.ListCountApi;
1748                 if (count == 0)
1749                 {
1750                     if (more && AppendSettingDialog.Instance.MoreCountApi != 0)
1751                     {
1752                         count = AppendSettingDialog.Instance.MoreCountApi;
1753                     }
1754                     else if (startup && AppendSettingDialog.Instance.FirstCountApi != 0)
1755                     {
1756                         count = AppendSettingDialog.Instance.FirstCountApi;
1757                     }
1758                     else
1759                     {
1760                         count = AppendSettingDialog.Instance.CountApi;
1761                     }
1762                 }
1763             }
1764             else
1765             {
1766                 count = AppendSettingDialog.Instance.CountApi;
1767             }
1768             try
1769             {
1770                 if (more)
1771                 {
1772                     res = twCon.GetListsStatuses(tab.ListInfo.UserId, tab.ListInfo.Id, count, tab.OldestId, null, AppendSettingDialog.Instance.IsListStatusesIncludeRts, ref content);
1773                 }
1774                 else
1775                 {
1776                     res = twCon.GetListsStatuses(tab.ListInfo.UserId, tab.ListInfo.Id, count, null, null, AppendSettingDialog.Instance.IsListStatusesIncludeRts, ref content);
1777                 }
1778             }
1779             catch(Exception ex)
1780             {
1781                 return "Err:" + ex.Message;
1782             }
1783
1784             var err = this.CheckStatusCode(res, content);
1785             if (err != null) return err;
1786
1787             return CreatePostsFromJson(content, MyCommon.WORKERTYPE.List, tab, read, count, ref tab.OldestId);
1788         }
1789
1790         /// <summary>
1791         /// startStatusId からリプライ先の発言を辿る。発言は posts 以外からは検索しない。
1792         /// </summary>
1793         /// <returns>posts の中から検索されたリプライチェインの末端</returns>
1794         internal static PostClass FindTopOfReplyChain(IDictionary<Int64, PostClass> posts, Int64 startStatusId)
1795         {
1796             if (!posts.ContainsKey(startStatusId))
1797                 throw new ArgumentException("startStatusId (" + startStatusId + ") が posts の中から見つかりませんでした。");
1798
1799             var nextPost = posts[startStatusId];
1800             while (nextPost.InReplyToStatusId != null)
1801             {
1802                 if (!posts.ContainsKey(nextPost.InReplyToStatusId.Value))
1803                     break;
1804                 nextPost = posts[nextPost.InReplyToStatusId.Value];
1805             }
1806
1807             return nextPost;
1808         }
1809
1810         public string GetRelatedResult(bool read, TabClass tab)
1811         {
1812             var rslt = "";
1813             var relPosts = new Dictionary<Int64, PostClass>();
1814             if (tab.RelationTargetPost.TextFromApi.Contains("@") && tab.RelationTargetPost.InReplyToStatusId == null)
1815             {
1816                 //検索結果対応
1817                 var p = TabInformations.GetInstance()[tab.RelationTargetPost.StatusId];
1818                 if (p != null && p.InReplyToStatusId != null)
1819                 {
1820                     tab.RelationTargetPost = p;
1821                 }
1822                 else
1823                 {
1824                     rslt = this.GetStatusApi(read, tab.RelationTargetPost.StatusId, ref p);
1825                     if (!string.IsNullOrEmpty(rslt)) return rslt;
1826                     tab.RelationTargetPost = p;
1827                 }
1828             }
1829             relPosts.Add(tab.RelationTargetPost.StatusId, tab.RelationTargetPost.Clone());
1830
1831             // in_reply_to_status_id を使用してリプライチェインを辿る
1832             var nextPost = FindTopOfReplyChain(relPosts, tab.RelationTargetPost.StatusId);
1833             var loopCount = 1;
1834             while (nextPost.InReplyToStatusId != null && loopCount++ <= 20)
1835             {
1836                 var inReplyToId = nextPost.InReplyToStatusId.Value;
1837
1838                 var inReplyToPost = TabInformations.GetInstance()[inReplyToId];
1839                 if (inReplyToPost != null)
1840                 {
1841                     inReplyToPost = inReplyToPost.Clone();
1842                 }
1843                 else
1844                 {
1845                     var errorText = this.GetStatusApi(read, inReplyToId, ref inReplyToPost);
1846                     if (!string.IsNullOrEmpty(errorText))
1847                     {
1848                         rslt = errorText;
1849                         break;
1850                     }
1851                 }
1852
1853                 relPosts.Add(inReplyToPost.StatusId, inReplyToPost);
1854
1855                 nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId);
1856             }
1857
1858             //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む
1859             var text = tab.RelationTargetPost.Text;
1860             var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>()
1861                 .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>());
1862             foreach (var _match in ma)
1863             {
1864                 Int64 _statusId;
1865                 if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId))
1866                 {
1867                     if (relPosts.ContainsKey(_statusId))
1868                         continue;
1869
1870                     PostClass p = null;
1871                     var _post = TabInformations.GetInstance()[_statusId];
1872                     if (_post == null)
1873                     {
1874                         this.GetStatusApi(read, _statusId, ref p);
1875                     }
1876                     else
1877                     {
1878                         p = _post.Clone();
1879                     }
1880
1881                     if (p != null)
1882                         relPosts.Add(p.StatusId, p);
1883                 }
1884             }
1885
1886             relPosts.Values.ToList().ForEach(p =>
1887             {
1888                 if (p.IsMe && !read && this._readOwnPost)
1889                     p.IsRead = true;
1890                 else
1891                     p.IsRead = read;
1892
1893                 p.RelTabName = tab.TabName;
1894                 TabInformations.GetInstance().AddPost(p);
1895             });
1896
1897             return rslt;
1898         }
1899
1900         public string GetSearch(bool read,
1901                             TabClass tab,
1902                             bool more)
1903         {
1904             if (MyCommon._endingFlag) return "";
1905
1906             HttpStatusCode res;
1907             var content = "";
1908             long? maxId = null;
1909             long? sinceId = null;
1910             var count = 100;
1911             if (AppendSettingDialog.Instance.UseAdditionalCount &&
1912                 AppendSettingDialog.Instance.SearchCountApi != 0)
1913             {
1914                 count = AppendSettingDialog.Instance.SearchCountApi;
1915             }
1916             else
1917             {
1918                 count = AppendSettingDialog.Instance.CountApi;
1919             }
1920             if (more)
1921             {
1922                 maxId = tab.OldestId - 1;
1923             }
1924             else
1925             {
1926                 sinceId = tab.SinceId;
1927             }
1928
1929             try
1930             {
1931                 // TODO:一時的に40>100件に 件数変更UI作成の必要あり
1932                 res = twCon.Search(tab.SearchWords, tab.SearchLang, count, maxId, sinceId, ref content);
1933             }
1934             catch(Exception ex)
1935             {
1936                 return "Err:" + ex.Message;
1937             }
1938             switch (res)
1939             {
1940                 case HttpStatusCode.BadRequest:
1941                     return "Invalid query";
1942                 case HttpStatusCode.NotFound:
1943                     return "Invalid query";
1944                 case HttpStatusCode.PaymentRequired: //API Documentには420と書いてあるが、該当コードがないので402にしてある
1945                     return "Search API Limit?";
1946                 case HttpStatusCode.OK:
1947                     break;
1948                 default:
1949                     return "Err:" + res.ToString() + "(" + MethodBase.GetCurrentMethod().Name + ")";
1950             }
1951
1952             if (!TabInformations.GetInstance().ContainsTab(tab)) return "";
1953
1954             return this.CreatePostsFromSearchJson(content, tab, read, count, ref tab.OldestId, more);
1955         }
1956
1957         private string CreateDirectMessagesFromJson(string content, MyCommon.WORKERTYPE gType, bool read)
1958         {
1959             TwitterDirectMessage[] item;
1960             try
1961             {
1962                 if (gType == MyCommon.WORKERTYPE.UserStream)
1963                 {
1964                     item = new[] { TwitterStreamEventDirectMessage.ParseJson(content).DirectMessage };
1965                 }
1966                 else
1967                 {
1968                     item = TwitterDirectMessage.ParseJsonArray(content);
1969                 }
1970             }
1971             catch(SerializationException ex)
1972             {
1973                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
1974                 return "Json Parse Error(DataContractJsonSerializer)";
1975             }
1976             catch(Exception ex)
1977             {
1978                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
1979                 return "Invalid Json!";
1980             }
1981
1982             foreach (var message in item)
1983             {
1984                 var post = new PostClass();
1985                 try
1986                 {
1987                     post.StatusId = message.Id;
1988                     if (gType != MyCommon.WORKERTYPE.UserStream)
1989                     {
1990                         if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
1991                         {
1992                             if (minDirectmessage > post.StatusId) minDirectmessage = post.StatusId;
1993                         }
1994                         else
1995                         {
1996                             if (minDirectmessageSent > post.StatusId) minDirectmessageSent = post.StatusId;
1997                         }
1998                     }
1999
2000                     //二重取得回避
2001                     lock (LockObj)
2002                     {
2003                         if (TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.DirectMessage).Contains(post.StatusId)) continue;
2004                     }
2005                     //sender_id
2006                     //recipient_id
2007                     post.CreatedAt = MyCommon.DateTimeParse(message.CreatedAt);
2008                     //本文
2009                     var textFromApi = message.Text;
2010                     //HTMLに整形
2011                     post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, message.Entities, post.Media);
2012                     post.TextFromApi = this.ReplaceTextFromApi(textFromApi, message.Entities);
2013                     post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
2014                     post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
2015                     post.IsFav = false;
2016
2017                     //以下、ユーザー情報
2018                     TwitterUser user;
2019                     if (gType == MyCommon.WORKERTYPE.UserStream)
2020                     {
2021                         if (twCon.AuthenticatedUsername.Equals(message.Recipient.ScreenName, StringComparison.CurrentCultureIgnoreCase))
2022                         {
2023                             user = message.Sender;
2024                             post.IsMe = false;
2025                             post.IsOwl = true;
2026                         }
2027                         else
2028                         {
2029                             user = message.Recipient;
2030                             post.IsMe = true;
2031                             post.IsOwl = false;
2032                         }
2033                     }
2034                     else
2035                     {
2036                         if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
2037                         {
2038                             user = message.Sender;
2039                             post.IsMe = false;
2040                             post.IsOwl = true;
2041                         }
2042                         else
2043                         {
2044                             user = message.Recipient;
2045                             post.IsMe = true;
2046                             post.IsOwl = false;
2047                         }
2048                     }
2049
2050                     post.UserId = user.Id;
2051                     post.ScreenName = user.ScreenName;
2052                     post.Nickname = user.Name.Trim();
2053                     post.ImageUrl = user.ProfileImageUrlHttps;
2054                     post.IsProtect = user.Protected;
2055                 }
2056                 catch(Exception ex)
2057                 {
2058                     MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2059                     MessageBox.Show("Parse Error(CreateDirectMessagesFromJson)");
2060                     continue;
2061                 }
2062
2063                 post.IsRead = read;
2064                 if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
2065                 post.IsReply = false;
2066                 post.IsExcludeReply = false;
2067                 post.IsDm = true;
2068
2069                 TabInformations.GetInstance().AddPost(post);
2070             }
2071
2072             return "";
2073
2074         }
2075
2076         public string GetDirectMessageApi(bool read,
2077                                 MyCommon.WORKERTYPE gType,
2078                                 bool more)
2079         {
2080             if (MyCommon._endingFlag) return "";
2081
2082             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2083
2084             if (this.AccessLevel == TwitterApiAccessLevel.Read || this.AccessLevel == TwitterApiAccessLevel.ReadWrite)
2085             {
2086                 return "Auth Err:try to re-authorization.";
2087             }
2088
2089             HttpStatusCode res;
2090             var content = "";
2091
2092             try
2093             {
2094                 if (gType == MyCommon.WORKERTYPE.DirectMessegeRcv)
2095                 {
2096                     if (more)
2097                     {
2098                         res = twCon.DirectMessages(20, minDirectmessage, null, ref content);
2099                     }
2100                     else
2101                     {
2102                         res = twCon.DirectMessages(20, null, null, ref content);
2103                     }
2104                 }
2105                 else
2106                 {
2107                     if (more)
2108                     {
2109                         res = twCon.DirectMessagesSent(20, minDirectmessageSent, null, ref content);
2110                     }
2111                     else
2112                     {
2113                         res = twCon.DirectMessagesSent(20, null, null, ref content);
2114                     }
2115                 }
2116             }
2117             catch(Exception ex)
2118             {
2119                 return "Err:" + ex.Message;
2120             }
2121
2122             var err = this.CheckStatusCode(res, content);
2123             if (err != null) return err;
2124
2125             return CreateDirectMessagesFromJson(content, gType, read);
2126         }
2127
2128         static int page_ = 1;
2129         public string GetFavoritesApi(bool read,
2130                             MyCommon.WORKERTYPE gType,
2131                             bool more)
2132         {
2133             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2134
2135             if (MyCommon._endingFlag) return "";
2136
2137             var count = AppendSettingDialog.Instance.CountApi;
2138             if (AppendSettingDialog.Instance.UseAdditionalCount &&
2139                 AppendSettingDialog.Instance.FavoritesCountApi != 0)
2140             {
2141                 count = AppendSettingDialog.Instance.FavoritesCountApi;
2142             }
2143
2144             // 前ページ取得の場合はページカウンタをインクリメント、それ以外の場合はページカウンタリセット
2145             if (more)
2146             {
2147                 page_++;
2148             }
2149             else
2150             {
2151                 page_ = 1;
2152             }
2153
2154             HttpStatusCode res;
2155             var content = "";
2156             try
2157             {
2158                 res = twCon.Favorites(count, page_, ref content);
2159             }
2160             catch(Exception ex)
2161             {
2162                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2163             }
2164
2165             var err = this.CheckStatusCode(res, content);
2166             if (err != null) return err;
2167
2168             TwitterStatus[] item;
2169             try
2170             {
2171                 item = TwitterStatus.ParseJsonArray(content);
2172             }
2173             catch(SerializationException ex)
2174             {
2175                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2176                 return "Json Parse Error(DataContractJsonSerializer)";
2177             }
2178             catch(Exception ex)
2179             {
2180                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2181                 return "Invalid Json!";
2182             }
2183
2184             foreach (var status in item)
2185             {
2186                 var post = new PostClass();
2187                 TwitterEntities entities;
2188
2189                 try
2190                 {
2191                     post.StatusId = status.Id;
2192                     //二重取得回避
2193                     lock (LockObj)
2194                     {
2195                         if (TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Contains(post.StatusId)) continue;
2196                     }
2197                     //Retweet判定
2198                     if (status.RetweetedStatus != null)
2199                     {
2200                         var retweeted = status.RetweetedStatus;
2201                         post.CreatedAt = MyCommon.DateTimeParse(retweeted.CreatedAt);
2202
2203                         //Id
2204                         post.RetweetedId = post.StatusId;
2205                         //本文
2206                         post.TextFromApi = retweeted.Text;
2207                         entities = retweeted.MergedEntities;
2208                         //Source取得(htmlの場合は、中身を取り出し)
2209                         post.Source = retweeted.Source;
2210                         //Reply先
2211                         post.InReplyToStatusId = retweeted.InReplyToStatusId;
2212                         post.InReplyToUser = retweeted.InReplyToScreenName;
2213                         post.InReplyToUserId = retweeted.InReplyToUserId;
2214                         post.IsFav = true;
2215
2216                         //以下、ユーザー情報
2217                         var user = retweeted.User;
2218                         post.UserId = user.Id;
2219                         post.ScreenName = user.ScreenName;
2220                         post.Nickname = user.Name.Trim();
2221                         post.ImageUrl = user.ProfileImageUrlHttps;
2222                         post.IsProtect = user.Protected;
2223
2224                         //Retweetした人
2225                         post.RetweetedBy = status.User.ScreenName;
2226                         post.IsMe = post.RetweetedBy.ToLower().Equals(_uname);
2227                     }
2228                     else
2229                     {
2230                         post.CreatedAt = MyCommon.DateTimeParse(status.CreatedAt);
2231
2232                         //本文
2233                         post.TextFromApi = status.Text;
2234                         entities = status.MergedEntities;
2235                         //Source取得(htmlの場合は、中身を取り出し)
2236                         post.Source = status.Source;
2237                         post.InReplyToStatusId = status.InReplyToStatusId;
2238                         post.InReplyToUser = status.InReplyToScreenName;
2239                         post.InReplyToUserId = status.InReplyToUserId;
2240
2241                         post.IsFav = true;
2242
2243                         //以下、ユーザー情報
2244                         var user = status.User;
2245                         post.UserId = user.Id;
2246                         post.ScreenName = user.ScreenName;
2247                         post.Nickname = user.Name.Trim();
2248                         post.ImageUrl = user.ProfileImageUrlHttps;
2249                         post.IsProtect = user.Protected;
2250                         post.IsMe = post.ScreenName.ToLower().Equals(_uname);
2251                     }
2252                     //HTMLに整形
2253                     string textFromApi = post.TextFromApi;
2254                     post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, entities, post.Media);
2255                     post.TextFromApi = textFromApi;
2256                     post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities);
2257                     post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
2258                     post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
2259                     //Source整形
2260                     CreateSource(post);
2261
2262                     post.IsRead = read;
2263                     post.IsReply = post.ReplyToList.Contains(_uname);
2264                     post.IsExcludeReply = false;
2265
2266                     if (post.IsMe)
2267                     {
2268                         post.IsOwl = false;
2269                     }
2270                     else
2271                     {
2272                         if (followerId.Count > 0) post.IsOwl = !followerId.Contains(post.UserId);
2273                     }
2274
2275                     post.IsDm = false;
2276                 }
2277                 catch(Exception ex)
2278                 {
2279                     MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2280                     continue;
2281                 }
2282
2283                 TabInformations.GetInstance().AddPost(post);
2284
2285             }
2286
2287             return "";
2288         }
2289
2290         private string ReplaceTextFromApi(string text, TwitterEntities entities)
2291         {
2292             if (entities != null)
2293             {
2294                 if (entities.Urls != null)
2295                 {
2296                     foreach (var m in entities.Urls)
2297                     {
2298                         if (!string.IsNullOrEmpty(m.DisplayUrl)) text = text.Replace(m.Url, m.DisplayUrl);
2299                     }
2300                 }
2301                 if (entities.Media != null)
2302                 {
2303                     foreach (var m in entities.Media)
2304                     {
2305                         if (!string.IsNullOrEmpty(m.DisplayUrl)) text = text.Replace(m.Url, m.DisplayUrl);
2306                     }
2307                 }
2308             }
2309             return text;
2310         }
2311
2312         /// <summary>
2313         /// フォロワーIDを更新します
2314         /// </summary>
2315         /// <exception cref="WebApiException"/>
2316         public void RefreshFollowerIds()
2317         {
2318             if (MyCommon._endingFlag) return;
2319
2320             var cursor = -1L;
2321             var newFollowerIds = new List<long>();
2322             do
2323             {
2324                 var ret = this.GetFollowerIdsApi(ref cursor);
2325                 newFollowerIds.AddRange(ret.Ids);
2326                 cursor = ret.NextCursor;
2327             } while (cursor != 0);
2328
2329             this.followerId = newFollowerIds;
2330             TabInformations.GetInstance().RefreshOwl(this.followerId);
2331
2332             this._GetFollowerResult = true;
2333         }
2334
2335         public bool GetFollowersSuccess
2336         {
2337             get
2338             {
2339                 return _GetFollowerResult;
2340             }
2341         }
2342
2343         private TwitterIds GetFollowerIdsApi(ref long cursor)
2344         {
2345             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2346                 throw new WebApiException("AccountState invalid");
2347
2348             HttpStatusCode res;
2349             var content = "";
2350             try
2351             {
2352                 res = twCon.FollowerIds(cursor, ref content);
2353             }
2354             catch(Exception e)
2355             {
2356                 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2357             }
2358
2359             var err = this.CheckStatusCode(res, content);
2360             if (err != null)
2361                 throw new WebApiException(err, content);
2362
2363             try
2364             {
2365                 var ret = TwitterIds.ParseJson(content);
2366
2367                 if (ret.Ids == null)
2368                 {
2369                     var ex = new WebApiException("Err: ret.id == null (GetFollowerIdsApi)", content);
2370                     MyCommon.ExceptionOut(ex);
2371                     throw ex;
2372                 }
2373
2374                 return ret;
2375             }
2376             catch(SerializationException e)
2377             {
2378                 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2379                 MyCommon.TraceOut(ex);
2380                 throw ex;
2381             }
2382             catch(Exception e)
2383             {
2384                 var ex = new WebApiException("Err:Invalid Json!", content, e);
2385                 MyCommon.TraceOut(ex);
2386                 throw ex;
2387             }
2388         }
2389
2390         /// <summary>
2391         /// RT 非表示ユーザーを更新します
2392         /// </summary>
2393         /// <exception cref="WebApiException"/>
2394         public void RefreshNoRetweetIds()
2395         {
2396             if (MyCommon._endingFlag) return;
2397
2398             this.noRTId = this.NoRetweetIdsApi();
2399
2400             this._GetNoRetweetResult = true;
2401         }
2402
2403         private long[] NoRetweetIdsApi()
2404         {
2405             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2406                 throw new WebApiException("AccountState invalid");
2407
2408             HttpStatusCode res;
2409             var content = "";
2410             try
2411             {
2412                 res = twCon.NoRetweetIds(ref content);
2413             }
2414             catch(Exception e)
2415             {
2416                 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2417             }
2418
2419             var err = this.CheckStatusCode(res, content);
2420             if (err != null)
2421                 throw new WebApiException(err, content);
2422
2423             try
2424             {
2425                 return MyCommon.CreateDataFromJson<long[]>(content);
2426             }
2427             catch(SerializationException e)
2428             {
2429                 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2430                 MyCommon.TraceOut(ex);
2431                 throw ex;
2432             }
2433             catch(Exception e)
2434             {
2435                 var ex = new WebApiException("Err:Invalid Json!", content, e);
2436                 MyCommon.TraceOut(ex);
2437                 throw ex;
2438             }
2439         }
2440
2441         public bool GetNoRetweetSuccess
2442         {
2443             get
2444             {
2445                 return _GetNoRetweetResult;
2446             }
2447         }
2448
2449         /// <summary>
2450         /// t.co の文字列長などの設定情報を取得します
2451         /// </summary>
2452         /// <exception cref="WebApiException"/>
2453         public TwitterConfiguration ConfigurationApi()
2454         {
2455             HttpStatusCode res;
2456             var content = "";
2457             try
2458             {
2459                 res = twCon.GetConfiguration(ref content);
2460             }
2461             catch(Exception e)
2462             {
2463                 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
2464             }
2465
2466             var err = this.CheckStatusCode(res, content);
2467             if (err != null)
2468                 throw new WebApiException(err, content);
2469
2470             try
2471             {
2472                 return TwitterConfiguration.ParseJson(content);
2473             }
2474             catch(SerializationException e)
2475             {
2476                 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
2477                 MyCommon.TraceOut(ex);
2478                 throw ex;
2479             }
2480             catch(Exception e)
2481             {
2482                 var ex = new WebApiException("Err:Invalid Json!", content, e);
2483                 MyCommon.TraceOut(ex);
2484                 throw ex;
2485             }
2486         }
2487
2488         public string GetListsApi()
2489         {
2490             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2491
2492             HttpStatusCode res;
2493             IEnumerable<ListElement> lists;
2494             var content = "";
2495
2496             try
2497             {
2498                 res = twCon.GetLists(this.Username, ref content);
2499             }
2500             catch (Exception ex)
2501             {
2502                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2503             }
2504
2505             var err = this.CheckStatusCode(res, content);
2506             if (err != null) return err;
2507
2508             try
2509             {
2510                 lists = TwitterList.ParseJsonArray(content)
2511                     .Select(x => new ListElement(x, this));
2512             }
2513             catch (SerializationException ex)
2514             {
2515                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2516                 return "Err:Json Parse Error(DataContractJsonSerializer)";
2517             }
2518             catch (Exception ex)
2519             {
2520                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2521                 return "Err:Invalid Json!";
2522             }
2523
2524             try
2525             {
2526                 res = twCon.GetListsSubscriptions(this.Username, ref content);
2527             }
2528             catch (Exception ex)
2529             {
2530                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2531             }
2532
2533             err = this.CheckStatusCode(res, content);
2534             if (err != null) return err;
2535
2536             try
2537             {
2538                 lists = lists.Concat(TwitterList.ParseJsonArray(content)
2539                     .Select(x => new ListElement(x, this)));
2540             }
2541             catch (SerializationException ex)
2542             {
2543                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2544                 return "Err:Json Parse Error(DataContractJsonSerializer)";
2545             }
2546             catch (Exception ex)
2547             {
2548                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2549                 return "Err:Invalid Json!";
2550             }
2551
2552             TabInformations.GetInstance().SubscribableLists = lists.ToList();
2553             return "";
2554         }
2555
2556         public string DeleteList(string list_id)
2557         {
2558             HttpStatusCode res;
2559             var content = "";
2560
2561             try
2562             {
2563                 res = twCon.DeleteListID(this.Username, list_id, ref content);
2564             }
2565             catch(Exception ex)
2566             {
2567                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2568             }
2569
2570             var err = this.CheckStatusCode(res, content);
2571             if (err != null) return err;
2572
2573             return "";
2574         }
2575
2576         public string EditList(string list_id, string new_name, bool isPrivate, string description, ref ListElement list)
2577         {
2578             HttpStatusCode res;
2579             var content = "";
2580
2581             try
2582             {
2583                 res = twCon.UpdateListID(this.Username, list_id, new_name, isPrivate, description, ref content);
2584             }
2585             catch(Exception ex)
2586             {
2587                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2588             }
2589
2590             var err = this.CheckStatusCode(res, content);
2591             if (err != null) return err;
2592
2593             try
2594             {
2595                 var le = TwitterList.ParseJson(content);
2596                 var newList = new ListElement(le, this);
2597                 list.Description = newList.Description;
2598                 list.Id = newList.Id;
2599                 list.IsPublic = newList.IsPublic;
2600                 list.MemberCount = newList.MemberCount;
2601                 list.Name = newList.Name;
2602                 list.SubscriberCount = newList.SubscriberCount;
2603                 list.Slug = newList.Slug;
2604                 list.Nickname = newList.Nickname;
2605                 list.Username = newList.Username;
2606                 list.UserId = newList.UserId;
2607                 return "";
2608             }
2609             catch(SerializationException ex)
2610             {
2611                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2612                 return "Err:Json Parse Error(DataContractJsonSerializer)";
2613             }
2614             catch(Exception ex)
2615             {
2616                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2617                 return "Err:Invalid Json!";
2618             }
2619
2620         }
2621
2622         public string GetListMembers(string list_id, List<UserInfo> lists, ref long cursor)
2623         {
2624             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2625
2626             HttpStatusCode res;
2627             var content = "";
2628             try
2629             {
2630                 res = twCon.GetListMembers(this.Username, list_id, cursor, ref content);
2631             }
2632             catch(Exception ex)
2633             {
2634                 return "Err:" + ex.Message;
2635             }
2636
2637             var err = this.CheckStatusCode(res, content);
2638             if (err != null) return err;
2639
2640             try
2641             {
2642                 var users = TwitterUsers.ParseJson(content);
2643                 Array.ForEach<TwitterUser>(
2644                     users.Users,
2645                     u => lists.Add(new UserInfo(u)));
2646                 cursor = users.NextCursor;
2647                 return "";
2648             }
2649             catch(SerializationException ex)
2650             {
2651                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2652                 return "Err:Json Parse Error(DataContractJsonSerializer)";
2653             }
2654             catch(Exception ex)
2655             {
2656                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2657                 return "Err:Invalid Json!";
2658             }
2659         }
2660
2661         public string CreateListApi(string listName, bool isPrivate, string description)
2662         {
2663             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2664
2665             HttpStatusCode res;
2666             var content = "";
2667             try
2668             {
2669                 res = twCon.CreateLists(listName, isPrivate, description, ref content);
2670             }
2671             catch(Exception ex)
2672             {
2673                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2674             }
2675
2676             var err = this.CheckStatusCode(res, content);
2677             if (err != null) return err;
2678
2679             try
2680             {
2681                 var le = TwitterList.ParseJson(content);
2682                 TabInformations.GetInstance().SubscribableLists.Add(new ListElement(le, this));
2683                 return "";
2684             }
2685             catch(SerializationException ex)
2686             {
2687                 MyCommon.TraceOut(ex.Message + Environment.NewLine + content);
2688                 return "Err:Json Parse Error(DataContractJsonSerializer)";
2689             }
2690             catch(Exception ex)
2691             {
2692                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
2693                 return "Err:Invalid Json!";
2694             }
2695         }
2696
2697         public string ContainsUserAtList(string listId, string user, ref bool value)
2698         {
2699             value = false;
2700
2701             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
2702
2703             HttpStatusCode res;
2704             var content = "";
2705
2706             try
2707             {
2708                 res = this.twCon.ShowListMember(listId, user, ref content);
2709             }
2710             catch(Exception ex)
2711             {
2712                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2713             }
2714
2715             if (res == HttpStatusCode.NotFound)
2716             {
2717                 value = false;
2718                 return "";
2719             }
2720
2721             var err = this.CheckStatusCode(res, content);
2722             if (err != null) return err;
2723
2724             try
2725             {
2726                 var u = TwitterUser.ParseJson(content);
2727                 value = true;
2728                 return "";
2729             }
2730             catch(Exception)
2731             {
2732                 value = false;
2733                 return "";
2734             }
2735         }
2736
2737         public string AddUserToList(string listId, string user)
2738         {
2739             HttpStatusCode res;
2740             var content = "";
2741
2742             try
2743             {
2744                 res = twCon.CreateListMembers(listId, user, ref content);
2745             }
2746             catch(Exception ex)
2747             {
2748                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2749             }
2750
2751             var err = this.CheckStatusCode(res, content);
2752             if (err != null) return err;
2753
2754             return "";
2755         }
2756
2757         public string RemoveUserToList(string listId, string user)
2758         {
2759             HttpStatusCode res;
2760             var content = "";
2761
2762             try
2763             {
2764                 res = twCon.DeleteListMembers(listId, user, ref content);
2765             }
2766             catch(Exception ex)
2767             {
2768                 return "Err:" + ex.Message + "(" + MethodBase.GetCurrentMethod().Name + ")";
2769             }
2770
2771             var err = this.CheckStatusCode(res, content);
2772             if (err != null) return err;
2773
2774             return "";
2775         }
2776
2777         private class range
2778         {
2779             public int fromIndex { get; set; }
2780             public int toIndex { get; set; }
2781             public range(int fromIndex, int toIndex)
2782             {
2783                 this.fromIndex = fromIndex;
2784                 this.toIndex = toIndex;
2785             }
2786         }
2787         public async Task<string> CreateHtmlAnchorAsync(string Text, List<string> AtList, Dictionary<string, string> media)
2788         {
2789             if (Text == null) return null;
2790             var retStr = Text.Replace("&gt;", "<<<<<tweenだいなり>>>>>").Replace("&lt;", "<<<<<tweenしょうなり>>>>>");
2791             //uriの正規表現
2792             //const string url_valid_domain = "(?<domain>(?:[^\p{P}\s][\.\-_](?=[^\p{P}\s])|[^\p{P}\s]){1,}\.[a-z]{2,}(?::[0-9]+)?)"
2793             //const string url_valid_general_path_chars = "[a-z0-9!*';:=+$/%#\[\]\-_,~]"
2794             //const string url_balance_parens = "(?:\(" + url_valid_general_path_chars + "+\))"
2795             //const string url_valid_url_path_ending_chars = "(?:[a-z0-9=_#/\-\+]+|" + url_balance_parens + ")"
2796             //const string pth = "(?:" + url_balance_parens +
2797             //    "|@" + url_valid_general_path_chars + "+/" +
2798             //    "|[.,]?" + url_valid_general_path_chars + "+" +
2799             //    ")"
2800             //const string pth2 = "(/(?:" +
2801             //    pth + "+" + url_valid_url_path_ending_chars + "|" +
2802             //    pth + "+" + url_valid_url_path_ending_chars + "?|" +
2803             //    url_valid_url_path_ending_chars +
2804             //    ")?)?"
2805             //const string qry = "(?<query>\?[a-z0-9!*'();:&=+$/%#\[\]\-_.,~]*[a-z0-9_&=#])?"
2806             //const string rgUrl = "(?<before>(?:[^\""':!=#]|^|\:/))" +
2807             //                            "(?<url>(?<protocol>https?://)" +
2808             //                            url_valid_domain +
2809             //                            pth2 +
2810             //                            qry +
2811             //                            ")"
2812             //const string rgUrl = "(?<before>(?:[^\""':!=#]|^|\:/))" +
2813             //                            "(?<url>(?<protocol>https?://|www\.)" +
2814             //                            url_valid_domain +
2815             //                            pth2 +
2816             //                            qry +
2817             //                            ")"
2818             //絶対パス表現のUriをリンクに置換
2819             retStr = await new Regex(rgUrl, RegexOptions.IgnoreCase).ReplaceAsync(retStr, async mu =>
2820             {
2821                 var sb = new StringBuilder(mu.Result("${before}<a href=\""));
2822                 //if (mu.Result("${protocol}").StartsWith("w", StringComparison.OrdinalIgnoreCase))
2823                 //    sb.Append("http://");
2824                 //}
2825                 var url = mu.Result("${url}");
2826                 var title = await ShortUrl.Instance.ExpandUrlStrAsync(url);
2827                 sb.Append(url + "\" title=\"" + MyCommon.ConvertToReadableUrl(title) + "\">").Append(url).Append("</a>");
2828                 if (media != null && !media.ContainsKey(url)) media.Add(url, title);
2829                 return sb.ToString();
2830             });
2831
2832             //@先をリンクに置換(リスト)
2833             retStr = Regex.Replace(retStr,
2834                                    @"(^|[^a-zA-Z0-9_/])([@@]+)([a-zA-Z0-9_]{1,20}/[a-zA-Z][a-zA-Z0-9\p{IsLatin-1Supplement}\-]{0,79})",
2835                                    "$1$2<a href=\"/$3\">$3</a>");
2836
2837             var m = Regex.Match(retStr, "(^|[^a-zA-Z0-9_])[@@]([a-zA-Z0-9_]{1,20})");
2838             while (m.Success)
2839             {
2840                 if (!AtList.Contains(m.Result("$2").ToLower())) AtList.Add(m.Result("$2").ToLower());
2841                 m = m.NextMatch();
2842             }
2843             //@先をリンクに置換
2844             retStr = Regex.Replace(retStr,
2845                                    "(^|[^a-zA-Z0-9_/])([@@])([a-zA-Z0-9_]{1,20})",
2846                                    "$1$2<a href=\"/$3\">$3</a>");
2847
2848             //ハッシュタグを抽出し、リンクに置換
2849             var anchorRange = new List<range>();
2850             for (int i = 0; i < retStr.Length; i++)
2851             {
2852                 var index = retStr.IndexOf("<a ", i);
2853                 if (index > -1 && index < retStr.Length)
2854                 {
2855                     i = index;
2856                     var toIndex = retStr.IndexOf("</a>", index);
2857                     if (toIndex > -1)
2858                     {
2859                         anchorRange.Add(new range(index, toIndex + 3));
2860                         i = toIndex;
2861                     }
2862                 }
2863             }
2864             //retStr = Regex.Replace(retStr,
2865             //                       "(^|[^a-zA-Z0-9/&])([##])([0-9a-zA-Z_]*[a-zA-Z_]+[a-zA-Z0-9_\xc0-\xd6\xd8-\xf6\xf8-\xff]*)",
2866             //                       new MatchEvaluator(Function(mh As Match)
2867             //                                              foreach (var rng in anchorRange)
2868             //                                              {
2869             //                                                  if (mh.Index >= rng.fromIndex &&
2870             //                                                   mh.Index <= rng.toIndex) return mh.Result("$0");
2871             //                                              }
2872             //                                              if (IsNumeric(mh.Result("$3"))) return mh.Result("$0");
2873             //                                              lock (LockObj)
2874             //                                              {
2875             //                                                  _hashList.Add("#" + mh.Result("$3"))
2876             //                                              }
2877             //                                              return mh.Result("$1") + "<a href=\"" + _protocol + "twitter.com/search?q=%23" + mh.Result("$3") + "\">" + mh.Result("$2$3") + "</a>";
2878             //                                          }),
2879             //                                      RegexOptions.IgnoreCase)
2880             retStr = Regex.Replace(retStr,
2881                                    HASHTAG,
2882                                    new MatchEvaluator(mh =>
2883                                                       {
2884                                                           foreach (var rng in anchorRange)
2885                                                           {
2886                                                               if (mh.Index >= rng.fromIndex &&
2887                                                                mh.Index <= rng.toIndex) return mh.Result("$0");
2888                                                           }
2889                                                           lock (LockObj)
2890                                                           {
2891                                                               _hashList.Add("#" + mh.Result("$3"));
2892                                                           }
2893                                                           return mh.Result("$1") + "<a href=\"https://twitter.com/search?q=%23" + mh.Result("$3") + "\">" + mh.Result("$2$3") + "</a>";
2894                                                       }),
2895                                                   RegexOptions.IgnoreCase);
2896
2897
2898             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>");
2899
2900             retStr = retStr.Replace("<<<<<tweenだいなり>>>>>", "&gt;").Replace("<<<<<tweenしょうなり>>>>>", "&lt;");
2901
2902             //retStr = AdjustHtml(ShortUrl.Resolve(PreProcessUrl(retStr), true)) //IDN置換、短縮Uri解決、@リンクを相対→絶対にしてtarget属性付与
2903             retStr = AdjustHtml(PreProcessUrl(retStr)); //IDN置換、短縮Uri解決、@リンクを相対→絶対にしてtarget属性付与
2904             return retStr;
2905         }
2906
2907         public async Task<string> CreateHtmlAnchorAsync(string text, List<string> AtList, TwitterEntities entities, Dictionary<string, string> media)
2908         {
2909             if (entities != null)
2910             {
2911                 if (entities.Urls != null)
2912                 {
2913                     foreach (var ent in entities.Urls)
2914                     {
2915                         ent.ExpandedUrl = await ShortUrl.Instance.ExpandUrlStrAsync(ent.ExpandedUrl)
2916                             .ConfigureAwait(false);
2917
2918                         if (media != null && !media.ContainsKey(ent.Url))
2919                             media.Add(ent.Url, ent.ExpandedUrl);
2920                     }
2921                 }
2922                 if (entities.Hashtags != null)
2923                 {
2924                     lock (this.LockObj)
2925                     {
2926                         this._hashList.AddRange(entities.Hashtags.Select(x => "#" + x.Text));
2927                     }
2928                 }
2929                 if (entities.UserMentions != null)
2930                 {
2931                     foreach (var ent in entities.UserMentions)
2932                     {
2933                         var screenName = ent.ScreenName.ToLower();
2934                         if (!AtList.Contains(screenName))
2935                             AtList.Add(screenName);
2936                     }
2937                 }
2938                 if (entities.Media != null)
2939                 {
2940                     foreach (var ent in entities.Media)
2941                     {
2942                         if (media != null && !media.ContainsKey(ent.Url))
2943                             media.Add(ent.Url, ent.MediaUrl);
2944                     }
2945                 }
2946             }
2947
2948             text = TweetFormatter.AutoLinkHtml(text, entities);
2949
2950             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>");
2951             text = PreProcessUrl(text); //IDN置換
2952
2953             return text;
2954         }
2955
2956         [Obsolete]
2957         public string CreateHtmlAnchor(string text, List<string> AtList, TwitterEntities entities, Dictionary<string, string> media)
2958         {
2959             return this.CreateHtmlAnchorAsync(text, AtList, entities, media).Result;
2960         }
2961
2962         //Source整形
2963         private void CreateSource(PostClass post)
2964         {
2965             if (string.IsNullOrEmpty(post.Source)) return;
2966
2967             if (post.Source.StartsWith("<"))
2968             {
2969                 if (!post.Source.Contains("</a>"))
2970                 {
2971                     post.Source += "</a>";
2972                 }
2973                 var mS = Regex.Match(post.Source, ">(?<source>.+)<");
2974                 if (mS.Success)
2975                 {
2976                     post.SourceHtml = ShortUrl.Instance.ExpandUrlHtml(PreProcessUrl(post.Source));
2977                     post.Source = WebUtility.HtmlDecode(mS.Result("${source}"));
2978                 }
2979                 else
2980                 {
2981                     post.Source = "";
2982                     post.SourceHtml = "";
2983                 }
2984             }
2985             else
2986             {
2987                 if (post.Source == "web")
2988                 {
2989                     post.SourceHtml = Properties.Resources.WebSourceString;
2990                 }
2991                 else if (post.Source == "Keitai Mail")
2992                 {
2993                     post.SourceHtml = Properties.Resources.KeitaiMailSourceString;
2994                 }
2995                 else
2996                 {
2997                     post.SourceHtml = post.Source;
2998                 }
2999             }
3000         }
3001
3002         public TwitterApiStatus GetInfoApi()
3003         {
3004             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return null;
3005
3006             if (MyCommon._endingFlag) return null;
3007
3008             HttpStatusCode res;
3009             var content = "";
3010             try
3011             {
3012                 res = twCon.RateLimitStatus(ref content);
3013             }
3014             catch (Exception)
3015             {
3016                 this.ResetApiStatus();
3017                 return null;
3018             }
3019
3020             var err = this.CheckStatusCode(res, content);
3021             if (err != null) return null;
3022
3023             try
3024             {
3025                 MyCommon.TwitterApiInfo.UpdateFromJson(content);
3026                 return MyCommon.TwitterApiInfo;
3027             }
3028             catch (Exception ex)
3029             {
3030                 MyCommon.TraceOut(ex, MethodBase.GetCurrentMethod().Name + " " + content);
3031                 MyCommon.TwitterApiInfo.Reset();
3032                 return null;
3033             }
3034         }
3035
3036         /// <summary>
3037         /// ブロック中のユーザーを更新します
3038         /// </summary>
3039         /// <exception cref="WebApiException"/>
3040         public void RefreshBlockIds()
3041         {
3042             if (MyCommon._endingFlag) return;
3043
3044             var cursor = -1L;
3045             var newBlockIds = new List<long>();
3046             do
3047             {
3048                 var ret = this.GetBlockIdsApi(cursor);
3049                 newBlockIds.AddRange(ret.Ids);
3050                 cursor = ret.NextCursor;
3051             } while (cursor != 0);
3052
3053             newBlockIds.Remove(this.UserId); // 元のソースにあったので一応残しておく
3054
3055             TabInformations.GetInstance().BlockIds = newBlockIds;
3056         }
3057
3058         public TwitterIds GetBlockIdsApi(long cursor)
3059         {
3060             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
3061                 throw new WebApiException("AccountState invalid");
3062
3063             HttpStatusCode res;
3064             var content = "";
3065             try
3066             {
3067                 res = twCon.GetBlockUserIds(ref content, cursor);
3068             }
3069             catch(Exception e)
3070             {
3071                 throw new WebApiException("Err:" + e.Message + "(" + MethodBase.GetCurrentMethod().Name + ")", e);
3072             }
3073
3074             var err = this.CheckStatusCode(res, content);
3075             if (err != null)
3076                 throw new WebApiException(err, content);
3077
3078             try
3079             {
3080                 return TwitterIds.ParseJson(content);
3081             }
3082             catch(SerializationException e)
3083             {
3084                 var ex = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, e);
3085                 MyCommon.TraceOut(ex);
3086                 throw ex;
3087             }
3088             catch(Exception e)
3089             {
3090                 var ex = new WebApiException("Err:Invalid Json!", content, e);
3091                 MyCommon.TraceOut(ex);
3092                 throw ex;
3093             }
3094         }
3095
3096         /// <summary>
3097         /// ミュート中のユーザーIDを更新します
3098         /// </summary>
3099         /// <exception cref="WebApiException"/>
3100         public async Task RefreshMuteUserIdsAsync()
3101         {
3102             if (MyCommon._endingFlag) return;
3103
3104             var ids = await TwitterIds.GetAllItemsAsync(this.GetMuteUserIdsApiAsync)
3105                 .ConfigureAwait(false);
3106
3107             TabInformations.GetInstance().MuteUserIds = ids.ToList();
3108         }
3109
3110         public async Task<TwitterIds> GetMuteUserIdsApiAsync(long cursor)
3111         {
3112             var content = "";
3113
3114             try
3115             {
3116                 var res = await Task.Run(() => twCon.GetMuteUserIds(ref content, cursor))
3117                     .ConfigureAwait(false);
3118
3119                 var err = this.CheckStatusCode(res, content);
3120                 if (err != null)
3121                     throw new WebApiException(err, content);
3122
3123                 return TwitterIds.ParseJson(content);
3124             }
3125             catch (WebException ex)
3126             {
3127                 var ex2 = new WebApiException("Err:" + ex.Message, ex);
3128                 MyCommon.TraceOut(ex2);
3129                 throw ex2;
3130             }
3131             catch (SerializationException ex)
3132             {
3133                 var ex2 = new WebApiException("Err:Json Parse Error(DataContractJsonSerializer)", content, ex);
3134                 MyCommon.TraceOut(ex2);
3135                 throw ex2;
3136             }
3137         }
3138
3139         public string[] GetHashList()
3140         {
3141             string[] hashArray;
3142             lock (LockObj)
3143             {
3144                 hashArray = _hashList.ToArray();
3145                 _hashList.Clear();
3146             }
3147             return hashArray;
3148         }
3149
3150         public string AccessToken
3151         {
3152             get
3153             {
3154                 return twCon.AccessToken;
3155             }
3156         }
3157
3158         public string AccessTokenSecret
3159         {
3160             get
3161             {
3162                 return twCon.AccessTokenSecret;
3163             }
3164         }
3165
3166         [MethodImpl(MethodImplOptions.NoInlining)] // 呼び出し元の取得に必要
3167         private string CheckStatusCode(HttpStatusCode httpStatus, string responseText)
3168         {
3169             if (httpStatus == HttpStatusCode.OK)
3170             {
3171                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3172                 return null;
3173             }
3174
3175             // 404エラーの挙動が変なので無視: https://dev.twitter.com/discussions/1213
3176             if (httpStatus == HttpStatusCode.NotFound) return null;
3177
3178             var callerMethod = new StackTrace(false).GetFrame(1).GetMethod();
3179             var callerMethodName = callerMethod != null
3180                 ? callerMethod.Name
3181                 : "";
3182
3183             if (string.IsNullOrWhiteSpace(responseText))
3184             {
3185                 if (httpStatus == HttpStatusCode.Unauthorized)
3186                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3187
3188                 return "Err:" + httpStatus + "(" + callerMethodName + ")";
3189             }
3190
3191             try
3192             {
3193                 var errors = TwitterError.ParseJson(responseText).Errors;
3194                 if (errors == null || !errors.Any())
3195                 {
3196                     return "Err:" + httpStatus + "(" + callerMethodName + ")";
3197                 }
3198
3199                 foreach (var error in errors)
3200                 {
3201                     if (error.Code == TwitterErrorCode.InvalidToken ||
3202                         error.Code == TwitterErrorCode.SuspendedAccount)
3203                     {
3204                         Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3205                     }
3206                 }
3207
3208                 return "Err:" + string.Join(",", errors.Select(x => x.ToString())) + "(" + callerMethodName + ")";
3209             }
3210             catch (SerializationException) { }
3211
3212             return "Err:" + httpStatus + "(" + callerMethodName + ")";
3213         }
3214
3215 #region "UserStream"
3216         private string trackWord_ = "";
3217         public string TrackWord
3218         {
3219             get
3220             {
3221                 return trackWord_;
3222             }
3223             set
3224             {
3225                 trackWord_ = value;
3226             }
3227         }
3228         private bool allAtReply_ = false;
3229         public bool AllAtReply
3230         {
3231             get
3232             {
3233                 return allAtReply_;
3234             }
3235             set
3236             {
3237                 allAtReply_ = value;
3238             }
3239         }
3240
3241         public event Action NewPostFromStream;
3242         public event Action UserStreamStarted;
3243         public event Action UserStreamStopped;
3244         public event Action<long> PostDeleted;
3245         public event Action<FormattedEvent> UserStreamEventReceived;
3246         private DateTime _lastUserstreamDataReceived;
3247         private TwitterUserstream userStream;
3248
3249         public class FormattedEvent
3250         {
3251             public MyCommon.EVENTTYPE Eventtype { get; set; }
3252             public DateTime CreatedAt { get; set; }
3253             public string Event { get; set; }
3254             public string Username { get; set; }
3255             public string Target { get; set; }
3256             public Int64 Id { get; set; }
3257             public bool IsMe { get; set; }
3258         }
3259
3260         public List<FormattedEvent> storedEvent_ = new List<FormattedEvent>();
3261         public List<FormattedEvent> StoredEvent
3262         {
3263             get
3264             {
3265                 return storedEvent_;
3266             }
3267             set
3268             {
3269                 storedEvent_ = value;
3270             }
3271         }
3272
3273         private class EventTypeTableElement
3274         {
3275             public string Name;
3276             public MyCommon.EVENTTYPE Type;
3277
3278             public EventTypeTableElement(string name, MyCommon.EVENTTYPE type)
3279             {
3280                 this.Name = name;
3281                 this.Type = type;
3282             }
3283         }
3284
3285         private EventTypeTableElement[] EventTable = {
3286             new EventTypeTableElement("favorite", MyCommon.EVENTTYPE.Favorite),
3287             new EventTypeTableElement("unfavorite", MyCommon.EVENTTYPE.Unfavorite),
3288             new EventTypeTableElement("follow", MyCommon.EVENTTYPE.Follow),
3289             new EventTypeTableElement("list_member_added", MyCommon.EVENTTYPE.ListMemberAdded),
3290             new EventTypeTableElement("list_member_removed", MyCommon.EVENTTYPE.ListMemberRemoved),
3291             new EventTypeTableElement("block", MyCommon.EVENTTYPE.Block),
3292             new EventTypeTableElement("unblock", MyCommon.EVENTTYPE.Unblock),
3293             new EventTypeTableElement("user_update", MyCommon.EVENTTYPE.UserUpdate),
3294             new EventTypeTableElement("deleted", MyCommon.EVENTTYPE.Deleted),
3295             new EventTypeTableElement("list_created", MyCommon.EVENTTYPE.ListCreated),
3296             new EventTypeTableElement("list_destroyed", MyCommon.EVENTTYPE.ListDestroyed), 
3297             new EventTypeTableElement("list_updated", MyCommon.EVENTTYPE.ListUpdated),
3298             new EventTypeTableElement("unfollow", MyCommon.EVENTTYPE.Unfollow),
3299             new EventTypeTableElement("list_user_subscribed", MyCommon.EVENTTYPE.ListUserSubscribed),
3300             new EventTypeTableElement("list_user_unsubscribed", MyCommon.EVENTTYPE.ListUserUnsubscribed),
3301         };
3302
3303         public MyCommon.EVENTTYPE EventNameToEventType(string EventName)
3304         {
3305             return (from tbl in EventTable where tbl.Name.Equals(EventName) select tbl.Type).FirstOrDefault();
3306         }
3307
3308         public bool IsUserstreamDataReceived
3309         {
3310             get
3311             {
3312                 return DateTime.Now.Subtract(this._lastUserstreamDataReceived).TotalSeconds < 31;
3313             }
3314         }
3315
3316         private void userStream_StatusArrived(string line)
3317         {
3318             this._lastUserstreamDataReceived = DateTime.Now;
3319             if (string.IsNullOrEmpty(line)) return;
3320
3321             var isDm = false;
3322
3323             try
3324             {
3325                 using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(line), XmlDictionaryReaderQuotas.Max))
3326                 {
3327                     var xElm = XElement.Load(jsonReader);
3328                     if (xElm.Element("friends") != null)
3329                     {
3330                         Debug.WriteLine("friends");
3331                         return;
3332                     }
3333                     else if (xElm.Element("delete") != null)
3334                     {
3335                         Debug.WriteLine("delete");
3336                         Int64 id;
3337                         if (xElm.Element("delete").Element("direct_message") != null &&
3338                             xElm.Element("delete").Element("direct_message").Element("id") != null)
3339                         {
3340                             id = 0;
3341                             long.TryParse(xElm.Element("delete").Element("direct_message").Element("id").Value, out id);
3342                             if (PostDeleted != null)
3343                             {
3344                                 PostDeleted(id);
3345                             }
3346                         }
3347                         else if (xElm.Element("delete").Element("status") != null &&
3348                             xElm.Element("delete").Element("status").Element("id") != null)
3349                         {
3350                             id = 0;
3351                             long.TryParse(xElm.Element("delete").Element("status").Element("id").Value, out id);
3352                             if (PostDeleted != null)
3353                             {
3354                                 PostDeleted(id);
3355                             }
3356                         }
3357                         else
3358                         {
3359                             MyCommon.TraceOut("delete:" + line);
3360                             return;
3361                         }
3362                         for (int i = this.StoredEvent.Count - 1; i >= 0; i--)
3363                         {
3364                             var sEvt = this.StoredEvent[i];
3365                             if (sEvt.Id == id && (sEvt.Event == "favorite" || sEvt.Event == "unfavorite"))
3366                             {
3367                                 this.StoredEvent.RemoveAt(i);
3368                             }
3369                         }
3370                         return;
3371                     }
3372                     else if (xElm.Element("limit") != null)
3373                     {
3374                         Debug.WriteLine(line);
3375                         return;
3376                     }
3377                     else if (xElm.Element("event") != null)
3378                     {
3379                         Debug.WriteLine("event: " + xElm.Element("event").Value);
3380                         CreateEventFromJson(line);
3381                         return;
3382                     }
3383                     else if (xElm.Element("direct_message") != null)
3384                     {
3385                         Debug.WriteLine("direct_message");
3386                         isDm = true;
3387                     }
3388                     else if (xElm.Element("scrub_geo") != null)
3389                     {
3390                         try
3391                         {
3392                             TabInformations.GetInstance().ScrubGeoReserve(long.Parse(xElm.Element("scrub_geo").Element("user_id").Value),
3393                                                                         long.Parse(xElm.Element("scrub_geo").Element("up_to_status_id").Value));
3394                         }
3395                         catch(Exception)
3396                         {
3397                             MyCommon.TraceOut("scrub_geo:" + line);
3398                         }
3399                         return;
3400                     }
3401                 }
3402
3403                 if (isDm)
3404                 {
3405                     CreateDirectMessagesFromJson(line, MyCommon.WORKERTYPE.UserStream, false);
3406                 }
3407                 else
3408                 {
3409                     long dummy = 0;
3410                     CreatePostsFromJson("[" + line + "]", MyCommon.WORKERTYPE.Timeline, null, false, 0, ref dummy);
3411                 }
3412             }
3413             catch(NullReferenceException)
3414             {
3415                 MyCommon.TraceOut("NullRef StatusArrived: " + line);
3416             }
3417
3418             if (NewPostFromStream != null)
3419             {
3420                 NewPostFromStream();
3421             }
3422         }
3423
3424         private void CreateEventFromJson(string content)
3425         {
3426             TwitterStreamEvent eventData = null;
3427             try
3428             {
3429                 eventData = TwitterStreamEvent.ParseJson(content);
3430             }
3431             catch(SerializationException ex)
3432             {
3433                 MyCommon.TraceOut(ex, "Event Serialize Exception!" + Environment.NewLine + content);
3434             }
3435             catch(Exception ex)
3436             {
3437                 MyCommon.TraceOut(ex, "Event Exception!" + Environment.NewLine + content);
3438             }
3439
3440             var evt = new FormattedEvent();
3441             evt.CreatedAt = MyCommon.DateTimeParse(eventData.CreatedAt);
3442             evt.Event = eventData.Event;
3443             evt.Username = eventData.Source.ScreenName;
3444             evt.IsMe = evt.Username.ToLower().Equals(this.Username.ToLower());
3445             evt.Eventtype = EventNameToEventType(evt.Event);
3446             switch (eventData.Event)
3447             {
3448                 case "access_revoked":
3449                     return;
3450                 case "follow":
3451                     if (eventData.Target.ScreenName.ToLower().Equals(_uname))
3452                     {
3453                         if (!this.followerId.Contains(eventData.Source.Id)) this.followerId.Add(eventData.Source.Id);
3454                     }
3455                     else
3456                     {
3457                         return;    //Block後のUndoをすると、SourceとTargetが逆転したfollowイベントが帰ってくるため。
3458                     }
3459                     evt.Target = "";
3460                     break;
3461                 case "unfollow":
3462                     evt.Target = "@" + eventData.Target.ScreenName;
3463                     break;
3464                 case "favorite":
3465                 case "unfavorite":
3466                     var tweetEvent = TwitterStreamEvent<TwitterStatus>.ParseJson(content);
3467                     evt.Target = "@" + tweetEvent.TargetObject.User.ScreenName + ":" + WebUtility.HtmlDecode(tweetEvent.TargetObject.Text);
3468                     evt.Id = tweetEvent.TargetObject.Id;
3469                     if (AppendSettingDialog.Instance.IsRemoveSameEvent)
3470                     {
3471                         if (StoredEvent.Any(ev =>
3472                                            {
3473                                                return ev.Username == evt.Username && ev.Eventtype == evt.Eventtype && ev.Target == evt.Target;
3474                                            })) return;
3475                     }
3476                     if (TabInformations.GetInstance().ContainsKey(tweetEvent.TargetObject.Id))
3477                     {
3478                         var post = TabInformations.GetInstance()[tweetEvent.TargetObject.Id];
3479                         if (eventData.Event == "favorite")
3480                         {
3481                             if (evt.Username.ToLower().Equals(_uname))
3482                             {
3483                                 post.IsFav = true;
3484                                 TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, false);
3485                             }
3486                             else
3487                             {
3488                                 post.FavoritedCount++;
3489                                 if (!TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Contains(post.StatusId))
3490                                 {
3491                                     if (AppendSettingDialog.Instance.FavEventUnread && post.IsRead)
3492                                     {
3493                                         post.IsRead = false;
3494                                     }
3495                                     TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, false);
3496                                 }
3497                                 else
3498                                 {
3499                                     if (AppendSettingDialog.Instance.FavEventUnread)
3500                                     {
3501                                         TabInformations.GetInstance().SetRead(false, TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).TabName, TabInformations.GetInstance().GetTabByType(MyCommon.TabUsageType.Favorites).IndexOf(post.StatusId));
3502                                     }
3503                                 }
3504                             }
3505                         }
3506                         else
3507                         {
3508                             if (evt.Username.ToLower().Equals(_uname))
3509                             {
3510                                 post.IsFav = false;
3511                             }
3512                             else
3513                             {
3514                                 post.FavoritedCount--;
3515                                 if (post.FavoritedCount < 0) post.FavoritedCount = 0;
3516                             }
3517                         }
3518                     }
3519                     break;
3520                 case "list_member_added":
3521                 case "list_member_removed":
3522                 case "list_destroyed":
3523                 case "list_updated":
3524                 case "list_user_subscribed":
3525                 case "list_user_unsubscribed":
3526                     var listEvent = TwitterStreamEvent<TwitterList>.ParseJson(content);
3527                     evt.Target = listEvent.TargetObject.FullName;
3528                     break;
3529                 case "block":
3530                     if (!TabInformations.GetInstance().BlockIds.Contains(eventData.Target.Id)) TabInformations.GetInstance().BlockIds.Add(eventData.Target.Id);
3531                     evt.Target = "";
3532                     break;
3533                 case "unblock":
3534                     if (TabInformations.GetInstance().BlockIds.Contains(eventData.Target.Id)) TabInformations.GetInstance().BlockIds.Remove(eventData.Target.Id);
3535                     evt.Target = "";
3536                     break;
3537                 case "user_update":
3538                     evt.Target = "";
3539                     break;
3540                 case "list_created":
3541                     evt.Target = "";
3542                     break;
3543                 default:
3544                     MyCommon.TraceOut("Unknown Event:" + evt.Event + Environment.NewLine + content);
3545                     break;
3546             }
3547             this.StoredEvent.Insert(0, evt);
3548             if (UserStreamEventReceived != null)
3549             {
3550                 UserStreamEventReceived(evt);
3551             }
3552         }
3553
3554         private void userStream_Started()
3555         {
3556             if (UserStreamStarted != null)
3557             {
3558                 UserStreamStarted();
3559             }
3560         }
3561
3562         private void userStream_Stopped()
3563         {
3564             if (UserStreamStopped != null)
3565             {
3566                 UserStreamStopped();
3567             }
3568         }
3569
3570         public bool UserStreamEnabled
3571         {
3572             get
3573             {
3574                 return userStream == null ? false : userStream.Enabled;
3575             }
3576         }
3577
3578         public void StartUserStream()
3579         {
3580             if (userStream != null)
3581             {
3582                 StopUserStream();
3583             }
3584             userStream = new TwitterUserstream(twCon);
3585             userStream.StatusArrived += userStream_StatusArrived;
3586             userStream.Started += userStream_Started;
3587             userStream.Stopped += userStream_Stopped;
3588             userStream.Start(this.AllAtReply, this.TrackWord);
3589         }
3590
3591         public void StopUserStream()
3592         {
3593             if (userStream != null) userStream.Dispose();
3594             userStream = null;
3595             if (!MyCommon._endingFlag)
3596             {
3597                 if (UserStreamStopped != null)
3598                 {
3599                     UserStreamStopped();
3600                 }
3601             }
3602         }
3603
3604         public void ReconnectUserStream()
3605         {
3606             if (userStream != null)
3607             {
3608                 this.StartUserStream();
3609             }
3610         }
3611
3612         private class TwitterUserstream : IDisposable
3613         {
3614             public event Action<string> StatusArrived;
3615             public event Action Stopped;
3616             public event Action Started;
3617             private HttpTwitter twCon;
3618
3619             private Thread _streamThread;
3620             private bool _streamActive;
3621
3622             private bool _allAtreplies = false;
3623             private string _trackwords = "";
3624
3625             public TwitterUserstream(HttpTwitter twitterConnection)
3626             {
3627                 twCon = (HttpTwitter)twitterConnection.Clone();
3628             }
3629
3630             public void Start(bool allAtReplies, string trackwords)
3631             {
3632                 this.AllAtReplies = allAtReplies;
3633                 this.TrackWords = trackwords;
3634                 _streamActive = true;
3635                 if (_streamThread != null && _streamThread.IsAlive) return;
3636                 _streamThread = new Thread(UserStreamLoop);
3637                 _streamThread.Name = "UserStreamReceiver";
3638                 _streamThread.IsBackground = true;
3639                 _streamThread.Start();
3640             }
3641
3642             public bool Enabled
3643             {
3644                 get
3645                 {
3646                     return _streamActive;
3647                 }
3648             }
3649
3650             public bool AllAtReplies
3651             {
3652                 get
3653                 {
3654                     return _allAtreplies;
3655                 }
3656                 set
3657                 {
3658                     _allAtreplies = value;
3659                 }
3660             }
3661
3662             public string TrackWords
3663             {
3664                 get
3665                 {
3666                     return _trackwords;
3667                 }
3668                 set
3669                 {
3670                     _trackwords = value;
3671                 }
3672             }
3673
3674             private void UserStreamLoop()
3675             {
3676                 var sleepSec = 0;
3677                 do
3678                 {
3679                     Stream st = null;
3680                     StreamReader sr = null;
3681                     try
3682                     {
3683                         if (!MyCommon.IsNetworkAvailable())
3684                         {
3685                             sleepSec = 30;
3686                             continue;
3687                         }
3688
3689                         if (Started != null)
3690                         {
3691                             Started();
3692                         }
3693                         var res = twCon.UserStream(ref st, _allAtreplies, _trackwords, MyCommon.GetUserAgentString());
3694
3695                         switch (res)
3696                         {
3697                             case HttpStatusCode.OK:
3698                                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3699                                 break;
3700                             case HttpStatusCode.Unauthorized:
3701                                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
3702                                 sleepSec = 120;
3703                                 continue;
3704                         }
3705
3706                         if (st == null)
3707                         {
3708                             sleepSec = 30;
3709                             //MyCommon.TraceOut("Stop:stream is null")
3710                             continue;
3711                         }
3712
3713                         sr = new StreamReader(st);
3714
3715                         while (_streamActive && !sr.EndOfStream && Twitter.AccountState == MyCommon.ACCOUNT_STATE.Valid)
3716                         {
3717                             if (StatusArrived != null)
3718                             {
3719                                 StatusArrived(sr.ReadLine());
3720                             }
3721                             //this.LastTime = Now;
3722                         }
3723
3724                         if (sr.EndOfStream || Twitter.AccountState == MyCommon.ACCOUNT_STATE.Invalid)
3725                         {
3726                             sleepSec = 30;
3727                             //MyCommon.TraceOut("Stop:EndOfStream")
3728                             continue;
3729                         }
3730                         break;
3731                     }
3732                     catch(WebException ex)
3733                     {
3734                         if (ex.Status == WebExceptionStatus.Timeout)
3735                         {
3736                             sleepSec = 30;                        //MyCommon.TraceOut("Stop:Timeout")
3737                         }
3738                         else if (ex.Response != null && (int)((HttpWebResponse)ex.Response).StatusCode == 420)
3739                         {
3740                             //MyCommon.TraceOut("Stop:Connection Limit")
3741                             break;
3742                         }
3743                         else
3744                         {
3745                             sleepSec = 30;
3746                             //MyCommon.TraceOut("Stop:WebException " + ex.Status.ToString())
3747                         }
3748                     }
3749                     catch(ThreadAbortException)
3750                     {
3751                         break;
3752                     }
3753                     catch(IOException)
3754                     {
3755                         sleepSec = 30;
3756                         //MyCommon.TraceOut("Stop:IOException with Active." + Environment.NewLine + ex.Message)
3757                     }
3758                     catch(ArgumentException ex)
3759                     {
3760                         //System.ArgumentException: ストリームを読み取れませんでした。
3761                         //サーバー側もしくは通信経路上で切断された場合?タイムアウト頻発後発生
3762                         sleepSec = 30;
3763                         MyCommon.TraceOut(ex, "Stop:ArgumentException");
3764                     }
3765                     catch(Exception ex)
3766                     {
3767                         MyCommon.TraceOut("Stop:Exception." + Environment.NewLine + ex.Message);
3768                         MyCommon.ExceptionOut(ex);
3769                         sleepSec = 30;
3770                     }
3771                     finally
3772                     {
3773                         if (_streamActive)
3774                         {
3775                             if (Stopped != null)
3776                             {
3777                                 Stopped();
3778                             }
3779                         }
3780                         twCon.RequestAbort();
3781                         if (sr != null) sr.Close();
3782                         if (sleepSec > 0)
3783                         {
3784                             var ms = 0;
3785                             while (_streamActive && ms < sleepSec * 1000)
3786                             {
3787                                 Thread.Sleep(500);
3788                                 ms += 500;
3789                             }
3790                         }
3791                         sleepSec = 0;
3792                     }
3793                 } while (this._streamActive);
3794
3795                 if (_streamActive)
3796                 {
3797                     if (Stopped != null)
3798                     {
3799                         Stopped();
3800                     }
3801                 }
3802                 MyCommon.TraceOut("Stop:EndLoop");
3803             }
3804
3805 #region "IDisposable Support"
3806             private bool disposedValue; // 重複する呼び出しを検出するには
3807
3808             // IDisposable
3809             protected virtual void Dispose(bool disposing)
3810             {
3811                 if (!this.disposedValue)
3812                 {
3813                     if (disposing)
3814                     {
3815                         _streamActive = false;
3816                         if (_streamThread != null && _streamThread.IsAlive)
3817                         {
3818                             _streamThread.Abort();
3819                         }
3820                     }
3821                 }
3822                 this.disposedValue = true;
3823             }
3824
3825             //protected Overrides void Finalize()
3826             //{
3827             //    // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3828             //    Dispose(false)
3829             //    MyBase.Finalize()
3830             //}
3831
3832             // このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
3833             public void Dispose()
3834             {
3835                 // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3836                 Dispose(true);
3837                 GC.SuppressFinalize(this);
3838             }
3839 #endregion
3840
3841         }
3842 #endregion
3843
3844 #region "IDisposable Support"
3845         private bool disposedValue; // 重複する呼び出しを検出するには
3846
3847         // IDisposable
3848         protected virtual void Dispose(bool disposing)
3849         {
3850             if (!this.disposedValue)
3851             {
3852                 if (disposing)
3853                 {
3854                     this.StopUserStream();
3855                 }
3856             }
3857             this.disposedValue = true;
3858         }
3859
3860         //protected Overrides void Finalize()
3861         //{
3862         //    // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3863         //    Dispose(false)
3864         //    MyBase.Finalize()
3865         //}
3866
3867         // このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
3868         public void Dispose()
3869         {
3870             // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
3871             Dispose(true);
3872             GC.SuppressFinalize(this);
3873         }
3874 #endregion
3875     }
3876 }