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読み込み
157 ErrorState, //エラー表示のみで後処理終了(認証エラー時など)
160 public static class DEFAULTTAB
162 public const string RECENT = "Recent";
163 public const string REPLY = "Reply";
164 public const string DM = "Direct";
165 public const string FAV = "Favorites";
167 //private string dummy;
169 //private object ReferenceEquals()
171 // return new object();
173 //private object Equals()
175 // return new object();
179 public static readonly object Block = null;
180 public static bool TraceFlag = false;
183 public static bool DebugBuild = true;
185 public static bool DebugBuild = false;
188 public enum ACCOUNT_STATE
194 public enum REPLY_ICONSTATE
202 public enum EVENTTYPE
209 ListMemberRemoved = 16,
217 ListUserSubscribed = 4096,
218 ListUserUnsubscribed = 8192,
220 All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
221 Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
222 ListUserSubscribed | ListUserUnsubscribed),
225 public static string GetErrorLogPath()
227 return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
230 public static void TraceOut(Exception ex, string Message)
232 var buf = ExceptionOutMessage(ex);
233 TraceOut(TraceFlag, Message + Environment.NewLine + buf);
236 public static void TraceOut(string Message)
238 TraceOut(TraceFlag, Message);
241 public static void TraceOut(bool OutputFlag, string Message)
245 if (!OutputFlag) return;
247 var logPath = MyCommon.GetErrorLogPath();
248 if (!Directory.Exists(logPath))
249 Directory.CreateDirectory(logPath);
251 var now = DateTime.Now;
252 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);
253 fileName = Path.Combine(logPath, fileName);
255 using (var writer = new StreamWriter(fileName))
257 writer.WriteLine("**** TraceOut: {0} ****", DateTime.Now.ToString());
258 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
259 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
261 writer.WriteLine(Properties.Resources.TraceOutText3);
262 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
263 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
264 writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), fileVersion);
265 writer.WriteLine(Message);
272 // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
274 // Dataプロパティにある終了許可フラグのパースもここで行う
276 public static string ExceptionOutMessage(Exception ex)
278 bool IsTerminatePermission = true;
279 return ExceptionOutMessage(ex, ref IsTerminatePermission);
282 public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
284 if (ex == null) return "";
286 var buf = new StringBuilder();
288 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
292 var needHeader = true;
293 foreach (DictionaryEntry dt in ex.Data)
298 buf.AppendLine("-------Extra Information-------");
301 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
303 if (dt.Key.Equals("IsTerminatePermission"))
305 IsTerminatePermission = (bool)dt.Value;
310 buf.AppendLine("-----End Extra Information-----");
313 buf.AppendLine(ex.StackTrace);
316 //InnerExceptionが存在する場合書き出す
317 var _ex = ex.InnerException;
321 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
323 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
325 if (_ex.Data != null)
327 var needHeader = true;
329 foreach (DictionaryEntry dt in _ex.Data)
334 buf.AppendLine("-------Extra Information-------");
337 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
338 if (dt.Key.Equals("IsTerminatePermission"))
340 IsTerminatePermission = (bool)dt.Value;
345 buf.AppendLine("-----End Extra Information-----");
348 buf.AppendLine(_ex.StackTrace);
351 _ex = _ex.InnerException;
353 return buf.ToString();
356 public static bool ExceptionOut(Exception ex)
360 var IsTerminatePermission = true;
362 var logPath = MyCommon.GetErrorLogPath();
363 if (!Directory.Exists(logPath))
364 Directory.CreateDirectory(logPath);
366 var now = DateTime.Now;
367 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);
368 fileName = Path.Combine(logPath, fileName);
370 using (var writer = new StreamWriter(fileName))
372 var ident = WindowsIdentity.GetCurrent();
373 var princ = new WindowsPrincipal(ident);
375 writer.WriteLine(Properties.Resources.UnhandledExceptionText1, DateTime.Now.ToString());
376 writer.WriteLine(Properties.Resources.UnhandledExceptionText2, ApplicationSettings.FeedbackEmailAddress);
377 writer.WriteLine(Properties.Resources.UnhandledExceptionText3, ApplicationSettings.FeedbackTwitterName);
379 writer.WriteLine(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator).ToString());
380 writer.WriteLine(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User).ToString());
382 // OSVersion,AppVersion書き出し
383 writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
384 writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
385 writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
386 writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), fileVersion);
388 writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
392 switch (MessageBox.Show(MyCommon.ReplaceAppName(string.Format(Properties.Resources.UnhandledExceptionText9, fileName, ApplicationSettings.FeedbackEmailAddress, ApplicationSettings.FeedbackTwitterName, Environment.NewLine)),
393 Properties.Resources.UnhandledExceptionText10, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error))
395 case DialogResult.Yes:
396 Process.Start(fileName);
398 case DialogResult.No:
400 case DialogResult.Cancel:
401 return IsTerminatePermission;
403 throw new Exception("");
409 /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
411 /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
414 /// <param name="_input">エンコード対象のURL</param>
415 /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
417 public static string urlEncodeMultibyteChar(string _input)
420 var sb = new StringBuilder(256);
423 foreach (var c in _input)
426 if (Convert.ToInt32(c) > 127 || c == '%') break;
428 if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
430 var input = Uri.UnescapeDataString(_input);
432 foreach (char c in input)
434 if (Convert.ToInt32(c) > 255)
436 // Unicodeの場合(1charが複数のバイトで構成されている)
437 // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
438 foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
440 sb.AppendFormat("%{0:X2}", b);
443 else if (Convert.ToInt32(c) > 127 || c == '%')
446 // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
449 uri = new Uri(input);
450 input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
456 sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpper());
467 result = sb.ToString();
471 result = uri.GetLeftPart(UriPartial.Authority) + sb.ToString();
478 ////// URLのドメイン名をPunycode展開します。
480 ////// ドメイン名がIDNでない場合はそのまま返します。
481 ////// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
484 ////// <param name="input">展開対象のURL</param>
485 ////// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
487 public static string IDNDecode(string input)
489 var IDNConverter = new IdnMapping();
491 if (!input.Contains("://")) return null;
499 Domain = input.Split('/')[2];
500 AsciiDomain = IDNConverter.GetAscii(Domain);
507 return input.Replace("://" + Domain, "://" + AsciiDomain);
510 public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
512 var moved_value = values[idx_fr];
513 var num_moved = Math.Abs(idx_fr - idx_to);
517 Array.Copy(values, idx_to, values,
518 idx_to + 1, num_moved);
522 Array.Copy(values, idx_fr + 1, values,
526 values[idx_to] = moved_value;
529 public static string EncryptString(string str)
531 if (string.IsNullOrEmpty(str)) return "";
534 var bytesIn = Encoding.UTF8.GetBytes(str);
536 //DESCryptoServiceProviderオブジェクトの作成
537 using (var des = new DESCryptoServiceProvider())
541 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
543 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
544 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
546 MemoryStream msOut = null;
547 ICryptoTransform desdecrypt = null;
551 //暗号化されたデータを書き出すためのMemoryStream
552 msOut = new MemoryStream();
555 desdecrypt = des.CreateEncryptor();
557 //書き込むためのCryptoStreamの作成
558 using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
560 //Disposeが重複して呼ばれないようにする
561 MemoryStream msTmp = msOut;
566 cryptStream.Write(bytesIn, 0, bytesIn.Length);
567 cryptStream.FlushFinalBlock();
569 var bytesOut = msTmp.ToArray();
571 //Base64で文字列に変更して結果を返す
572 return Convert.ToBase64String(bytesOut);
577 if (msOut != null) msOut.Dispose();
578 if (desdecrypt != null) desdecrypt.Dispose();
583 public static string DecryptString(string str)
585 if (string.IsNullOrEmpty(str)) return "";
587 //DESCryptoServiceProviderオブジェクトの作成
588 using (var des = new System.Security.Cryptography.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 //Base64で文字列をバイト配列に戻す
598 var bytesIn = Convert.FromBase64String(str);
600 MemoryStream msIn = null;
601 ICryptoTransform desdecrypt = null;
602 CryptoStream cryptStreem = null;
606 //暗号化されたデータを読み込むためのMemoryStream
607 msIn = new MemoryStream(bytesIn);
609 desdecrypt = des.CreateDecryptor();
610 //読み込むためのCryptoStreamの作成
611 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
613 //Disposeが重複して呼ばれないようにする
617 //復号化されたデータを取得するためのStreamReader
618 using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
620 //Disposeが重複して呼ばれないようにする
624 var result = srOut.ReadToEnd();
631 if (msIn != null) msIn.Dispose();
632 if (desdecrypt != null) desdecrypt.Dispose();
633 if (cryptStreem != null) cryptStreem.Dispose();
638 public static byte[] ResizeBytesArray(byte[] bytes,
641 var newBytes = new byte[newSize];
642 if (bytes.Length <= newSize)
644 foreach (var i in Enumerable.Range(0, bytes.Length))
646 newBytes[i] = bytes[i];
652 foreach (var i in Enumerable.Range(0, bytes.Length))
654 newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
656 if (pos >= newBytes.Length)
665 public static bool IsNT6()
668 return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6;
672 public enum TabUsageType
676 Mentions = 2, //Unique
677 DirectMessage = 4, //Unique
678 Favorites = 8, //Unique
680 LocalQuery = 32, //Pin(no save/no save query/distribute/no update(normal update))
681 Profile = 64, //Pin(save/no distribute/manual update)
682 PublicSearch = 128, //Pin(save/no distribute/auto update)
691 public static string fileVersion = "";
693 public static string GetUserAgentString()
695 if (string.IsNullOrEmpty(fileVersion))
697 throw new Exception("fileversion is not Initialized.");
699 return GetAssemblyName() + "/" + fileVersion;
702 public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
703 public static TwitterApiStatus11 TwitterApiInfo11 = new TwitterApiStatus11();
705 public static bool IsAnimatedGif(string filename)
710 img = Image.FromFile(filename);
711 if (img == null) return false;
712 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
714 var fd = new FrameDimension(img.FrameDimensionsList[0]);
715 var fd_count = img.GetFrameCount(fd);
733 if (img != null) img.Dispose();
737 public static DateTime DateTimeParse(string input)
741 "ddd MMM dd HH:mm:ss zzzz yyyy",
742 "ddd, d MMM yyyy HH:mm:ss zzzz",
744 foreach (var fmt in format)
746 if (DateTime.TryParseExact(input,
748 DateTimeFormatInfo.InvariantInfo,
759 TraceOut("Parse Error(DateTimeFormat) : " + input);
760 return new DateTime();
763 public static T CreateDataFromJson<T>(string content)
766 var buf = Encoding.Unicode.GetBytes(content);
767 using (var stream = new MemoryStream(buf))
769 data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
774 public static bool IsNetworkAvailable()
778 return NetworkInterface.GetIsNetworkAvailable();
786 public static bool IsValidEmail(string strIn)
788 // Return true if strIn is in valid e-mail format.
789 return Regex.IsMatch(strIn,
790 @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
791 @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
795 /// 指定された修飾キーが押されている状態かを取得します。
797 /// <param name="keys">状態を調べるキー</param>
798 /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
799 public static bool IsKeyDown(params Keys[] keys)
801 return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
804 internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
806 foreach (Keys key in targetKeys)
808 if ((modifierKeys & key) != key)
817 /// アプリケーションのアセンブリ名を取得します。
820 /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
822 /// <returns>アプリケーションのアセンブリ名</returns>
823 public static string GetAssemblyName()
825 return MyCommon.EntryAssembly.GetName().Name;
828 internal static _Assembly EntryAssembly = Assembly.GetEntryAssembly();
831 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
833 /// <param name="orig">対象となる文字列</param>
834 /// <returns>置換後の文字列</returns>
835 public static string ReplaceAppName(string orig)
837 return MyCommon.ReplaceAppName(orig, Application.ProductName);
841 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
843 /// <param name="orig">対象となる文字列</param>
844 /// <param name="appname">アプリケーション名</param>
845 /// <returns>置換後の文字列</returns>
846 public static string ReplaceAppName(string orig, string appname)
848 return orig.Replace("%AppName%", appname);
852 /// 表示用のバージョン番号の文字列を生成する
855 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
860 public static string GetReadableVersion(string fileVersion = null)
862 if (fileVersion == null)
864 fileVersion = MyCommon.fileVersion;
867 if (string.IsNullOrEmpty(fileVersion))
872 int[] version = fileVersion.Split('.')
873 .Select(x => int.Parse(x)).ToArray();
877 return string.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
881 version[2] = version[2] + 1;
884 if (version[2] >= 10)
886 version[1] += version[2] / 10;
889 if (version[1] >= 10)
891 version[0] += version[1] / 10;
896 return string.Format("{0}.{1}.{2}-beta{3}", version[0], version[1], version[2], version[3]);
900 public const string TwitterUrl = "https://twitter.com/";
902 public static string GetStatusUrl(PostClass post)
904 if (post.RetweetedId == 0)
905 return GetStatusUrl(post.ScreenName, post.StatusId);
907 return GetStatusUrl(post.ScreenName, post.RetweetedId);
910 public static string GetStatusUrl(string screenName, long statusId)
912 return TwitterUrl + screenName + "/status/" + statusId.ToString();