using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;
+using System.Net;
+using System.Net.Http;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
+using OpenTween.Api;
namespace OpenTween
{
- public sealed class MyCommon
+ public static class MyCommon
{
private static readonly object LockObj = new object();
public static bool _endingFlag; //終了フラグ
Unu = -1,
}
- public enum OutputzUrlmode
- {
- twittercom,
- twittercomWithUsername,
- }
-
public enum HITRESULT
{
None,
UserTimeline, //UserTimeline
BlockIds, //Blocking/ids
Configuration, //Twitter Configuration読み込み
+ NoRetweetIds, //RT非表示ユーザー取得
//////
ErrorState, //エラー表示のみで後処理終了(認証エラー時など)
}
Deleted = 256,
ListCreated = 512,
ListUpdated = 1024,
+ Unfollow = 2048,
+ ListUserSubscribed = 4096,
+ ListUserUnsubscribed = 8192,
+ ListDestroyed = 16384,
All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
- Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated),
+ Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
+ ListUserSubscribed | ListUserUnsubscribed | ListDestroyed),
+ }
+
+ public static _Assembly EntryAssembly { get; internal set; }
+ public static string FileVersion { get; internal set; }
+
+ static MyCommon()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ MyCommon.EntryAssembly = assembly;
+
+ var fileVersionAttribute = (AssemblyFileVersionAttribute)assembly
+ .GetCustomAttributes(typeof(AssemblyFileVersionAttribute)).First();
+ MyCommon.FileVersion = fileVersionAttribute.Version;
+ }
+
+ public static string GetErrorLogPath()
+ {
+ return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
+ }
+
+ public static void TraceOut(WebApiException ex)
+ {
+ var message = ExceptionOutMessage(ex);
+
+ if (ex.ResponseText != null)
+ message += Environment.NewLine + "------- Response Data -------" + Environment.NewLine + ex.ResponseText;
+
+ TraceOut(TraceFlag, message);
}
public static void TraceOut(Exception ex, string Message)
lock (LockObj)
{
if (!OutputFlag) return;
+
+ var logPath = MyCommon.GetErrorLogPath();
+ if (!Directory.Exists(logPath))
+ Directory.CreateDirectory(logPath);
+
var now = DateTime.Now;
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);
+ fileName = Path.Combine(logPath, fileName);
using (var writer = new StreamWriter(fileName))
{
writer.WriteLine(Properties.Resources.TraceOutText3);
writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version.ToString());
- writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), fileVersion);
+ writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), FileVersion);
writer.WriteLine(Message);
writer.WriteLine();
}
lock (LockObj)
{
var IsTerminatePermission = true;
+
+ var logPath = MyCommon.GetErrorLogPath();
+ if (!Directory.Exists(logPath))
+ Directory.CreateDirectory(logPath);
+
var now = DateTime.Now;
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);
+ fileName = Path.Combine(logPath, fileName);
using (var writer = new StreamWriter(fileName))
{
writer.WriteLine(Properties.Resources.UnhandledExceptionText4);
writer.WriteLine(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString);
writer.WriteLine(Properties.Resources.UnhandledExceptionText6, Environment.Version.ToString());
- writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), fileVersion);
+ writer.WriteLine(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), FileVersion);
writer.Write(ExceptionOutMessage(ex, ref IsTerminatePermission));
writer.Flush();
case DialogResult.No:
return false;
case DialogResult.Cancel:
- return IsTerminatePermission;
default:
- throw new Exception("");
+ return IsTerminatePermission;
}
}
}
/// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
/// </newpara>
/// </summary>
- /// <param name = input>エンコード対象のURL</param>
+ /// <param name="_input">エンコード対象のURL</param>
/// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
public static string urlEncodeMultibyteChar(string _input)
}
if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
- var input = HttpUtility.UrlDecode(_input);
+ var input = Uri.UnescapeDataString(_input);
retry:
foreach (char c in input)
{
return result;
}
- ////// <summary>
- ////// URLのドメイン名をPunycode展開します。
- ////// <para>
- ////// ドメイン名がIDNでない場合はそのまま返します。
- ////// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
- ////// </para>
- ////// </summary>
- ////// <param name="input">展開対象のURL</param>
- ////// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
-
- public static string IDNDecode(string input)
+ /// <summary>
+ /// URLのドメイン名をPunycode展開します。
+ /// <para>
+ /// ドメイン名がIDNでない場合はそのまま返します。
+ /// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
+ /// </para>
+ /// </summary>
+ /// <param name="input">展開対象のURL</param>
+ /// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
+ public static string IDNEncode(string inputUrl)
{
- var IDNConverter = new IdnMapping();
+ try
+ {
+ var uriBuilder = new UriBuilder(inputUrl);
- if (!input.Contains("://")) return null;
+ var idnConverter = new IdnMapping();
+ uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
- // ドメイン名をPunycode展開
- string Domain;
- string AsciiDomain;
+ return uriBuilder.Uri.ToString();
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+ public static string IDNDecode(string inputUrl)
+ {
try
{
- Domain = input.Split('/')[2];
- AsciiDomain = IDNConverter.GetAscii(Domain);
+ var uriBuilder = new UriBuilder(inputUrl);
+
+ if (uriBuilder.Host != null)
+ {
+ var idnConverter = new IdnMapping();
+ uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
+ }
+
+ return uriBuilder.Uri.ToString();
}
catch (Exception)
{
return null;
}
+ }
+
+ /// <summary>
+ /// URL を画面上で人間に読みやすい文字列に変換する(エスケープ解除など)
+ /// </summary>
+ public static string ConvertToReadableUrl(string inputUrl)
+ {
+ try
+ {
+ var outputUrl = inputUrl;
+
+ // Punycodeをデコードする
+ outputUrl = MyCommon.IDNDecode(outputUrl);
+ if (outputUrl == null)
+ return inputUrl;
- return input.Replace("://" + Domain, "://" + AsciiDomain);
+ // URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
+ // 参考: Firefoxの losslessDecodeURI() 関数
+ // http://hg.mozilla.org/mozilla-central/annotate/FIREFOX_AURORA_27_BASE/browser/base/content/browser.js#l2128
+ outputUrl = Regex.Replace(outputUrl, @"%(2[3456BCF]|3[ABDF]|40)", @"%25$1", RegexOptions.IgnoreCase);
+
+ // エスケープを解除する
+ outputUrl = Uri.UnescapeDataString(outputUrl);
+
+ return outputUrl;
+ }
+ catch (UriFormatException)
+ {
+ return inputUrl;
+ }
}
public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
//RTByMe
}
- public static string fileVersion = "";
-
- public static string GetUserAgentString()
- {
- if (string.IsNullOrEmpty(fileVersion))
- {
- throw new Exception("fileversion is not Initialized.");
- }
- return GetAssemblyName() + "/" + fileVersion;
- }
-
- public static ApiInformation TwitterApiInfo = new ApiInformation();
+ public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
public static bool IsAnimatedGif(string filename)
{
public static T CreateDataFromJson<T>(string content)
{
T data;
- using (var stream = new MemoryStream())
+ var buf = Encoding.Unicode.GetBytes(content);
+ using (var stream = new MemoryStream(buf))
{
- var buf = Encoding.Unicode.GetBytes(content);
- stream.Write(Encoding.Unicode.GetBytes(content), offset: 0, count: buf.Length);
- stream.Seek(offset: 0, loc: SeekOrigin.Begin);
data = (T)((new DataContractJsonSerializer(typeof(T))).ReadObject(stream));
}
return data;
return MyCommon.EntryAssembly.GetName().Name;
}
- internal static _Assembly EntryAssembly = Assembly.GetEntryAssembly();
-
/// <summary>
/// 文字列中に含まれる %AppName% をアプリケーション名に置換する
/// </summary>
/// <returns>
/// 生成されたバージョン番号の文字列
/// </returns>
- public static string GetReadableVersion(string fileVersion = null)
+ public static string GetReadableVersion(string versionStr = null)
{
- if (fileVersion == null)
- {
- fileVersion = MyCommon.fileVersion;
- }
+ var version = Version.Parse(versionStr ?? MyCommon.FileVersion);
- if (string.IsNullOrEmpty(fileVersion))
- {
- return null;
- }
+ return GetReadableVersion(version);
+ }
- int[] version = fileVersion.Split('.')
- .Select(x => int.Parse(x)).ToArray();
+ /// <summary>
+ /// 表示用のバージョン番号の文字列を生成する
+ /// </summary>
+ /// <remarks>
+ /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
+ /// </remarks>
+ /// <returns>
+ /// 生成されたバージョン番号の文字列
+ /// </returns>
+ public static string GetReadableVersion(Version version)
+ {
+ var versionNum = new[] { version.Major, version.Minor, version.Build, version.Revision };
- if (version[3] == 0)
+ if (versionNum[3] == 0)
{
- return string.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
+ return string.Format("{0}.{1}.{2}", versionNum[0], versionNum[1], versionNum[2]);
}
else
{
- version[2] = version[2] + 1;
+ versionNum[2] = versionNum[2] + 1;
// 10を越えたら桁上げ
- if (version[2] >= 10)
+ if (versionNum[2] >= 10)
{
- version[1] += version[2] / 10;
- version[2] %= 10;
+ versionNum[1] += versionNum[2] / 10;
+ versionNum[2] %= 10;
- if (version[1] >= 10)
+ if (versionNum[1] >= 10)
{
- version[0] += version[1] / 10;
- version[1] %= 10;
+ versionNum[0] += versionNum[1] / 10;
+ versionNum[1] %= 10;
}
}
- return string.Format("{0}.{1}.{2}-beta{3}", version[0], version[1], version[2], version[3]);
+ return string.Format("{0}.{1}.{2}-beta{3}", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
}
}
public static string GetStatusUrl(PostClass post)
{
- if (post.RetweetedId == 0)
+ if (post.RetweetedId == null)
return GetStatusUrl(post.ScreenName, post.StatusId);
else
- return GetStatusUrl(post.ScreenName, post.RetweetedId);
+ return GetStatusUrl(post.ScreenName, post.RetweetedId.Value);
}
public static string GetStatusUrl(string screenName, long statusId)
{
return TwitterUrl + screenName + "/status/" + statusId.ToString();
}
+
+ /// <summary>
+ /// 指定された IDictionary を元にクエリ文字列を生成します
+ /// </summary>
+ /// <param name="param">生成するクエリの key-value コレクション</param>
+ public static string BuildQueryString(IDictionary<string, string> param)
+ {
+ if (param == null || param.Count == 0)
+ return string.Empty;
+
+ var query = param
+ .Where(x => x.Value != null)
+ .Select(x => EscapeQueryString(x.Key) + '=' + EscapeQueryString(x.Value));
+
+ return string.Join("&", query);
+ }
+
+ // .NET 4.5+: Reserved characters のうち、Uriクラスによってエスケープ強制解除されてしまうものも最初から Unreserved として扱う
+ private static readonly HashSet<char> UnreservedChars =
+ new HashSet<char>("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*:");
+
+ /// <summary>
+ /// 2バイト文字も考慮したクエリ用エンコード
+ /// </summary>
+ /// <param name="stringToEncode">エンコードする文字列</param>
+ /// <returns>エンコード結果文字列</returns>
+ public static string EscapeQueryString(string stringToEncode)
+ {
+ var sb = new StringBuilder(stringToEncode.Length * 2);
+
+ foreach (var b in Encoding.UTF8.GetBytes(stringToEncode))
+ {
+ if (UnreservedChars.Contains((char)b))
+ sb.Append((char)b);
+ else
+ sb.AppendFormat("%{0:X2}", b);
+ }
+
+ return sb.ToString();
+ }
}
}