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.
11 // This file is part of OpenTween.
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
23 // You should have received a copy of the GNU General public License along
24 // with this program. if not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
29 using System.Collections.Generic;
33 using System.Windows.Forms;
35 using System.Globalization;
36 using System.Security.Cryptography;
38 using System.Drawing.Imaging;
39 using System.Collections;
40 using System.Security.Principal;
41 using System.Runtime.Serialization.Json;
42 using System.Reflection;
43 using System.Diagnostics;
44 using System.Text.RegularExpressions;
46 using System.Net.Http;
47 using System.Net.NetworkInformation;
48 using System.Runtime.InteropServices;
53 public sealed class MyCommon
55 private static readonly object LockObj = new object();
56 public static bool _endingFlag; //終了フラグ
57 public static string cultureStr = null;
58 public static string settingPath;
69 public enum NameBalloonEnum
76 public enum DispTitleEnum
88 public enum LogUnitEnum
95 public enum UploadFileType
102 public enum UrlConverter
116 public enum HITRESULT
125 public enum HttpTimeOut
132 //Backgroundworkerへ処理種別を通知するための引数用enum
133 public enum WORKERTYPE
137 DirectMessegeRcv, //受信DM取得
138 DirectMessegeSnt, //送信DM取得
139 PostMessage, //発言POST
142 Follower, //Followerリスト取得
148 UserStream, //UserStream
149 UserTimeline, //UserTimeline
150 BlockIds, //Blocking/ids
151 Configuration, //Twitter Configuration読み込み
152 NoRetweetIds, //RT非表示ユーザー取得
154 ErrorState, //エラー表示のみで後処理終了(認証エラー時など)
157 public static class DEFAULTTAB
159 public const string RECENT = "Recent";
160 public const string REPLY = "Reply";
161 public const string DM = "Direct";
162 public const string FAV = "Favorites";
164 //private string dummy;
166 //private object ReferenceEquals()
168 // return new object();
170 //private object Equals()
172 // return new object();
176 public static readonly object Block = null;
177 public static bool TraceFlag = false;
180 public static bool DebugBuild = true;
182 public static bool DebugBuild = false;
185 public enum ACCOUNT_STATE
191 public enum REPLY_ICONSTATE
199 public enum EVENTTYPE
206 ListMemberRemoved = 16,
214 ListUserSubscribed = 4096,
215 ListUserUnsubscribed = 8192,
216 ListDestroyed = 16384,
218 All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
219 Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
220 ListUserSubscribed | ListUserUnsubscribed | ListDestroyed),
223 public static string GetErrorLogPath()
225 return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
228 public static void TraceOut(WebApiException ex)
230 var message = ExceptionOutMessage(ex);
232 if (ex.ResponseText != null)
233 message += Environment.NewLine + "------- Response Data -------" + Environment.NewLine + ex.ResponseText;
235 TraceOut(TraceFlag, message);
238 public static void TraceOut(Exception ex, string Message)
240 var buf = ExceptionOutMessage(ex);
241 TraceOut(TraceFlag, Message + Environment.NewLine + buf);
244 public static void TraceOut(string Message)
246 TraceOut(TraceFlag, Message);
249 public static void TraceOut(bool OutputFlag, string Message)
253 if (!OutputFlag) return;
255 var logPath = MyCommon.GetErrorLogPath();
256 if (!Directory.Exists(logPath))
257 Directory.CreateDirectory(logPath);
259 var now = DateTime.Now;
260 var fileName = string.Format("{0}Trace-{1:0000}{2:00}{3:00}-{4:00}{5:00}{6:00}.log", GetAssemblyName(), now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
261 fileName = Path.Combine(logPath, fileName);
263 using (var writer = new StreamWriter(fileName))
265 writer.WriteLine("**** TraceOut: {0} ****", DateTime.Now.ToString());
266 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
267 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
269 writer.WriteLine(Properties.Resources.TraceOutText3);
270 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
271 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
272 writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), fileVersion);
273 writer.WriteLine(Message);
280 // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
282 // Dataプロパティにある終了許可フラグのパースもここで行う
284 public static string ExceptionOutMessage(Exception ex)
286 bool IsTerminatePermission = true;
287 return ExceptionOutMessage(ex, ref IsTerminatePermission);
290 public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
292 if (ex == null) return "";
294 var buf = new StringBuilder();
296 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
300 var needHeader = true;
301 foreach (DictionaryEntry dt in ex.Data)
306 buf.AppendLine("-------Extra Information-------");
309 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
311 if (dt.Key.Equals("IsTerminatePermission"))
313 IsTerminatePermission = (bool)dt.Value;
318 buf.AppendLine("-----End Extra Information-----");
321 buf.AppendLine(ex.StackTrace);
324 //InnerExceptionが存在する場合書き出す
325 var _ex = ex.InnerException;
329 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
331 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
333 if (_ex.Data != null)
335 var needHeader = true;
337 foreach (DictionaryEntry dt in _ex.Data)
342 buf.AppendLine("-------Extra Information-------");
345 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
346 if (dt.Key.Equals("IsTerminatePermission"))
348 IsTerminatePermission = (bool)dt.Value;
353 buf.AppendLine("-----End Extra Information-----");
356 buf.AppendLine(_ex.StackTrace);
359 _ex = _ex.InnerException;
361 return buf.ToString();
364 public static bool ExceptionOut(Exception ex)
368 var IsTerminatePermission = true;
370 var logPath = MyCommon.GetErrorLogPath();
371 if (!Directory.Exists(logPath))
372 Directory.CreateDirectory(logPath);
374 var now = DateTime.Now;
375 var fileName = string.Format("{0}-{1:0000}{2:00}{3:00}-{4:00}{5:00}{6:00}.log", GetAssemblyName(), now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
376 fileName = Path.Combine(logPath, fileName);
378 using (var writer = new StreamWriter(fileName))
380 var ident = WindowsIdentity.GetCurrent();
381 var princ = new WindowsPrincipal(ident);
383 writer.WriteLine(Properties.Resources.UnhandledExceptionText1, DateTime.Now.ToString());
384 writer.WriteLine(Properties.Resources.UnhandledExceptionText2, ApplicationSettings.FeedbackEmailAddress);
385 writer.WriteLine(Properties.Resources.UnhandledExceptionText3, ApplicationSettings.FeedbackTwitterName);
387 writer.WriteLine(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator).ToString());
388 writer.WriteLine(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User).ToString());
390 // OSVersion,AppVersion書き出し
391 writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
392 writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
393 writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
394 writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), fileVersion);
396 writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
400 switch (MessageBox.Show(MyCommon.ReplaceAppName(string.Format(Properties.Resources.UnhandledExceptionText9, fileName, ApplicationSettings.FeedbackEmailAddress, ApplicationSettings.FeedbackTwitterName, Environment.NewLine)),
401 Properties.Resources.UnhandledExceptionText10, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error))
403 case DialogResult.Yes:
404 Process.Start(fileName);
406 case DialogResult.No:
408 case DialogResult.Cancel:
409 return IsTerminatePermission;
411 throw new Exception("");
417 /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
419 /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
422 /// <param name="_input">エンコード対象のURL</param>
423 /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
425 public static string urlEncodeMultibyteChar(string _input)
428 var sb = new StringBuilder(256);
431 foreach (var c in _input)
434 if (Convert.ToInt32(c) > 127 || c == '%') break;
436 if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
438 var input = Uri.UnescapeDataString(_input);
440 foreach (char c in input)
442 if (Convert.ToInt32(c) > 255)
444 // Unicodeの場合(1charが複数のバイトで構成されている)
445 // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
446 foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
448 sb.AppendFormat("%{0:X2}", b);
451 else if (Convert.ToInt32(c) > 127 || c == '%')
454 // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
457 uri = new Uri(input);
458 input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
464 sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpper());
475 result = sb.ToString();
479 result = uri.GetLeftPart(UriPartial.Authority) + sb.ToString();
486 /// URLのドメイン名をPunycode展開します。
488 /// ドメイン名がIDNでない場合はそのまま返します。
489 /// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
492 /// <param name="input">展開対象のURL</param>
493 /// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
494 public static string IDNEncode(string inputUrl)
498 var uriBuilder = new UriBuilder(inputUrl);
500 var idnConverter = new IdnMapping();
501 uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
503 return uriBuilder.Uri.ToString();
511 public static string IDNDecode(string inputUrl)
515 var uriBuilder = new UriBuilder(inputUrl);
517 if (uriBuilder.Host != null)
519 var idnConverter = new IdnMapping();
520 uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
523 return uriBuilder.Uri.ToString();
532 /// URL を画面上で人間に読みやすい文字列に変換する(エスケープ解除など)
534 public static string ConvertToReadableUrl(string inputUrl)
538 var outputUrl = inputUrl;
541 outputUrl = MyCommon.IDNDecode(outputUrl);
542 if (outputUrl == null)
545 // URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
546 // 参考: Firefoxの losslessDecodeURI() 関数
547 // http://hg.mozilla.org/mozilla-central/annotate/FIREFOX_AURORA_27_BASE/browser/base/content/browser.js#l2128
548 outputUrl = Regex.Replace(outputUrl, @"%(2[3456BCF]|3[ABDF]|40)", @"%25$1", RegexOptions.IgnoreCase);
551 outputUrl = Uri.UnescapeDataString(outputUrl);
555 catch (UriFormatException)
561 public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
563 var moved_value = values[idx_fr];
564 var num_moved = Math.Abs(idx_fr - idx_to);
568 Array.Copy(values, idx_to, values,
569 idx_to + 1, num_moved);
573 Array.Copy(values, idx_fr + 1, values,
577 values[idx_to] = moved_value;
580 public static string EncryptString(string str)
582 if (string.IsNullOrEmpty(str)) return "";
585 var bytesIn = Encoding.UTF8.GetBytes(str);
587 //DESCryptoServiceProviderオブジェクトの作成
588 using (var des = new DESCryptoServiceProvider())
592 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
594 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
595 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
597 MemoryStream msOut = null;
598 ICryptoTransform desdecrypt = null;
602 //暗号化されたデータを書き出すためのMemoryStream
603 msOut = new MemoryStream();
606 desdecrypt = des.CreateEncryptor();
608 //書き込むためのCryptoStreamの作成
609 using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
611 //Disposeが重複して呼ばれないようにする
612 MemoryStream msTmp = msOut;
617 cryptStream.Write(bytesIn, 0, bytesIn.Length);
618 cryptStream.FlushFinalBlock();
620 var bytesOut = msTmp.ToArray();
622 //Base64で文字列に変更して結果を返す
623 return Convert.ToBase64String(bytesOut);
628 if (msOut != null) msOut.Dispose();
629 if (desdecrypt != null) desdecrypt.Dispose();
634 public static string DecryptString(string str)
636 if (string.IsNullOrEmpty(str)) return "";
638 //DESCryptoServiceProviderオブジェクトの作成
639 using (var des = new System.Security.Cryptography.DESCryptoServiceProvider())
643 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
645 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
646 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
648 //Base64で文字列をバイト配列に戻す
649 var bytesIn = Convert.FromBase64String(str);
651 MemoryStream msIn = null;
652 ICryptoTransform desdecrypt = null;
653 CryptoStream cryptStreem = null;
657 //暗号化されたデータを読み込むためのMemoryStream
658 msIn = new MemoryStream(bytesIn);
660 desdecrypt = des.CreateDecryptor();
661 //読み込むためのCryptoStreamの作成
662 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
664 //Disposeが重複して呼ばれないようにする
668 //復号化されたデータを取得するためのStreamReader
669 using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
671 //Disposeが重複して呼ばれないようにする
675 var result = srOut.ReadToEnd();
682 if (msIn != null) msIn.Dispose();
683 if (desdecrypt != null) desdecrypt.Dispose();
684 if (cryptStreem != null) cryptStreem.Dispose();
689 public static byte[] ResizeBytesArray(byte[] bytes,
692 var newBytes = new byte[newSize];
693 if (bytes.Length <= newSize)
695 foreach (var i in Enumerable.Range(0, bytes.Length))
697 newBytes[i] = bytes[i];
703 foreach (var i in Enumerable.Range(0, bytes.Length))
705 newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
707 if (pos >= newBytes.Length)
716 public static bool IsNT6()
719 return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6;
723 public enum TabUsageType
727 Mentions = 2, //Unique
728 DirectMessage = 4, //Unique
729 Favorites = 8, //Unique
731 LocalQuery = 32, //Pin(no save/no save query/distribute/no update(normal update))
732 Profile = 64, //Pin(save/no distribute/manual update)
733 PublicSearch = 128, //Pin(save/no distribute/auto update)
742 public static string fileVersion = "";
744 public static string GetUserAgentString(bool fakeMSIE = false)
746 if (string.IsNullOrEmpty(fileVersion))
748 throw new Exception("fileversion is not Initialized.");
752 return GetAssemblyName() + "/" + fileVersion + " (compatible; MSIE 10.0)";
754 return GetAssemblyName() + "/" + fileVersion;
757 public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
759 public static bool IsAnimatedGif(string filename)
764 img = Image.FromFile(filename);
765 if (img == null) return false;
766 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
768 var fd = new FrameDimension(img.FrameDimensionsList[0]);
769 var fd_count = img.GetFrameCount(fd);
787 if (img != null) img.Dispose();
791 public static DateTime DateTimeParse(string input)
795 "ddd MMM dd HH:mm:ss zzzz yyyy",
796 "ddd, d MMM yyyy HH:mm:ss zzzz",
798 foreach (var fmt in format)
800 if (DateTime.TryParseExact(input,
802 DateTimeFormatInfo.InvariantInfo,
813 TraceOut("Parse Error(DateTimeFormat) : " + input);
814 return new DateTime();
817 public static T CreateDataFromJson<T>(string content)
820 var buf = Encoding.Unicode.GetBytes(content);
821 using (var stream = new MemoryStream(buf))
823 data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
828 public static bool IsNetworkAvailable()
832 return NetworkInterface.GetIsNetworkAvailable();
840 public static bool IsValidEmail(string strIn)
842 // Return true if strIn is in valid e-mail format.
843 return Regex.IsMatch(strIn,
844 @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
845 @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
849 /// 指定された修飾キーが押されている状態かを取得します。
851 /// <param name="keys">状態を調べるキー</param>
852 /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
853 public static bool IsKeyDown(params Keys[] keys)
855 return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
858 internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
860 foreach (Keys key in targetKeys)
862 if ((modifierKeys & key) != key)
871 /// アプリケーションのアセンブリ名を取得します。
874 /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
876 /// <returns>アプリケーションのアセンブリ名</returns>
877 public static string GetAssemblyName()
879 return MyCommon.EntryAssembly.GetName().Name;
882 internal static _Assembly EntryAssembly = Assembly.GetEntryAssembly();
885 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
887 /// <param name="orig">対象となる文字列</param>
888 /// <returns>置換後の文字列</returns>
889 public static string ReplaceAppName(string orig)
891 return MyCommon.ReplaceAppName(orig, Application.ProductName);
895 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
897 /// <param name="orig">対象となる文字列</param>
898 /// <param name="appname">アプリケーション名</param>
899 /// <returns>置換後の文字列</returns>
900 public static string ReplaceAppName(string orig, string appname)
902 return orig.Replace("%AppName%", appname);
906 /// 表示用のバージョン番号の文字列を生成する
909 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
914 public static string GetReadableVersion(string versionStr = null)
916 if (versionStr == null)
918 if (MyCommon.fileVersion == null)
921 versionStr = MyCommon.fileVersion;
924 return GetReadableVersion(Version.Parse(versionStr));
928 /// 表示用のバージョン番号の文字列を生成する
931 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
936 public static string GetReadableVersion(Version version)
938 var versionNum = new[] { version.Major, version.Minor, version.Build, version.Revision };
940 if (versionNum[3] == 0)
942 return string.Format("{0}.{1}.{2}", versionNum[0], versionNum[1], versionNum[2]);
946 versionNum[2] = versionNum[2] + 1;
949 if (versionNum[2] >= 10)
951 versionNum[1] += versionNum[2] / 10;
954 if (versionNum[1] >= 10)
956 versionNum[0] += versionNum[1] / 10;
961 return string.Format("{0}.{1}.{2}-beta{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
965 public const string TwitterUrl = "https://twitter.com/";
967 public static string GetStatusUrl(PostClass post)
969 if (post.RetweetedId == null)
970 return GetStatusUrl(post.ScreenName, post.StatusId);
972 return GetStatusUrl(post.ScreenName, post.RetweetedId.Value);
975 public static string GetStatusUrl(string screenName, long statusId)
977 return TwitterUrl + screenName + "/status/" + statusId.ToString();
981 /// OpenTween 内で共通して使う設定を施した HttpClient インスタンスを作成します
983 public static HttpClient CreateHttpClient()
985 return CreateHttpClient(new HttpClientHandler());
989 /// OpenTween 内で共通して使う設定を施した HttpClient インスタンスを作成します
991 public static HttpClient CreateHttpClient(HttpClientHandler handler)
993 switch (HttpConnection.proxyKind)
995 case HttpConnection.ProxyType.None:
996 handler.UseProxy = false;
998 case HttpConnection.ProxyType.Specified:
999 handler.UseProxy = true;
1000 handler.Proxy = HttpConnection.proxy;
1002 case HttpConnection.ProxyType.IE:
1004 handler.UseProxy = true;
1005 handler.Proxy = WebRequest.GetSystemWebProxy();
1009 var client = new HttpClient(handler);
1010 client.DefaultRequestHeaders.Add("User-Agent", MyCommon.GetUserAgentString());