OSDN Git Service

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