OSDN Git Service

982d67cc2788b1ef3ece5e2699f5a697d89191b2
[opentween/open-tween.git] / OpenTween / MyCommon.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) 2011      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 #nullable enable
29
30 using System;
31 using System.Collections.Generic;
32 using System.Linq;
33 using System.Text;
34 using System.IO;
35 using System.Windows.Forms;
36 using System.Web;
37 using System.Globalization;
38 using System.Security.Cryptography;
39 using System.Drawing;
40 using System.Drawing.Imaging;
41 using System.Collections;
42 using System.Security.Principal;
43 using System.Runtime.Serialization.Json;
44 using System.Reflection;
45 using System.Diagnostics;
46 using System.Text.RegularExpressions;
47 using System.Net;
48 using System.Net.Http;
49 using System.Net.NetworkInformation;
50 using System.Runtime.InteropServices;
51 using OpenTween.Api;
52 using OpenTween.Models;
53 using OpenTween.Setting;
54
55 namespace OpenTween
56 {
57     public static class MyCommon
58     {
59         private static readonly object LockObj = new object();
60         public static bool _endingFlag;        //終了フラグ
61         public static string settingPath;
62
63         public enum IconSizes
64         {
65             IconNone = 0,
66             Icon16 = 1,
67             Icon24 = 2,
68             Icon48 = 3,
69             Icon48_2 = 4,
70         }
71
72         public enum NameBalloonEnum
73         {
74             None,
75             UserID,
76             NickName,
77         }
78
79         public enum DispTitleEnum
80         {
81             None,
82             Ver,
83             Post,
84             UnreadRepCount,
85             UnreadAllCount,
86             UnreadAllRepCount,
87             UnreadCountAllCount,
88             OwnStatus,
89         }
90
91         public enum LogUnitEnum
92         {
93             Minute,
94             Hour,
95             Day,
96         }
97
98         public enum UploadFileType
99         {
100             Invalid,
101             Picture,
102             MultiMedia,
103         }
104
105         public enum UrlConverter
106         {
107             TinyUrl,
108             Isgd,
109             Bitly,
110             Jmp,
111             Uxnu,
112             //特殊
113             Nicoms,
114             //廃止
115             Unu = -1,
116             Twurl = -1,
117         }
118
119         public enum HITRESULT
120         {
121             None,
122             Copy,
123             CopyAndMark,
124             Move,
125             Exclude,
126         }
127
128         public enum HttpTimeOut
129         {
130             MinValue = 10,
131             MaxValue = 1000,
132             DefaultValue = 20,
133         }
134
135         //Backgroundworkerへ処理種別を通知するための引数用enum
136         public enum WORKERTYPE
137         {
138             Timeline,                //タイムライン取得
139             Reply,                   //返信取得
140             DirectMessegeRcv,        //受信DM取得
141             DirectMessegeSnt,        //送信DM取得
142             PostMessage,             //発言POST
143             FavAdd,                  //Fav追加
144             FavRemove,               //Fav削除
145             Follower,                //Followerリスト取得
146             Favorites,               //Fav取得
147             Retweet,                 //Retweetする
148             PublicSearch,            //公式検索
149             List,                    //Lists
150             Related,                 //関連発言
151             UserStream,              //UserStream
152             UserTimeline,            //UserTimeline
153             BlockIds,                //Blocking/ids
154             Configuration,           //Twitter Configuration読み込み
155             NoRetweetIds,            //RT非表示ユーザー取得
156             //////
157             ErrorState,              //エラー表示のみで後処理終了(認証エラー時など)
158         }
159
160         public static class DEFAULTTAB
161         {
162             public const string RECENT = "Recent";
163             public const string REPLY = "Reply";
164             public const string DM = "Direct";
165             public const string FAV = "Favorites";
166             public static readonly string MUTE = Properties.Resources.MuteTabName;
167
168             //private string dummy;
169
170             //private object ReferenceEquals()
171             //{
172             //    return new object();
173             //}
174             //private object Equals()
175             //{
176             //    return new object();
177             //}
178         }
179
180         public static readonly object? Block = null;
181         public static bool TraceFlag = false;
182
183 #if DEBUG
184         public static bool DebugBuild = true;
185 #else
186         public static bool DebugBuild = false;
187 #endif
188
189         public enum ACCOUNT_STATE
190         {
191             Valid,
192             Invalid,
193         }
194
195         public enum REPLY_ICONSTATE
196         {
197             None,
198             StaticIcon,
199             BlinkIcon,
200         }
201
202         [Flags]
203         public enum EVENTTYPE
204         {
205             None = 0,
206             Favorite = 1,
207             Unfavorite = 2,
208             Follow = 4,
209             ListMemberAdded = 8,
210             ListMemberRemoved = 16,
211             Block = 32,
212             Unblock = 64,
213             UserUpdate = 128,
214             Deleted = 256,
215             ListCreated = 512,
216             ListUpdated = 1024,
217             Unfollow = 2048,
218             ListUserSubscribed = 4096,
219             ListUserUnsubscribed = 8192,
220             ListDestroyed = 16384,
221             Mute = 32768,
222             Unmute = 65536,
223             QuotedTweet = 131072,
224             Retweet = 262144,
225
226             All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
227                    Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
228                    ListUserSubscribed | ListUserUnsubscribed | ListDestroyed |
229                    Mute | Unmute | QuotedTweet | Retweet),
230         }
231
232         public static _Assembly EntryAssembly { get; internal set; }
233         public static string FileVersion { get; internal set; }
234
235         static MyCommon()
236         {
237             var assembly = Assembly.GetExecutingAssembly();
238             MyCommon.EntryAssembly = assembly;
239
240             var fileVersionAttribute = (AssemblyFileVersionAttribute)assembly
241                 .GetCustomAttributes(typeof(AssemblyFileVersionAttribute)).First();
242             MyCommon.FileVersion = fileVersionAttribute.Version;
243         }
244
245         public static string GetErrorLogPath()
246             => Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
247
248         public static void TraceOut(WebApiException ex)
249         {
250             var message = ExceptionOutMessage(ex);
251
252             if (ex.ResponseText != null)
253                 message += Environment.NewLine + "------- Response Data -------" + Environment.NewLine + ex.ResponseText;
254
255             TraceOut(TraceFlag, message);
256         }
257
258         public static void TraceOut(Exception ex, string Message)
259         {
260             var buf = ExceptionOutMessage(ex);
261             TraceOut(TraceFlag, Message + Environment.NewLine + buf);
262         }
263
264         public static void TraceOut(string Message)
265             => TraceOut(TraceFlag, Message);
266
267         public static void TraceOut(bool OutputFlag, string Message)
268         {
269             lock (LockObj)
270             {
271                 if (!OutputFlag) return;
272
273                 var logPath = MyCommon.GetErrorLogPath();
274                 if (!Directory.Exists(logPath))
275                     Directory.CreateDirectory(logPath);
276
277                 var now = DateTimeUtc.Now;
278                 var fileName = $"{ApplicationSettings.AssemblyName}Trace-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
279                 fileName = Path.Combine(logPath, fileName);
280
281                 using var writer = new StreamWriter(fileName);
282
283                 writer.WriteLine("**** TraceOut: {0} ****", now.ToLocalTimeString());
284                 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
285                 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
286                 writer.WriteLine();
287                 writer.WriteLine(Properties.Resources.TraceOutText3);
288                 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
289                 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version);
290                 writer.WriteLine(Properties.Resources.TraceOutText6, ApplicationSettings.AssemblyName, FileVersion);
291                 writer.WriteLine(Message);
292                 writer.WriteLine();
293             }
294         }
295
296         // エラー内容をバッファに書き出し
297         // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
298         // 文頭メッセージ、権限、動作環境
299         // Dataプロパティにある終了許可フラグのパースもここで行う
300
301         public static string ExceptionOutMessage(Exception ex)
302         {
303             var IsTerminatePermission = true;
304             return ExceptionOutMessage(ex, ref IsTerminatePermission);
305         }
306
307         public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
308         {
309             if (ex == null) return "";
310
311             var buf = new StringBuilder();
312
313             buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
314             buf.AppendLine();
315             if (ex.Data != null)
316             {
317                 var needHeader = true;
318                 foreach (DictionaryEntry dt in ex.Data)
319                 {
320                     if (needHeader)
321                     {
322                         buf.AppendLine();
323                         buf.AppendLine("-------Extra Information-------");
324                         needHeader = false;
325                     }
326                     buf.AppendFormat("{0}  :  {1}", dt.Key, dt.Value);
327                     buf.AppendLine();
328                     if (dt.Key.Equals("IsTerminatePermission"))
329                     {
330                         IsTerminatePermission = (bool)dt.Value;
331                     }
332                 }
333                 if (!needHeader)
334                 {
335                     buf.AppendLine("-----End Extra Information-----");
336                 }
337             }
338             buf.AppendLine(ex.StackTrace);
339             buf.AppendLine();
340
341             //InnerExceptionが存在する場合書き出す
342             var _ex = ex.InnerException;
343             var nesting = 0;
344             while (_ex != null)
345             {
346                 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
347                 buf.AppendLine();
348                 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
349                 buf.AppendLine();
350                 if (_ex.Data != null)
351                 {
352                     var needHeader = true;
353
354                     foreach (DictionaryEntry dt in _ex.Data)
355                     {
356                         if (needHeader)
357                         {
358                             buf.AppendLine();
359                             buf.AppendLine("-------Extra Information-------");
360                             needHeader = false;
361                         }
362                         buf.AppendFormat("{0}  :  {1}", dt.Key, dt.Value);
363                         if (dt.Key.Equals("IsTerminatePermission"))
364                         {
365                             IsTerminatePermission = (bool)dt.Value;
366                         }
367                     }
368                     if (!needHeader)
369                     {
370                         buf.AppendLine("-----End Extra Information-----");
371                     }
372                 }
373                 buf.AppendLine(_ex.StackTrace);
374                 buf.AppendLine();
375                 nesting++;
376                 _ex = _ex.InnerException;
377             }
378             return buf.ToString();
379         }
380
381         public static bool ExceptionOut(Exception ex)
382         {
383             lock (LockObj)
384             {
385                 var IsTerminatePermission = true;
386
387                 var ident = WindowsIdentity.GetCurrent();
388                 var princ = new WindowsPrincipal(ident);
389                 var now = DateTimeUtc.Now;
390
391                 var errorReport = string.Join(Environment.NewLine,
392                     string.Format(Properties.Resources.UnhandledExceptionText1, now.ToLocalTimeString()),
393
394                     // 権限書き出し
395                     string.Format(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator)),
396                     string.Format(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User)),
397                     "",
398
399                     // OSVersion,AppVersion書き出し
400                     string.Format(Properties.Resources.UnhandledExceptionText4),
401                     string.Format(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString),
402                     string.Format(Properties.Resources.UnhandledExceptionText6, Environment.Version),
403                     string.Format(Properties.Resources.UnhandledExceptionText7, ApplicationSettings.AssemblyName, FileVersion),
404
405                     ExceptionOutMessage(ex, ref IsTerminatePermission));
406
407                 var logPath = MyCommon.GetErrorLogPath();
408                 if (!Directory.Exists(logPath))
409                     Directory.CreateDirectory(logPath);
410
411                 var fileName = $"{ApplicationSettings.AssemblyName}-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
412                 using (var writer = new StreamWriter(Path.Combine(logPath, fileName)))
413                 {
414                     writer.Write(errorReport);
415                 }
416
417                 var settings = SettingManager.Common;
418                 var mainForm = Application.OpenForms.OfType<TweenMain>().FirstOrDefault();
419
420                 ErrorReport report;
421                 if (mainForm != null && !mainForm.IsDisposed)
422                     report = new ErrorReport(mainForm.TwitterInstance, errorReport);
423                 else
424                     report = new ErrorReport(errorReport);
425
426                 report.AnonymousReport = settings.ErrorReportAnonymous;
427
428                 OpenErrorReportDialog(mainForm, report);
429
430                 // ダイアログ内で設定が変更されていれば保存する
431                 if (settings.ErrorReportAnonymous != report.AnonymousReport)
432                 {
433                     settings.ErrorReportAnonymous = report.AnonymousReport;
434                     settings.Save();
435                 }
436
437                 return false;
438             }
439         }
440
441         private static void OpenErrorReportDialog(Form? owner, ErrorReport report)
442         {
443             if (owner != null && owner.InvokeRequired)
444             {
445                 owner.Invoke((Action)(() => OpenErrorReportDialog(owner, report)));
446                 return;
447             }
448
449             using var dialog = new SendErrorReportForm();
450             dialog.ErrorReport = report;
451             dialog.ShowDialog(owner);
452         }
453
454         /// <summary>
455         /// URLのドメイン名をPunycode展開します。
456         /// <para>
457         /// ドメイン名がIDNでない場合はそのまま返します。
458         /// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
459         /// </para>
460         /// </summary>
461         /// <param name="inputUrl">展開対象のURL</param>
462         /// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
463         public static string? IDNEncode(string inputUrl)
464         {
465             try
466             {
467                 var uriBuilder = new UriBuilder(inputUrl);
468
469                 var idnConverter = new IdnMapping();
470                 uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
471
472                 return uriBuilder.Uri.AbsoluteUri;
473             }
474             catch (Exception)
475             {
476                 return null;
477             }
478         }
479
480         public static string IDNDecode(string inputUrl)
481         {
482             try
483             {
484                 var uriBuilder = new UriBuilder(inputUrl);
485
486                 if (uriBuilder.Host != null)
487                 {
488                     var idnConverter = new IdnMapping();
489                     uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
490                 }
491
492                 return uriBuilder.Uri.AbsoluteUri;
493             }
494             catch (Exception)
495             {
496                 return inputUrl;
497             }
498         }
499
500         /// <summary>
501         /// URL を画面上で人間に読みやすい文字列に変換する(エスケープ解除など)
502         /// </summary>
503         public static string ConvertToReadableUrl(string inputUrl)
504         {
505             try
506             {
507                 var outputUrl = inputUrl;
508
509                 // Punycodeをデコードする
510                 outputUrl = MyCommon.IDNDecode(outputUrl);
511
512                 // URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
513                 // 参考: Firefoxの losslessDecodeURI() 関数
514                 //   http://hg.mozilla.org/mozilla-central/annotate/FIREFOX_AURORA_27_BASE/browser/base/content/browser.js#l2128
515                 outputUrl = Regex.Replace(outputUrl, @"%(2[3456BCF]|3[ABDF]|40)", @"%25$1", RegexOptions.IgnoreCase);
516
517                 // エスケープを解除する
518                 outputUrl = Uri.UnescapeDataString(outputUrl);
519
520                 return outputUrl;
521             }
522             catch (UriFormatException)
523             {
524                 return inputUrl;
525             }
526         }
527
528         public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
529         {
530             var moved_value = values[idx_fr];
531             var num_moved = Math.Abs(idx_fr - idx_to);
532
533             if (idx_to < idx_fr)
534             {
535                 Array.Copy(values, idx_to, values,
536                     idx_to + 1, num_moved);
537             }
538             else
539             {
540                 Array.Copy(values, idx_fr + 1, values,
541                     idx_fr, num_moved);
542             }
543
544             values[idx_to] = moved_value;
545         }
546
547         public static string EncryptString(string str)
548         {
549             if (string.IsNullOrEmpty(str)) return "";
550
551             //文字列をバイト型配列にする
552             var bytesIn = Encoding.UTF8.GetBytes(str);
553
554             //DESCryptoServiceProviderオブジェクトの作成
555             using var des = new DESCryptoServiceProvider();
556
557             //共有キーと初期化ベクタを決定
558             //パスワードをバイト配列にする
559             var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
560             //共有キーと初期化ベクタを設定
561             des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
562             des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
563
564             MemoryStream? msOut = null;
565             ICryptoTransform? desdecrypt = null;
566
567             try
568             {
569                 //暗号化されたデータを書き出すためのMemoryStream
570                 msOut = new MemoryStream();
571
572                 //DES暗号化オブジェクトの作成
573                 desdecrypt = des.CreateEncryptor();
574
575                 //書き込むためのCryptoStreamの作成
576                 using var cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write);
577
578                 //Disposeが重複して呼ばれないようにする
579                 var msTmp = msOut;
580                 msOut = null;
581                 desdecrypt = null;
582
583                 //書き込む
584                 cryptStream.Write(bytesIn, 0, bytesIn.Length);
585                 cryptStream.FlushFinalBlock();
586                 //暗号化されたデータを取得
587                 var bytesOut = msTmp.ToArray();
588
589                 //Base64で文字列に変更して結果を返す
590                 return Convert.ToBase64String(bytesOut);
591             }
592             finally
593             {
594                 msOut?.Dispose();
595                 desdecrypt?.Dispose();
596             }
597         }
598
599         public static string DecryptString(string str)
600         {
601             if (string.IsNullOrEmpty(str)) return "";
602
603             //DESCryptoServiceProviderオブジェクトの作成
604             using var des = new DESCryptoServiceProvider();
605
606             //共有キーと初期化ベクタを決定
607             //パスワードをバイト配列にする
608             var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
609             //共有キーと初期化ベクタを設定
610             des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
611             des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
612
613             //Base64で文字列をバイト配列に戻す
614             var bytesIn = Convert.FromBase64String(str);
615
616             MemoryStream? msIn = null;
617             ICryptoTransform? desdecrypt = null;
618             CryptoStream? cryptStreem = null;
619
620             try
621             {
622                 //暗号化されたデータを読み込むためのMemoryStream
623                 msIn = new MemoryStream(bytesIn);
624                 //DES復号化オブジェクトの作成
625                 desdecrypt = des.CreateDecryptor();
626                 //読み込むためのCryptoStreamの作成
627                 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
628
629                 //Disposeが重複して呼ばれないようにする
630                 msIn = null;
631                 desdecrypt = null;
632
633                 //復号化されたデータを取得するためのStreamReader
634                 using var srOut = new StreamReader(cryptStreem, Encoding.UTF8);
635
636                 //Disposeが重複して呼ばれないようにする
637                 cryptStreem = null;
638
639                 //復号化されたデータを取得する
640                 var result = srOut.ReadToEnd();
641
642                 return result;
643             }
644             finally
645             {
646                 msIn?.Dispose();
647                 desdecrypt?.Dispose();
648                 cryptStreem?.Dispose();
649             }
650         }
651
652         public static byte[] ResizeBytesArray(byte[] bytes,
653                                     int newSize)
654         {
655             var newBytes = new byte[newSize];
656             if (bytes.Length <= newSize)
657             {
658                 foreach (var i in Enumerable.Range(0, bytes.Length))
659                 {
660                     newBytes[i] = bytes[i];
661                 }
662             }
663             else
664             {
665                 var pos = 0;
666                 foreach (var i in Enumerable.Range(0, bytes.Length))
667                 {
668                     newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
669                     pos++;
670                     if (pos >= newBytes.Length)
671                     {
672                         pos = 0;
673                     }
674                 }
675             }
676             return newBytes;
677         }
678
679         [Flags]
680         public enum TabUsageType
681         {
682             Undefined = 0,
683             Home = 1,      //Unique
684             Mentions = 2,     //Unique
685             DirectMessage = 4,   //Unique
686             Favorites = 8,       //Unique
687             UserDefined = 16,
688             LocalQuery = 32,      //Pin(no save/no save query/distribute/no update(normal update))
689             Profile = 64,         //Pin(save/no distribute/manual update)
690             PublicSearch = 128,    //Pin(save/no distribute/auto update)
691             Lists = 256,
692             Related = 512,
693             UserTimeline = 1024,
694             Mute = 2048,
695             SearchResults = 4096,
696             //RTMyTweet
697             //RTByOthers
698             //RTByMe
699         }
700
701         public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
702
703         public static bool IsAnimatedGif(string filename)
704         {
705             Image? img = null;
706             try
707             {
708                 img = Image.FromFile(filename);
709                 if (img == null) return false;
710                 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
711                 {
712                     var fd = new FrameDimension(img.FrameDimensionsList[0]);
713                     var fd_count = img.GetFrameCount(fd);
714                     if (fd_count > 1)
715                     {
716                         return true;
717                     }
718                     else
719                     {
720                         return false;
721                     }
722                 }
723                 return false;
724             }
725             catch (Exception)
726             {
727                 return false;
728             }
729             finally
730             {
731                 img?.Dispose();
732             }
733         }
734
735         public static DateTimeUtc DateTimeParse(string input)
736         {
737             var formats = new[] {
738                 "ddd MMM dd HH:mm:ss zzzz yyyy",
739                 "ddd, d MMM yyyy HH:mm:ss zzzz",
740             };
741
742             if (DateTimeUtc.TryParseExact(input, formats, DateTimeFormatInfo.InvariantInfo, out var result))
743                 return result;
744
745             TraceOut("Parse Error(DateTimeFormat) : " + input);
746
747             return DateTimeUtc.Now;
748         }
749
750         public static T CreateDataFromJson<T>(string content)
751         {
752             var buf = Encoding.Unicode.GetBytes(content);
753             using var stream = new MemoryStream(buf);
754             var settings = new DataContractJsonSerializerSettings
755             {
756                 UseSimpleDictionaryFormat = true,
757             };
758             return (T)((new DataContractJsonSerializer(typeof(T), settings)).ReadObject(stream));
759         }
760
761         public static bool IsNetworkAvailable()
762         {
763             try
764             {
765                 return NetworkInterface.GetIsNetworkAvailable();
766             }
767             catch(Exception)
768             {
769                 return false;
770             }
771         }
772
773         public static bool IsValidEmail(string strIn)
774         {
775             // Return true if strIn is in valid e-mail format.
776             return Regex.IsMatch(strIn,
777                    @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
778                    @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
779         }
780
781         /// <summary>
782         /// 指定された修飾キーが押されている状態かを取得します。
783         /// </summary>
784         /// <param name="keys">状態を調べるキー</param>
785         /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
786         public static bool IsKeyDown(params Keys[] keys)
787             => MyCommon._IsKeyDown(Control.ModifierKeys, keys);
788
789         internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
790         {
791             foreach (var key in targetKeys)
792             {
793                 if ((modifierKeys & key) != key)
794                 {
795                     return false;
796                 }
797             }
798             return true;
799         }
800
801         /// <summary>
802         /// アプリケーションのアセンブリ名を取得します。
803         /// </summary>
804         /// <remarks>
805         /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
806         /// </remarks>
807         /// <returns>アプリケーションのアセンブリ名</returns>
808         public static string GetAssemblyName()
809             => MyCommon.EntryAssembly.GetName().Name;
810
811         /// <summary>
812         /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
813         /// </summary>
814         /// <param name="orig">対象となる文字列</param>
815         /// <returns>置換後の文字列</returns>
816         public static string ReplaceAppName(string orig)
817             => MyCommon.ReplaceAppName(orig, ApplicationSettings.ApplicationName);
818
819         /// <summary>
820         /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
821         /// </summary>
822         /// <param name="orig">対象となる文字列</param>
823         /// <param name="appname">アプリケーション名</param>
824         /// <returns>置換後の文字列</returns>
825         public static string ReplaceAppName(string orig, string appname)
826             => orig.Replace("%AppName%", appname);
827
828         /// <summary>
829         /// 表示用のバージョン番号の文字列を生成する
830         /// </summary>
831         /// <remarks>
832         /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
833         /// </remarks>
834         /// <returns>
835         /// 生成されたバージョン番号の文字列
836         /// </returns>
837         public static string GetReadableVersion(string? versionStr = null)
838         {
839             var version = Version.Parse(versionStr ?? MyCommon.FileVersion);
840
841             return GetReadableVersion(version);
842         }
843
844         /// <summary>
845         /// 表示用のバージョン番号の文字列を生成する
846         /// </summary>
847         /// <remarks>
848         /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-dev」のように出力される
849         /// </remarks>
850         /// <returns>
851         /// 生成されたバージョン番号の文字列
852         /// </returns>
853         public static string GetReadableVersion(Version version)
854         {
855             var versionNum = new[] { version.Major, version.Minor, version.Build, version.Revision };
856
857             if (versionNum[3] == 0)
858             {
859                 return string.Format("{0}.{1}.{2}", versionNum[0], versionNum[1], versionNum[2]);
860             }
861             else
862             {
863                 versionNum[2] = versionNum[2] + 1;
864
865                 if (versionNum[3] == 1)
866                     return string.Format("{0}.{1}.{2}-dev", versionNum[0], versionNum[1], versionNum[2]);
867                 else
868                     return string.Format("{0}.{1}.{2}-dev+build.{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
869             }
870         }
871
872         public const string TwitterUrl = "https://twitter.com/";
873
874         public static string GetStatusUrl(PostClass post)
875         {
876             if (post.RetweetedId == null)
877                 return GetStatusUrl(post.ScreenName, post.StatusId);
878             else
879                 return GetStatusUrl(post.ScreenName, post.RetweetedId.Value);
880         }
881
882         public static string GetStatusUrl(string screenName, long statusId)
883             => TwitterUrl + screenName + "/status/" + statusId;
884
885         /// <summary>
886         /// 指定された IDictionary を元にクエリ文字列を生成します
887         /// </summary>
888         /// <param name="param">生成するクエリの key-value コレクション</param>
889         public static string BuildQueryString(IEnumerable<KeyValuePair<string, string>> param)
890         {
891             if (param == null)
892                 return string.Empty;
893
894             var query = param
895                 .Where(x => x.Value != null)
896                 .Select(x => EscapeQueryString(x.Key) + '=' + EscapeQueryString(x.Value));
897
898             return string.Join("&", query);
899         }
900
901         // .NET 4.5+: Reserved characters のうち、Uriクラスによってエスケープ強制解除されてしまうものも最初から Unreserved として扱う
902         private static readonly HashSet<char> UnreservedChars =
903             new HashSet<char>("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*:");
904
905         /// <summary>
906         /// 2バイト文字も考慮したクエリ用エンコード
907         /// </summary>
908         /// <param name="stringToEncode">エンコードする文字列</param>
909         /// <returns>エンコード結果文字列</returns>
910         public static string EscapeQueryString(string stringToEncode)
911         {
912             var sb = new StringBuilder(stringToEncode.Length * 2);
913
914             foreach (var b in Encoding.UTF8.GetBytes(stringToEncode))
915             {
916                 if (UnreservedChars.Contains((char)b))
917                     sb.Append((char)b);
918                 else
919                     sb.AppendFormat("%{0:X2}", b);
920             }
921
922             return sb.ToString();
923         }
924
925         /// <summary>
926         /// 指定された範囲の整数を昇順に列挙します
927         /// </summary>
928         /// <remarks>
929         /// start, start + 1, start + 2, ..., end の範囲の数列を生成します
930         /// </remarks>
931         /// <param name="from">数列の先頭の値 (最小値)</param>
932         /// <param name="to">数列の末尾の値 (最大値)</param>
933         /// <returns>整数を列挙する IEnumerable インスタンス</returns>
934         public static IEnumerable<int> CountUp(int from, int to)
935         {
936             if (from > to)
937                 return Enumerable.Empty<int>();
938
939             return Enumerable.Range(from, to - from + 1);
940         }
941
942         /// <summary>
943         /// 指定された範囲の整数を降順に列挙します
944         /// </summary>
945         /// <remarks>
946         /// start, start - 1, start - 2, ..., end の範囲の数列を生成します
947         /// </remarks>
948         /// <param name="from">数列の先頭の値 (最大値)</param>
949         /// <param name="to">数列の末尾の値 (最小値)</param>
950         /// <returns>整数を列挙する IEnumerable インスタンス</returns>
951         public static IEnumerable<int> CountDown(int from, int to)
952         {
953             for (var i = from; i >= to; i--)
954                 yield return i;
955         }
956
957         public static IEnumerable<int> CircularCountUp(int length, int startIndex)
958         {
959             if (length < 1)
960                 throw new ArgumentOutOfRangeException(nameof(length));
961             if (startIndex < 0 || startIndex >= length)
962                 throw new ArgumentOutOfRangeException(nameof(startIndex));
963
964             // startindex ... 末尾
965             var indices = MyCommon.CountUp(startIndex, length - 1);
966
967             // 先頭 ... (startIndex - 1)
968             if (startIndex != 0)
969                 indices = indices.Concat(MyCommon.CountUp(0, startIndex - 1));
970
971             return indices;
972         }
973
974         public static IEnumerable<int> CircularCountDown(int length, int startIndex)
975         {
976             if (length < 1)
977                 throw new ArgumentOutOfRangeException(nameof(length));
978             if (startIndex < 0 || startIndex >= length)
979                 throw new ArgumentOutOfRangeException(nameof(startIndex));
980
981             // startIndex ... 先頭
982             var indices = MyCommon.CountDown(startIndex, 0);
983
984             // 末尾 ... (startIndex + 1)
985             if (startIndex != length - 1)
986                 indices = indices.Concat(MyCommon.CountDown(length - 1, startIndex + 1));
987
988             return indices;
989         }
990
991         /// <summary>
992         /// 2バイト文字も考慮したUrlエンコード
993         /// </summary>
994         /// <param name="stringToEncode">エンコードする文字列</param>
995         /// <returns>エンコード結果文字列</returns>
996         public static string UrlEncode(string stringToEncode)
997         {
998             const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
999             var sb = new StringBuilder();
1000             var bytes = Encoding.UTF8.GetBytes(stringToEncode);
1001
1002             foreach (var b in bytes)
1003             {
1004                 if (UnreservedChars.IndexOf((char)b) != -1)
1005                     sb.Append((char)b);
1006                 else
1007                     sb.AppendFormat("%{0:X2}", b);
1008             }
1009             return sb.ToString();
1010         }
1011     }
1012 }