OSDN Git Service

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