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;
45 using System.Net.NetworkInformation;
46 using System.Runtime.InteropServices;
51 public sealed class MyCommon
53 private static readonly object LockObj = new object();
54 public static bool _endingFlag; //終了フラグ
55 public static string cultureStr = null;
56 public static string settingPath;
67 public enum NameBalloonEnum
74 public enum DispTitleEnum
86 public enum LogUnitEnum
93 public enum UploadFileType
100 public enum UrlConverter
114 public enum OutputzUrlmode
117 twittercomWithUsername,
120 public enum HITRESULT
129 public enum HttpTimeOut
136 //Backgroundworkerへ処理種別を通知するための引数用enum
137 public enum WORKERTYPE
141 DirectMessegeRcv, //受信DM取得
142 DirectMessegeSnt, //送信DM取得
143 PostMessage, //発言POST
146 Follower, //Followerリスト取得
152 UserStream, //UserStream
153 UserTimeline, //UserTimeline
154 BlockIds, //Blocking/ids
155 Configuration, //Twitter Configuration読み込み
156 NoRetweetIds, //RT非表示ユーザー取得
158 ErrorState, //エラー表示のみで後処理終了(認証エラー時など)
161 public static class DEFAULTTAB
163 public const string RECENT = "Recent";
164 public const string REPLY = "Reply";
165 public const string DM = "Direct";
166 public const string FAV = "Favorites";
168 //private string dummy;
170 //private object ReferenceEquals()
172 // return new object();
174 //private object Equals()
176 // return new object();
180 public static readonly object Block = null;
181 public static bool TraceFlag = false;
184 public static bool DebugBuild = true;
186 public static bool DebugBuild = false;
189 public enum ACCOUNT_STATE
195 public enum REPLY_ICONSTATE
203 public enum EVENTTYPE
210 ListMemberRemoved = 16,
218 ListUserSubscribed = 4096,
219 ListUserUnsubscribed = 8192,
221 All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
222 Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
223 ListUserSubscribed | ListUserUnsubscribed),
226 public static string GetErrorLogPath()
228 return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
231 public static void TraceOut(WebApiException ex)
233 var message = ExceptionOutMessage(ex);
235 if (ex.ResponseText != null)
236 message += Environment.NewLine + "------- Response Data -------" + Environment.NewLine + ex.ResponseText;
238 TraceOut(TraceFlag, message);
241 public static void TraceOut(Exception ex, string Message)
243 var buf = ExceptionOutMessage(ex);
244 TraceOut(TraceFlag, Message + Environment.NewLine + buf);
247 public static void TraceOut(string Message)
249 TraceOut(TraceFlag, Message);
252 public static void TraceOut(bool OutputFlag, string Message)
256 if (!OutputFlag) return;
258 var logPath = MyCommon.GetErrorLogPath();
259 if (!Directory.Exists(logPath))
260 Directory.CreateDirectory(logPath);
262 var now = DateTime.Now;
263 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);
264 fileName = Path.Combine(logPath, fileName);
266 using (var writer = new StreamWriter(fileName))
268 writer.WriteLine("**** TraceOut: {0} ****", DateTime.Now.ToString());
269 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
270 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
272 writer.WriteLine(Properties.Resources.TraceOutText3);
273 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
274 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
275 writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), fileVersion);
276 writer.WriteLine(Message);
283 // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
285 // Dataプロパティにある終了許可フラグのパースもここで行う
287 public static string ExceptionOutMessage(Exception ex)
289 bool IsTerminatePermission = true;
290 return ExceptionOutMessage(ex, ref IsTerminatePermission);
293 public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
295 if (ex == null) return "";
297 var buf = new StringBuilder();
299 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
303 var needHeader = true;
304 foreach (DictionaryEntry dt in ex.Data)
309 buf.AppendLine("-------Extra Information-------");
312 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
314 if (dt.Key.Equals("IsTerminatePermission"))
316 IsTerminatePermission = (bool)dt.Value;
321 buf.AppendLine("-----End Extra Information-----");
324 buf.AppendLine(ex.StackTrace);
327 //InnerExceptionが存在する場合書き出す
328 var _ex = ex.InnerException;
332 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
334 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
336 if (_ex.Data != null)
338 var needHeader = true;
340 foreach (DictionaryEntry dt in _ex.Data)
345 buf.AppendLine("-------Extra Information-------");
348 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
349 if (dt.Key.Equals("IsTerminatePermission"))
351 IsTerminatePermission = (bool)dt.Value;
356 buf.AppendLine("-----End Extra Information-----");
359 buf.AppendLine(_ex.StackTrace);
362 _ex = _ex.InnerException;
364 return buf.ToString();
367 public static bool ExceptionOut(Exception ex)
371 var IsTerminatePermission = true;
373 var logPath = MyCommon.GetErrorLogPath();
374 if (!Directory.Exists(logPath))
375 Directory.CreateDirectory(logPath);
377 var now = DateTime.Now;
378 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);
379 fileName = Path.Combine(logPath, fileName);
381 using (var writer = new StreamWriter(fileName))
383 var ident = WindowsIdentity.GetCurrent();
384 var princ = new WindowsPrincipal(ident);
386 writer.WriteLine(Properties.Resources.UnhandledExceptionText1, DateTime.Now.ToString());
387 writer.WriteLine(Properties.Resources.UnhandledExceptionText2, ApplicationSettings.FeedbackEmailAddress);
388 writer.WriteLine(Properties.Resources.UnhandledExceptionText3, ApplicationSettings.FeedbackTwitterName);
390 writer.WriteLine(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator).ToString());
391 writer.WriteLine(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User).ToString());
393 // OSVersion,AppVersion書き出し
394 writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
395 writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
396 writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
397 writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), fileVersion);
399 writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
403 switch (MessageBox.Show(MyCommon.ReplaceAppName(string.Format(Properties.Resources.UnhandledExceptionText9, fileName, ApplicationSettings.FeedbackEmailAddress, ApplicationSettings.FeedbackTwitterName, Environment.NewLine)),
404 Properties.Resources.UnhandledExceptionText10, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error))
406 case DialogResult.Yes:
407 Process.Start(fileName);
409 case DialogResult.No:
411 case DialogResult.Cancel:
412 return IsTerminatePermission;
414 throw new Exception("");
420 /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
422 /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
425 /// <param name="_input">エンコード対象のURL</param>
426 /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
428 public static string urlEncodeMultibyteChar(string _input)
431 var sb = new StringBuilder(256);
434 foreach (var c in _input)
437 if (Convert.ToInt32(c) > 127 || c == '%') break;
439 if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
441 var input = Uri.UnescapeDataString(_input);
443 foreach (char c in input)
445 if (Convert.ToInt32(c) > 255)
447 // Unicodeの場合(1charが複数のバイトで構成されている)
448 // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
449 foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
451 sb.AppendFormat("%{0:X2}", b);
454 else if (Convert.ToInt32(c) > 127 || c == '%')
457 // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
460 uri = new Uri(input);
461 input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
467 sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpper());
478 result = sb.ToString();
482 result = uri.GetLeftPart(UriPartial.Authority) + sb.ToString();
489 /// URLのドメイン名をPunycode展開します。
491 /// ドメイン名がIDNでない場合はそのまま返します。
492 /// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
495 /// <param name="input">展開対象のURL</param>
496 /// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
497 public static string IDNEncode(string inputUrl)
501 var uriBuilder = new UriBuilder(inputUrl);
503 var idnConverter = new IdnMapping();
504 uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
506 return uriBuilder.Uri.ToString();
514 public static string IDNDecode(string inputUrl)
518 var uriBuilder = new UriBuilder(inputUrl);
520 var idnConverter = new IdnMapping();
521 uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
523 return uriBuilder.Uri.ToString();
532 /// URL を画面上で人間に読みやすい文字列に変換する(エスケープ解除など)
534 public static string ConvertToReadableUrl(string inputUrl)
536 var outputUrl = inputUrl;
539 outputUrl = MyCommon.IDNDecode(outputUrl);
541 // URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
542 // 参考: Firefoxの losslessDecodeURI() 関数
543 // http://hg.mozilla.org/mozilla-central/annotate/FIREFOX_AURORA_27_BASE/browser/base/content/browser.js#l2128
544 outputUrl = Regex.Replace(outputUrl, @"%(2[3456BCF]|3[ABDF]|40)", @"%25$1", RegexOptions.IgnoreCase);
547 outputUrl = Uri.UnescapeDataString(outputUrl);
552 public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
554 var moved_value = values[idx_fr];
555 var num_moved = Math.Abs(idx_fr - idx_to);
559 Array.Copy(values, idx_to, values,
560 idx_to + 1, num_moved);
564 Array.Copy(values, idx_fr + 1, values,
568 values[idx_to] = moved_value;
571 public static string EncryptString(string str)
573 if (string.IsNullOrEmpty(str)) return "";
576 var bytesIn = Encoding.UTF8.GetBytes(str);
578 //DESCryptoServiceProviderオブジェクトの作成
579 using (var des = new DESCryptoServiceProvider())
583 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
585 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
586 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
588 MemoryStream msOut = null;
589 ICryptoTransform desdecrypt = null;
593 //暗号化されたデータを書き出すためのMemoryStream
594 msOut = new MemoryStream();
597 desdecrypt = des.CreateEncryptor();
599 //書き込むためのCryptoStreamの作成
600 using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
602 //Disposeが重複して呼ばれないようにする
603 MemoryStream msTmp = msOut;
608 cryptStream.Write(bytesIn, 0, bytesIn.Length);
609 cryptStream.FlushFinalBlock();
611 var bytesOut = msTmp.ToArray();
613 //Base64で文字列に変更して結果を返す
614 return Convert.ToBase64String(bytesOut);
619 if (msOut != null) msOut.Dispose();
620 if (desdecrypt != null) desdecrypt.Dispose();
625 public static string DecryptString(string str)
627 if (string.IsNullOrEmpty(str)) return "";
629 //DESCryptoServiceProviderオブジェクトの作成
630 using (var des = new System.Security.Cryptography.DESCryptoServiceProvider())
634 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
636 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
637 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
639 //Base64で文字列をバイト配列に戻す
640 var bytesIn = Convert.FromBase64String(str);
642 MemoryStream msIn = null;
643 ICryptoTransform desdecrypt = null;
644 CryptoStream cryptStreem = null;
648 //暗号化されたデータを読み込むためのMemoryStream
649 msIn = new MemoryStream(bytesIn);
651 desdecrypt = des.CreateDecryptor();
652 //読み込むためのCryptoStreamの作成
653 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
655 //Disposeが重複して呼ばれないようにする
659 //復号化されたデータを取得するためのStreamReader
660 using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
662 //Disposeが重複して呼ばれないようにする
666 var result = srOut.ReadToEnd();
673 if (msIn != null) msIn.Dispose();
674 if (desdecrypt != null) desdecrypt.Dispose();
675 if (cryptStreem != null) cryptStreem.Dispose();
680 public static byte[] ResizeBytesArray(byte[] bytes,
683 var newBytes = new byte[newSize];
684 if (bytes.Length <= newSize)
686 foreach (var i in Enumerable.Range(0, bytes.Length))
688 newBytes[i] = bytes[i];
694 foreach (var i in Enumerable.Range(0, bytes.Length))
696 newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
698 if (pos >= newBytes.Length)
707 public static bool IsNT6()
710 return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6;
714 public enum TabUsageType
718 Mentions = 2, //Unique
719 DirectMessage = 4, //Unique
720 Favorites = 8, //Unique
722 LocalQuery = 32, //Pin(no save/no save query/distribute/no update(normal update))
723 Profile = 64, //Pin(save/no distribute/manual update)
724 PublicSearch = 128, //Pin(save/no distribute/auto update)
733 public static string fileVersion = "";
735 public static string GetUserAgentString()
737 if (string.IsNullOrEmpty(fileVersion))
739 throw new Exception("fileversion is not Initialized.");
741 return GetAssemblyName() + "/" + fileVersion;
744 public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
746 public static bool IsAnimatedGif(string filename)
751 img = Image.FromFile(filename);
752 if (img == null) return false;
753 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
755 var fd = new FrameDimension(img.FrameDimensionsList[0]);
756 var fd_count = img.GetFrameCount(fd);
774 if (img != null) img.Dispose();
778 public static DateTime DateTimeParse(string input)
782 "ddd MMM dd HH:mm:ss zzzz yyyy",
783 "ddd, d MMM yyyy HH:mm:ss zzzz",
785 foreach (var fmt in format)
787 if (DateTime.TryParseExact(input,
789 DateTimeFormatInfo.InvariantInfo,
800 TraceOut("Parse Error(DateTimeFormat) : " + input);
801 return new DateTime();
804 public static T CreateDataFromJson<T>(string content)
807 var buf = Encoding.Unicode.GetBytes(content);
808 using (var stream = new MemoryStream(buf))
810 data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
815 public static bool IsNetworkAvailable()
819 return NetworkInterface.GetIsNetworkAvailable();
827 public static bool IsValidEmail(string strIn)
829 // Return true if strIn is in valid e-mail format.
830 return Regex.IsMatch(strIn,
831 @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
832 @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
836 /// 指定された修飾キーが押されている状態かを取得します。
838 /// <param name="keys">状態を調べるキー</param>
839 /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
840 public static bool IsKeyDown(params Keys[] keys)
842 return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
845 internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
847 foreach (Keys key in targetKeys)
849 if ((modifierKeys & key) != key)
858 /// アプリケーションのアセンブリ名を取得します。
861 /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
863 /// <returns>アプリケーションのアセンブリ名</returns>
864 public static string GetAssemblyName()
866 return MyCommon.EntryAssembly.GetName().Name;
869 internal static _Assembly EntryAssembly = Assembly.GetEntryAssembly();
872 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
874 /// <param name="orig">対象となる文字列</param>
875 /// <returns>置換後の文字列</returns>
876 public static string ReplaceAppName(string orig)
878 return MyCommon.ReplaceAppName(orig, Application.ProductName);
882 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
884 /// <param name="orig">対象となる文字列</param>
885 /// <param name="appname">アプリケーション名</param>
886 /// <returns>置換後の文字列</returns>
887 public static string ReplaceAppName(string orig, string appname)
889 return orig.Replace("%AppName%", appname);
893 /// 表示用のバージョン番号の文字列を生成する
896 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
901 public static string GetReadableVersion(string fileVersion = null)
903 if (fileVersion == null)
905 fileVersion = MyCommon.fileVersion;
908 if (string.IsNullOrEmpty(fileVersion))
913 int[] version = fileVersion.Split('.')
914 .Select(x => int.Parse(x)).ToArray();
918 return string.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
922 version[2] = version[2] + 1;
925 if (version[2] >= 10)
927 version[1] += version[2] / 10;
930 if (version[1] >= 10)
932 version[0] += version[1] / 10;
937 return string.Format("{0}.{1}.{2}-beta{3}", version[0], version[1], version[2], version[3]);
941 public const string TwitterUrl = "https://twitter.com/";
943 public static string GetStatusUrl(PostClass post)
945 if (post.RetweetedId == null)
946 return GetStatusUrl(post.ScreenName, post.StatusId);
948 return GetStatusUrl(post.ScreenName, post.RetweetedId.Value);
951 public static string GetStatusUrl(string screenName, long statusId)
953 return TwitterUrl + screenName + "/status/" + statusId.ToString();