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,
218 All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
219 Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow),
222 public static string GetErrorLogPath()
224 return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
227 public static void TraceOut(Exception ex, string Message)
229 var buf = ExceptionOutMessage(ex);
230 TraceOut(TraceFlag, Message + Environment.NewLine + buf);
233 public static void TraceOut(string Message)
235 TraceOut(TraceFlag, Message);
238 public static void TraceOut(bool OutputFlag, string Message)
242 if (!OutputFlag) return;
244 var logPath = MyCommon.GetErrorLogPath();
245 if (!Directory.Exists(logPath))
246 Directory.CreateDirectory(logPath);
248 var now = DateTime.Now;
249 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);
250 fileName = Path.Combine(logPath, fileName);
252 using (var writer = new StreamWriter(fileName))
254 writer.WriteLine("**** TraceOut: {0} ****", DateTime.Now.ToString());
255 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
256 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
258 writer.WriteLine(Properties.Resources.TraceOutText3);
259 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
260 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
261 writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), fileVersion);
262 writer.WriteLine(Message);
269 // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
271 // Dataプロパティにある終了許可フラグのパースもここで行う
273 public static string ExceptionOutMessage(Exception ex)
275 bool IsTerminatePermission = true;
276 return ExceptionOutMessage(ex, ref IsTerminatePermission);
279 public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
281 if (ex == null) return "";
283 var buf = new StringBuilder();
285 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
289 var needHeader = true;
290 foreach (DictionaryEntry dt in ex.Data)
295 buf.AppendLine("-------Extra Information-------");
298 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
300 if (dt.Key.Equals("IsTerminatePermission"))
302 IsTerminatePermission = (bool)dt.Value;
307 buf.AppendLine("-----End Extra Information-----");
310 buf.AppendLine(ex.StackTrace);
313 //InnerExceptionが存在する場合書き出す
314 var _ex = ex.InnerException;
318 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
320 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
322 if (_ex.Data != null)
324 var needHeader = true;
326 foreach (DictionaryEntry dt in _ex.Data)
331 buf.AppendLine("-------Extra Information-------");
334 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
335 if (dt.Key.Equals("IsTerminatePermission"))
337 IsTerminatePermission = (bool)dt.Value;
342 buf.AppendLine("-----End Extra Information-----");
345 buf.AppendLine(_ex.StackTrace);
348 _ex = _ex.InnerException;
350 return buf.ToString();
353 public static bool ExceptionOut(Exception ex)
357 var IsTerminatePermission = true;
359 var logPath = MyCommon.GetErrorLogPath();
360 if (!Directory.Exists(logPath))
361 Directory.CreateDirectory(logPath);
363 var now = DateTime.Now;
364 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);
365 fileName = Path.Combine(logPath, fileName);
367 using (var writer = new StreamWriter(fileName))
369 var ident = WindowsIdentity.GetCurrent();
370 var princ = new WindowsPrincipal(ident);
372 writer.WriteLine(Properties.Resources.UnhandledExceptionText1, DateTime.Now.ToString());
373 writer.WriteLine(Properties.Resources.UnhandledExceptionText2, ApplicationSettings.FeedbackEmailAddress);
374 writer.WriteLine(Properties.Resources.UnhandledExceptionText3, ApplicationSettings.FeedbackTwitterName);
376 writer.WriteLine(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator).ToString());
377 writer.WriteLine(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User).ToString());
379 // OSVersion,AppVersion書き出し
380 writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
381 writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
382 writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
383 writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), fileVersion);
385 writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
389 switch (MessageBox.Show(MyCommon.ReplaceAppName(string.Format(Properties.Resources.UnhandledExceptionText9, fileName, ApplicationSettings.FeedbackEmailAddress, ApplicationSettings.FeedbackTwitterName, Environment.NewLine)),
390 Properties.Resources.UnhandledExceptionText10, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error))
392 case DialogResult.Yes:
393 Process.Start(fileName);
395 case DialogResult.No:
397 case DialogResult.Cancel:
398 return IsTerminatePermission;
400 throw new Exception("");
406 /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
408 /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
411 /// <param name = input>エンコード対象のURL</param>
412 /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
414 public static string urlEncodeMultibyteChar(string _input)
417 var sb = new StringBuilder(256);
420 foreach (var c in _input)
423 if (Convert.ToInt32(c) > 127 || c == '%') break;
425 if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
427 var input = Uri.UnescapeDataString(_input);
429 foreach (char c in input)
431 if (Convert.ToInt32(c) > 255)
433 // Unicodeの場合(1charが複数のバイトで構成されている)
434 // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
435 foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
437 sb.AppendFormat("%{0:X2}", b);
440 else if (Convert.ToInt32(c) > 127 || c == '%')
443 // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
446 uri = new Uri(input);
447 input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
453 sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpper());
464 result = sb.ToString();
468 result = uri.GetLeftPart(UriPartial.Authority) + sb.ToString();
475 ////// URLのドメイン名をPunycode展開します。
477 ////// ドメイン名がIDNでない場合はそのまま返します。
478 ////// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
481 ////// <param name="input">展開対象のURL</param>
482 ////// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
484 public static string IDNDecode(string input)
486 var IDNConverter = new IdnMapping();
488 if (!input.Contains("://")) return null;
496 Domain = input.Split('/')[2];
497 AsciiDomain = IDNConverter.GetAscii(Domain);
504 return input.Replace("://" + Domain, "://" + AsciiDomain);
507 public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
509 var moved_value = values[idx_fr];
510 var num_moved = Math.Abs(idx_fr - idx_to);
514 Array.Copy(values, idx_to, values,
515 idx_to + 1, num_moved);
519 Array.Copy(values, idx_fr + 1, values,
523 values[idx_to] = moved_value;
526 public static string EncryptString(string str)
528 if (string.IsNullOrEmpty(str)) return "";
531 var bytesIn = Encoding.UTF8.GetBytes(str);
533 //DESCryptoServiceProviderオブジェクトの作成
534 using (var des = new DESCryptoServiceProvider())
538 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
540 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
541 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
543 MemoryStream msOut = null;
544 ICryptoTransform desdecrypt = null;
548 //暗号化されたデータを書き出すためのMemoryStream
549 msOut = new MemoryStream();
552 desdecrypt = des.CreateEncryptor();
554 //書き込むためのCryptoStreamの作成
555 using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
557 //Disposeが重複して呼ばれないようにする
558 MemoryStream msTmp = msOut;
563 cryptStream.Write(bytesIn, 0, bytesIn.Length);
564 cryptStream.FlushFinalBlock();
566 var bytesOut = msTmp.ToArray();
568 //Base64で文字列に変更して結果を返す
569 return Convert.ToBase64String(bytesOut);
574 if (msOut != null) msOut.Dispose();
575 if (desdecrypt != null) desdecrypt.Dispose();
580 public static string DecryptString(string str)
582 if (string.IsNullOrEmpty(str)) return "";
584 //DESCryptoServiceProviderオブジェクトの作成
585 using (var des = new System.Security.Cryptography.DESCryptoServiceProvider())
589 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
591 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
592 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
594 //Base64で文字列をバイト配列に戻す
595 var bytesIn = Convert.FromBase64String(str);
597 MemoryStream msIn = null;
598 ICryptoTransform desdecrypt = null;
599 CryptoStream cryptStreem = null;
603 //暗号化されたデータを読み込むためのMemoryStream
604 msIn = new MemoryStream(bytesIn);
606 desdecrypt = des.CreateDecryptor();
607 //読み込むためのCryptoStreamの作成
608 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
610 //Disposeが重複して呼ばれないようにする
614 //復号化されたデータを取得するためのStreamReader
615 using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
617 //Disposeが重複して呼ばれないようにする
621 var result = srOut.ReadToEnd();
628 if (msIn != null) msIn.Dispose();
629 if (desdecrypt != null) desdecrypt.Dispose();
630 if (cryptStreem != null) cryptStreem.Dispose();
635 public static byte[] ResizeBytesArray(byte[] bytes,
638 var newBytes = new byte[newSize];
639 if (bytes.Length <= newSize)
641 foreach (var i in Enumerable.Range(0, bytes.Length))
643 newBytes[i] = bytes[i];
649 foreach (var i in Enumerable.Range(0, bytes.Length))
651 newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
653 if (pos >= newBytes.Length)
662 public static bool IsNT6()
665 return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major == 6;
669 public enum TabUsageType
673 Mentions = 2, //Unique
674 DirectMessage = 4, //Unique
675 Favorites = 8, //Unique
677 LocalQuery = 32, //Pin(no save/no save query/distribute/no update(normal update))
678 Profile = 64, //Pin(save/no distribute/manual update)
679 PublicSearch = 128, //Pin(save/no distribute/auto update)
688 public static string fileVersion = "";
690 public static string GetUserAgentString()
692 if (string.IsNullOrEmpty(fileVersion))
694 throw new Exception("fileversion is not Initialized.");
696 return GetAssemblyName() + "/" + fileVersion;
699 public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
701 public static bool IsAnimatedGif(string filename)
706 img = Image.FromFile(filename);
707 if (img == null) return false;
708 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
710 var fd = new FrameDimension(img.FrameDimensionsList[0]);
711 var fd_count = img.GetFrameCount(fd);
729 if (img != null) img.Dispose();
733 public static DateTime DateTimeParse(string input)
737 "ddd MMM dd HH:mm:ss zzzz yyyy",
738 "ddd, d MMM yyyy HH:mm:ss zzzz",
740 foreach (var fmt in format)
742 if (DateTime.TryParseExact(input,
744 DateTimeFormatInfo.InvariantInfo,
755 TraceOut("Parse Error(DateTimeFormat) : " + input);
756 return new DateTime();
759 public static T CreateDataFromJson<T>(string content)
762 using (var stream = new MemoryStream())
764 var buf = Encoding.Unicode.GetBytes(content);
765 stream.Write(Encoding.Unicode.GetBytes(content), offset: 0, count: buf.Length);
766 stream.Seek(offset: 0, loc: SeekOrigin.Begin);
767 data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
772 public static bool IsNetworkAvailable()
776 return NetworkInterface.GetIsNetworkAvailable();
784 public static bool IsValidEmail(string strIn)
786 // Return true if strIn is in valid e-mail format.
787 return Regex.IsMatch(strIn,
788 @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
789 @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
793 /// 指定された修飾キーが押されている状態かを取得します。
795 /// <param name="keys">状態を調べるキー</param>
796 /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
797 public static bool IsKeyDown(params Keys[] keys)
799 return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
802 internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
804 foreach (Keys key in targetKeys)
806 if ((modifierKeys & key) != key)
815 /// アプリケーションのアセンブリ名を取得します。
818 /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
820 /// <returns>アプリケーションのアセンブリ名</returns>
821 public static string GetAssemblyName()
823 return MyCommon.EntryAssembly.GetName().Name;
826 internal static _Assembly EntryAssembly = Assembly.GetEntryAssembly();
829 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
831 /// <param name="orig">対象となる文字列</param>
832 /// <returns>置換後の文字列</returns>
833 public static string ReplaceAppName(string orig)
835 return MyCommon.ReplaceAppName(orig, Application.ProductName);
839 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
841 /// <param name="orig">対象となる文字列</param>
842 /// <param name="appname">アプリケーション名</param>
843 /// <returns>置換後の文字列</returns>
844 public static string ReplaceAppName(string orig, string appname)
846 return orig.Replace("%AppName%", appname);
850 /// 表示用のバージョン番号の文字列を生成する
853 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
858 public static string GetReadableVersion(string fileVersion = null)
860 if (fileVersion == null)
862 fileVersion = MyCommon.fileVersion;
865 if (string.IsNullOrEmpty(fileVersion))
870 int[] version = fileVersion.Split('.')
871 .Select(x => int.Parse(x)).ToArray();
875 return string.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
879 version[2] = version[2] + 1;
882 if (version[2] >= 10)
884 version[1] += version[2] / 10;
887 if (version[1] >= 10)
889 version[0] += version[1] / 10;
894 return string.Format("{0}.{1}.{2}-beta{3}", version[0], version[1], version[2], version[3]);
898 public const string TwitterUrl = "https://twitter.com/";
900 public static string GetStatusUrl(PostClass post)
902 if (post.RetweetedId == 0)
903 return GetStatusUrl(post.ScreenName, post.StatusId);
905 return GetStatusUrl(post.ScreenName, post.RetweetedId);
908 public static string GetStatusUrl(string screenName, long statusId)
910 return TwitterUrl + screenName + "/status/" + statusId.ToString();