- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
-# Build
script:
+ # Build
- xbuild /verbosity:quiet
-
-# Run Tests
-after_script:
+ # Run Tests
- if ! grep -q 'Version 11.00' OpenTween.sln; then echo 'OpenTween.sln is not compatible with Visual C# 2010 Express.'; false; fi
- nunit-console -timeout=10000 ./OpenTween.Tests/OpenTween.Tests.nunit
--- /dev/null
+[main]
+host = https://www.transifex.com
+lang_map = zh_CN: zh-CHS
+type = RESX
+
+[opentween.global]
+file_filter = OpenTween/Properties/Resources.<lang>.resx
+source_file = OpenTween/Properties/Resources.resx
+source_lang = ja
+
+[opentween.AppendSettingDialog]
+file_filter = OpenTween/AppendSettingDialog.<lang>.resx
+source_file = OpenTween/AppendSettingDialog.resx
+source_lang = ja
+
+[opentween.AtIdSupplement]
+file_filter = OpenTween/AtIdSupplement.<lang>.resx
+source_file = OpenTween/AtIdSupplement.resx
+source_lang = ja
+
+[opentween.AuthDialog]
+file_filter = OpenTween/AuthDialog.<lang>.resx
+source_file = OpenTween/AuthDialog.resx
+source_lang = ja
+
+[opentween.EventViewerDialog]
+file_filter = OpenTween/EventViewerDialog.<lang>.resx
+source_file = OpenTween/EventViewerDialog.resx
+source_lang = ja
+
+[opentween.FilterDialog]
+file_filter = OpenTween/FilterDialog.<lang>.resx
+source_file = OpenTween/FilterDialog.resx
+source_lang = ja
+
+[opentween.FormInfo]
+file_filter = OpenTween/FormInfo.<lang>.resx
+source_file = OpenTween/FormInfo.resx
+source_lang = ja
+
+[opentween.HashtagManage]
+file_filter = OpenTween/HashtagManage.<lang>.resx
+source_file = OpenTween/HashtagManage.resx
+source_lang = ja
+
+[opentween.InputTabName]
+file_filter = OpenTween/InputTabName.<lang>.resx
+source_file = OpenTween/InputTabName.resx
+source_lang = ja
+
+[opentween.ListAvailable]
+file_filter = OpenTween/ListAvailable.<lang>.resx
+source_file = OpenTween/ListAvailable.resx
+source_lang = ja
+
+[opentween.ListManage]
+file_filter = OpenTween/ListManage.<lang>.resx
+source_file = OpenTween/ListManage.resx
+source_lang = ja
+
+[opentween.MyLists]
+file_filter = OpenTween/MyLists.<lang>.resx
+source_file = OpenTween/MyLists.resx
+source_lang = ja
+
+[opentween.OpenURL]
+file_filter = OpenTween/OpenURL.<lang>.resx
+source_file = OpenTween/OpenURL.resx
+source_lang = ja
+
+[opentween.SearchWord]
+file_filter = OpenTween/SearchWord.<lang>.resx
+source_file = OpenTween/SearchWord.resx
+source_lang = ja
+
+[opentween.ShowUserInfo]
+file_filter = OpenTween/ShowUserInfo.<lang>.resx
+source_file = OpenTween/ShowUserInfo.resx
+source_lang = ja
+
+[opentween.TabsDialog]
+file_filter = OpenTween/TabsDialog.<lang>.resx
+source_file = OpenTween/TabsDialog.resx
+source_lang = ja
+
+[opentween.Tween]
+file_filter = OpenTween/Tween.<lang>.resx
+source_file = OpenTween/Tween.resx
+source_lang = ja
+
+[opentween.TweenAboutBox]
+file_filter = OpenTween/TweenAboutBox.<lang>.resx
+source_file = OpenTween/TweenAboutBox.resx
+source_lang = ja
+
+[opentween.UpdateDialog]
+file_filter = OpenTween/UpdateDialog.<lang>.resx
+source_file = OpenTween/UpdateDialog.resx
+source_lang = ja
+
var filter = new FiltersClass { UseRegex = true };
PostClass post;
- filter.NameFilter = "(hoge)+";
+ filter.NameFilter = "hoge(hoge)+";
post = new PostClass { ScreenName = "hogehoge", Text = "test" };
Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
- filter.NameFilter = "(hoge)+";
+ filter.NameFilter = "hoge(hoge)+";
post = new PostClass { ScreenName = "hoge", Text = "test" };
Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
// NameFilter は RetweetedBy にもマッチする
- filter.NameFilter = "(hoge)+";
+ filter.NameFilter = "hoge(hoge)+";
post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hogehoge" };
Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
- filter.NameFilter = "(hoge)+";
+ filter.NameFilter = "hoge(hoge)+";
post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hoge2" };
Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
// 大小文字を区別しないオプション
- filter.NameFilter = "(hoge)+";
+ filter.NameFilter = "hoge(hoge)+";
filter.CaseSensitive = false;
post = new PostClass { ScreenName = "HogeHogeHoge", Text = "test" };
Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
using System.Reflection;
using System.Windows.Forms;
using System.Runtime.Serialization;
+using System.IO;
namespace OpenTween
{
{
Assert.That(MyCommon.GetStatusUrl(screenName, statusId), Is.EqualTo(except));
}
+
+ [Test]
+ [Platform("Win")]
+ public void GetErrorLogPathTestWindows()
+ {
+ var mockAssembly = Substitute.For<_Assembly>();
+ mockAssembly.Location.Returns(@"C:\hogehoge\OpenTween\OpenTween.exe");
+ MyCommon.EntryAssembly = mockAssembly;
+
+ Assert.That(MyCommon.GetErrorLogPath(), Is.SamePath(@"C:\hogehoge\OpenTween\ErrorLogs\"));
+ }
+
+ [Test]
+ [Platform(Exclude = "Win")]
+ public void GetErrorLogPathTestOther()
+ {
+ var mockAssembly = Substitute.For<_Assembly>();
+ mockAssembly.Location.Returns(@"/hogehoge/OpenTween/OpenTween.exe");
+ MyCommon.EntryAssembly = mockAssembly;
+
+ Assert.That(MyCommon.GetErrorLogPath(), Is.SamePath(@"/hogehoge/OpenTween/ErrorLogs/"));
+ }
}
}
}
[Test]
+ [Ignore]
public void CancelAsyncTest()
{
using (var thumbbox = new TweetThumbnail())
return Twitter.ThirdPartyStatusUrlRegex.Matches(url).Cast<Match>()
.Select(x => x.Groups["StatusId"].Value).ToArray();
}
+
+ [Test]
+ public void FindTopOfReplyChainTest()
+ {
+ var posts = new Dictionary<long, PostClass>
+ {
+ {950L, new PostClass { StatusId = 950L, InReplyToStatusId = 0L }}, // このツイートが末端
+ {987L, new PostClass { StatusId = 987L, InReplyToStatusId = 950L }},
+ {999L, new PostClass { StatusId = 999L, InReplyToStatusId = 987L }},
+ {1000L, new PostClass { StatusId = 1000L, InReplyToStatusId = 999L }},
+ };
+ Assert.That(Twitter.FindTopOfReplyChain(posts, 1000L).StatusId, Is.EqualTo(950L));
+ Assert.That(Twitter.FindTopOfReplyChain(posts, 950L).StatusId, Is.EqualTo(950L));
+ Assert.That(() => Twitter.FindTopOfReplyChain(posts, 500L), Throws.ArgumentException);
+
+ posts = new Dictionary<long, PostClass>
+ {
+ // 1200L は posts の中に存在しない
+ {1210L, new PostClass { StatusId = 1210L, InReplyToStatusId = 1200L }},
+ {1220L, new PostClass { StatusId = 1220L, InReplyToStatusId = 1210L }},
+ {1230L, new PostClass { StatusId = 1230L, InReplyToStatusId = 1220L }},
+ };
+ Assert.That(Twitter.FindTopOfReplyChain(posts, 1230L).StatusId, Is.EqualTo(1210L));
+ Assert.That(Twitter.FindTopOfReplyChain(posts, 1210L).StatusId, Is.EqualTo(1210L));
+ }
}
}
/// </summary>
public readonly DateTime AccessLimitResetDate;
+ /// <summary>
+ /// API 実行回数制限値を取得した日時
+ /// </summary>
+ public readonly DateTime UpdatedAt;
+
public ApiLimit(int limitCount, int limitRemain, DateTime resetDate)
+ : this(limitCount, limitRemain, resetDate, DateTime.Now)
+ {
+ }
+
+ public ApiLimit(int limitCount, int limitRemain, DateTime resetDate, DateTime updatedAt)
{
this.AccessLimitCount = limitCount;
this.AccessLimitRemain = limitRemain;
this.AccessLimitResetDate = resetDate;
+ this.UpdatedAt = updatedAt;
}
public override bool Equals(object obj)
CheckPeriodAdjust.Checked = PeriodAdjust;
CheckStartupVersion.Checked = StartupVersion;
+ if (ApplicationSettings.VersionInfoUrl == null)
+ CheckStartupVersion.Enabled = false; // 更新チェック無効化
CheckStartupFollowers.Checked = StartupFollowers;
CheckFavRestrict.Checked = RestrictFavCheck;
CheckAlwaysTop.Checked = AlwaysTop;
/// </summary>
/// <remarks>
/// version.txt のフォーマットについては http://sourceforge.jp/projects/opentween/wiki/VersionTxt を参照。
+ /// 派生プロジェクトなどでこの機能を無効にする場合は null をセットして下さい。
/// </remarks>
- public const string VersionInfoUrl = "http://www.opentween.org/status/version.txt";
+ public static readonly string VersionInfoUrl = "http://www.opentween.org/status/version.txt";
//=====================================================================
// Twitter
///<param name="method">HTTP通信メソッド(POST/PUT)</param>
///<param name="requestUri">通信先URI</param>
///<param name="param">form-dataで指定する名前と文字列のディクショナリ</param>
- ///<param name="param">form-dataで指定する名前とバイナリファイル情報のリスト</param>
+ ///<param name="binaryFileInfo">form-dataで指定する名前とバイナリファイル情報のリスト</param>
///<param name="withCookie">通信にcookieを使用するか</param>
///<returns>引数で指定された内容を反映したHttpWebRequestオブジェクト</returns>
protected HttpWebRequest CreateRequest(string method,
///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
///</remarks>
///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
- ///<param name="contentText">[OUT]HTTP応答のボディデータを書き込むBitmap</param>
+ ///<param name="contentBitmap">[OUT]HTTP応答のボディデータを書き込むBitmap</param>
///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
///<param name="withCookie">通信にcookieを使用する</param>
///<returns>HTTP応答のステータスコード</returns>
///<summary>
///2バイト文字も考慮したUrlエンコード
///</summary>
- ///<param name="str">エンコードする文字列</param>
+ ///<param name="stringToEncode">エンコードする文字列</param>
///<returns>エンコード結果文字列</returns>
protected string UrlEncode(string stringToEncode)
{
/// 呼び出し元では戻されたurlをブラウザで開き、認証完了後PIN入力を受け付けて、リクエストトークンと共にAuthenticatePinFlowを呼び出す
/// </remarks>
/// <param name="requestTokenUrl">リクエストトークンの取得先URL</param>
- /// <param name="requestUri">ブラウザで開く認証用URLのベース</param>
+ /// <param name="authorizeUrl">ブラウザで開く認証用URLのベース</param>
/// <param name="requestToken">[OUT]認証要求で戻されるリクエストトークン。使い捨て</param>
/// <param name="authUri">[OUT]requestUriを元に生成された認証用URL。通常はリクエストトークンをクエリとして付加したUri</param>
/// <returns>取得結果真偽値</returns>
/// 事前にAuthenticatePinFlowRequestを呼んで、ブラウザで認証後に表示されるPINを入力してもらい、その値とともに呼び出すこと
/// </remarks>
/// <param name="accessTokenUrl">アクセストークンの取得先URL</param>
- /// <param name="requestUri">AuthenticatePinFlowRequestで取得したリクエストトークン</param>
+ /// <param name="requestToken">AuthenticatePinFlowRequestで取得したリクエストトークン</param>
/// <param name="pinCode">Webで認証後に表示されるPINコード</param>
/// <returns>取得結果真偽値</returns>
public HttpStatusCode AuthenticatePinFlow( string accessTokenUrl, string requestToken, string pinCode )
/// <summary>
/// OAuth認証のリクエストトークン取得。リクエストトークンと組み合わせた認証用のUriも生成する
/// </summary>
- /// <param name="accessTokenUrl">リクエストトークンの取得先URL</param>
+ /// <param name="requestTokenUrl">リクエストトークンの取得先URL</param>
/// <param name="authorizeUrl">ブラウザで開く認証用URLのベース</param>
/// <param name="requestToken">[OUT]取得したリクエストトークン</param>
/// <returns>取得結果真偽値</returns>
//認証なくても取得できるが、protectedユーザー分が抜ける
Dictionary<string, string> param = new Dictionary<string, string>();
+ param.Add("id", id.ToString());
param.Add("include_entities", "true");
// TODO: API v1.1 に存在しない API (旧 API で代替)
return httpCon.GetContent(GetMethod,
- CreateTwitterUri("/1/related_results/show/" + id + ".json"),
+ CreateTwitterUri("/1/related_results/show.json"),
param,
ref content,
this.apiStatusHeaders,
LabelInformation.Refresh();
}
- ///<summary>
- ///ダイアログに表示されるユーザー向けメッセージを設定あるいは取得する
- ///</summary>
- ///<param name="msg">表示するメッセージ</param>
- ///<returns>現在設定されているメッセージ</returns>
+ /// <summary>
+ /// ダイアログに表示されるユーザー向けメッセージを設定あるいは取得する
+ /// </summary>
+ /// <returns>現在設定されているメッセージ</returns>
public string InfoMessage
{
get { return _msg; }
}
}
- ///<summary>
- ///Servicerへ渡すパラメータ
- ///</summary>
- ///<param name="args">Servicerへ渡すパラメータ</param>
- ///<returns>現在設定されているServicerへ渡すパラメータ</returns>
+ /// <summary>
+ /// Servicerへ渡すパラメータ
+ /// </summary>
+ /// <returns>現在設定されているServicerへ渡すパラメータ</returns>
public object Argument
{
get { return _arg; }
/// <summary>
/// 指定されたバイト列から MemoryImage を作成します。
/// </summary>
- /// <param name="stream">読み込む対象となるバイト列</param>
+ /// <param name="bytes">読み込む対象となるバイト列</param>
/// <returns>作成された MemoryImage</returns>
public static MemoryImage CopyFromBytes(byte[] bytes)
{
Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow),
}
+ public static string GetErrorLogPath()
+ {
+ return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
+ }
+
public static void TraceOut(Exception ex, string Message)
{
var buf = ExceptionOutMessage(ex);
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))
{
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))
{
/// マルチバイト文字のコードは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)
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;
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MyLists));
- this.更新RToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.MenuItemReload = new System.Windows.Forms.ToolStripMenuItem();
this.ListRefreshButton = new System.Windows.Forms.Button();
this.ListsCheckedListBox = new System.Windows.Forms.CheckedListBox();
this.ContextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
- this.追加AToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.削除DToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.MenuItemAdd = new System.Windows.Forms.ToolStripMenuItem();
+ this.MenuItemDelete = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
this.CloseButton = new System.Windows.Forms.Button();
this.ContextMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
- // 更新RToolStripMenuItem
+ // MenuItemReload
//
- this.更新RToolStripMenuItem.Name = "更新RToolStripMenuItem";
- resources.ApplyResources(this.更新RToolStripMenuItem, "更新RToolStripMenuItem");
- this.更新RToolStripMenuItem.Click += new System.EventHandler(this.更新RToolStripMenuItem_Click);
+ this.MenuItemReload.Name = "MenuItemReload";
+ resources.ApplyResources(this.MenuItemReload, "MenuItemReload");
+ this.MenuItemReload.Click += new System.EventHandler(this.更新RToolStripMenuItem_Click);
//
// ListRefreshButton
//
// ContextMenuStrip1
//
this.ContextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.追加AToolStripMenuItem,
- this.削除DToolStripMenuItem,
+ this.MenuItemAdd,
+ this.MenuItemDelete,
this.ToolStripMenuItem1,
- this.更新RToolStripMenuItem});
+ this.MenuItemReload});
this.ContextMenuStrip1.Name = "ContextMenuStrip1";
resources.ApplyResources(this.ContextMenuStrip1, "ContextMenuStrip1");
this.ContextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.ContextMenuStrip1_Opening);
//
- // 追加AToolStripMenuItem
+ // MenuItemAdd
//
- this.追加AToolStripMenuItem.Name = "追加AToolStripMenuItem";
- resources.ApplyResources(this.追加AToolStripMenuItem, "追加AToolStripMenuItem");
- this.追加AToolStripMenuItem.Click += new System.EventHandler(this.追加AToolStripMenuItem_Click);
+ this.MenuItemAdd.Name = "MenuItemAdd";
+ resources.ApplyResources(this.MenuItemAdd, "MenuItemAdd");
+ this.MenuItemAdd.Click += new System.EventHandler(this.追加AToolStripMenuItem_Click);
//
- // 削除DToolStripMenuItem
+ // MenuItemDelete
//
- this.削除DToolStripMenuItem.Name = "削除DToolStripMenuItem";
- resources.ApplyResources(this.削除DToolStripMenuItem, "削除DToolStripMenuItem");
- this.削除DToolStripMenuItem.Click += new System.EventHandler(this.削除DToolStripMenuItem_Click);
+ this.MenuItemDelete.Name = "MenuItemDelete";
+ resources.ApplyResources(this.MenuItemDelete, "MenuItemDelete");
+ this.MenuItemDelete.Click += new System.EventHandler(this.削除DToolStripMenuItem_Click);
//
// ToolStripMenuItem1
//
#endregion
- internal System.Windows.Forms.ToolStripMenuItem 更新RToolStripMenuItem;
+ internal System.Windows.Forms.ToolStripMenuItem MenuItemReload;
internal System.Windows.Forms.Button ListRefreshButton;
internal System.Windows.Forms.CheckedListBox ListsCheckedListBox;
internal System.Windows.Forms.ContextMenuStrip ContextMenuStrip1;
- internal System.Windows.Forms.ToolStripMenuItem 追加AToolStripMenuItem;
- internal System.Windows.Forms.ToolStripMenuItem 削除DToolStripMenuItem;
+ internal System.Windows.Forms.ToolStripMenuItem MenuItemAdd;
+ internal System.Windows.Forms.ToolStripMenuItem MenuItemDelete;
internal System.Windows.Forms.ToolStripSeparator ToolStripMenuItem1;
internal System.Windows.Forms.Button CloseButton;
}
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
- <data name="追加AToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
+ <data name="MenuItemAdd.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 22</value>
</data>
- <data name="追加AToolStripMenuItem.Text" xml:space="preserve">
+ <data name="MenuItemAdd.Text" xml:space="preserve">
<value>追加(&A)</value>
</data>
- <data name="削除DToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
+ <data name="MenuItemDelete.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 22</value>
</data>
- <data name="削除DToolStripMenuItem.Text" xml:space="preserve">
+ <data name="MenuItemDelete.Text" xml:space="preserve">
<value>削除(&D)</value>
</data>
<data name="ToolStripMenuItem1.Size" type="System.Drawing.Size, System.Drawing">
<value>149, 6</value>
</data>
- <data name="更新RToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
+ <data name="MenuItemReload.Size" type="System.Drawing.Size, System.Drawing">
<value>152, 22</value>
</data>
- <data name="更新RToolStripMenuItem.Text" xml:space="preserve">
+ <data name="MenuItemReload.Text" xml:space="preserve">
<value>更新(&R)</value>
</data>
<data name="ContextMenuStrip1.Size" type="System.Drawing.Size, System.Drawing">
<data name="$this.Text" xml:space="preserve">
<value>MyLists</value>
</data>
- <data name=">>追加AToolStripMenuItem.Name" xml:space="preserve">
- <value>追加AToolStripMenuItem</value>
+ <data name=">>MenuItemAdd.Name" xml:space="preserve">
+ <value>MenuItemAdd</value>
</data>
- <data name=">>追加AToolStripMenuItem.Type" xml:space="preserve">
+ <data name=">>MenuItemAdd.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
- <data name=">>削除DToolStripMenuItem.Name" xml:space="preserve">
- <value>削除DToolStripMenuItem</value>
+ <data name=">>MenuItemDelete.Name" xml:space="preserve">
+ <value>MenuItemDelete</value>
</data>
- <data name=">>削除DToolStripMenuItem.Type" xml:space="preserve">
+ <data name=">>MenuItemDelete.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name=">>ToolStripMenuItem1.Name" xml:space="preserve">
<data name=">>ToolStripMenuItem1.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
- <data name=">>更新RToolStripMenuItem.Name" xml:space="preserve">
- <value>更新RToolStripMenuItem</value>
+ <data name=">>MenuItemReload.Name" xml:space="preserve">
+ <value>MenuItemReload</value>
</data>
- <data name=">>更新RToolStripMenuItem.Type" xml:space="preserve">
+ <data name=">>MenuItemReload.Type" xml:space="preserve">
<value>System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name=">>$this.Name" xml:space="preserve">
<EmbeddedResource Include="ListAvailable.zh-CHS.resx">
<DependentUpon>ListAvailable.cs</DependentUpon>
</EmbeddedResource>
+ <EmbeddedResource Include="ListManage.en.resx">
+ <DependentUpon>ListManage.cs</DependentUpon>
+ </EmbeddedResource>
<EmbeddedResource Include="ListManage.resx">
<DependentUpon>ListManage.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="AppendSettingDialog.zh-CHS.resx">
<DependentUpon>AppendSettingDialog.cs</DependentUpon>
</EmbeddedResource>
+ <EmbeddedResource Include="ListManage.zh-CHS.resx">
+ <DependentUpon>ListManage.cs</DependentUpon>
+ </EmbeddedResource>
<EmbeddedResource Include="TabsDialog.en.resx">
<DependentUpon>TabsDialog.cs</DependentUpon>
</EmbeddedResource>
// 既定値にすることができます:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.0.0")]
-[assembly: AssemblyFileVersion("1.0.9.1")]
+[assembly: AssemblyFileVersion("1.1.0.1")]
[assembly: InternalsVisibleTo("OpenTween.Tests")]
\ No newline at end of file
//------------------------------------------------------------------------------
// <auto-generated>
// このコードはツールによって生成されました。
-// ランタイム バージョン:4.0.30319.18033
+// ランタイム バージョン:4.0.30319.18034
//
// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
// コードが再生成されるときに損失したりします。
/// <summary>
/// 更新履歴
///
- ///==== Ver 1.1.0-beta1(2013/xx/xx)
+ ///==== Ver 1.1.1-beta1(2013/xx/xx)
///
- ///==== Ver 1.0.9(2013/04/07)
- /// * CHG: APIレートリミット関連の実装を修正
- /// * FIX: アイコンキャッシュの破棄時にエラーが出ることがある問題を修正
- /// * FIX: UserStreamsのunfollowイベントでエラーが起きていたのを修正
- /// * FIX: プロフィール画像・サムネイルの表示切り替え時にエラーが起きる場合がある問題を修正
- /// * FIX: タイムライン上のアイコンが空白のまま表示されない場合がある問題を修正 (thx @5px!)
- /// * FIX: アカウント追加時の初回認証に失敗する問題を修正 (thx @polka_roco_!)
- /// * FIX: ツールバー上のAPIレートリミット表示が正しく動作しなくなった問題を修正
- /// * FIX: ツイタマなど一部のTwitterクライアントから投稿されたツイートの改行が正しく表示されない問題を修正 (thx @ohta8801, @kossetsu_inryo!)
- /// * FIX: プロフィール編集画面で入力した [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。
+ ///==== Ver 1.1.0(2013/05/15)
+ /// * NEW: タブの表示位置を画面上部に変更可能に (thx @aokomoriuta!)
+ /// * NEW: mobile.twitter.com/<スクリーン名>/status/<ステータスID> のURLも関連発言表示の対象に追加
+ /// * NEW: Favstarなどサードパーティ製サービスのパーマリンクURLも関連発言表示の対象に追加
+ /// * CHG: エラーログの出力先を変更 (OpenTween.exe と同じ場所に ErrorLogs フォルダが作成されます)
+ /// * FIX: スペースが含まれているURLをブラウザで開こうとするとURLが分断されて複数のタブが開いてしまう問題を修正 (thx @5px!)
+ /// * FIX: 画面更新時にInvalidOperationExceptionのエラーが発生する不具合を修正
+ /// * FIX: 関連発言表示が非公開アカウントのツイートに対して機能しない問題を修正
+ /// * FIX: 言語設定が英語の状態でリスト管理 [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。
/// </summary>
internal static string ChangeLog {
get {
}
/// <summary>
+ /// 類似画像を検索 に類似しているローカライズされた文字列を検索します。
+ /// </summary>
+ internal static string SearchSimilarImageText {
+ get {
+ return ResourceManager.GetString("SearchSimilarImageText", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// [ユーザ・・・{0}] に類似しているローカライズされた文字列を検索します。
/// </summary>
internal static string SetFiltersText1 {
<data name="DeleteMenuText2" xml:space="preserve">
<value>Undo Retweet (&D)</value>
</data>
+ <data name="SearchSimilarImageText" xml:space="preserve">
+ <value>Search similar images</value>
+ </data>
</root>
\ No newline at end of file
<data name="BrowserStartFailed" xml:space="preserve">
<value>ブラウザの起動に失敗しました。エラーコード: {0}</value>
</data>
+ <data name="SearchSimilarImageText" xml:space="preserve">
+ <value>類似画像を検索</value>
+ </data>
</root>
\ No newline at end of file
更新履歴
-==== Ver 1.1.0-beta1(2013/xx/xx)
+==== Ver 1.1.1-beta1(2013/xx/xx)
* 当バージョンから Twitter API v1.1 に対応しています
* 旧 API v1 は太平洋標準時の 6/11 (Wed) に廃止される予定であると Twitter 社が予告しています
* API v1.1 の使用に不都合がある場合は、ステータスバーのレートリミット表示をクリックすることで API v1 と切り替えることができます。
* NEW: API v1.1 に対応
+ * NEW: サムネイル画像表示の右クリックメニューにGoogle画像検索を開くための項目を追加 (thx @moccos!)
+ * FIX: 画像投稿画面をキャンセルした場合にタイムライン表示に復帰できない不具合を修正 (thx @polka_roco_!)
+
+==== Ver 1.1.0(2013/05/15)
* NEW: タブの表示位置を画面上部に変更可能に (thx @aokomoriuta!)
* NEW: mobile.twitter.com/<スクリーン名>/status/<ステータスID> のURLも関連発言表示の対象に追加
* NEW: Favstarなどサードパーティ製サービスのパーマリンクURLも関連発言表示の対象に追加
+ * CHG: エラーログの出力先を変更 (OpenTween.exe と同じ場所に ErrorLogs フォルダが作成されます)
* FIX: スペースが含まれているURLをブラウザで開こうとするとURLが分断されて複数のタブが開いてしまう問題を修正 (thx @5px!)
* FIX: 画面更新時にInvalidOperationExceptionのエラーが発生する不具合を修正
+ * FIX: 関連発言表示が非公開アカウントのツイートに対して機能しない問題を修正
+ * FIX: 言語設定が英語の状態でリスト管理の画面が翻訳されずに表示される問題を修正
==== Ver 1.0.9(2013/04/07)
* CHG: APIレートリミット関連の実装を修正
{
foreach (var flt in this.BodyFilter)
{
- destination.BodyFilter.Add(string.Copy(flt));
+ destination.BodyFilter.Add(flt);
}
}
{
foreach (var flt in this.ExBodyFilter)
{
- destination.ExBodyFilter.Add(string.Copy(flt));
+ destination.ExBodyFilter.Add(flt);
}
}
class SimpleThumbnailService : IThumbnailService
{
protected Regex regex;
- protected string replacement;
+ protected string thumb_replacement;
+ protected string fullsize_replacement;
public SimpleThumbnailService(string pattern, string replacement)
{
this.regex = new Regex(pattern, RegexOptions.IgnoreCase);
- this.replacement = replacement;
+ this.thumb_replacement = replacement;
+ }
+
+ public SimpleThumbnailService(string pattern, string replacement, string file_replacement)
+ {
+ this.regex = new Regex(pattern, RegexOptions.IgnoreCase);
+ this.thumb_replacement = replacement;
+ this.fullsize_replacement = file_replacement;
}
public override ThumbnailInfo GetThumbnailInfo(string url, PostClass post)
ImageUrl = url,
ThumbnailUrl = thumbnailUrl,
TooltipText = null,
+ FullSizeImageUrl = ReplaceUrl(url, this.fullsize_replacement)
};
}
protected string ReplaceUrl(string url)
{
+ return ReplaceUrl(url, this.thumb_replacement);
+ }
+
+ protected string ReplaceUrl(string url, string replacement)
+ {
+ if (replacement == null) return null;
var match = this.regex.Match(url);
- return match.Success ? match.Result(this.replacement) : null;
+ return match.Success ? match.Result(replacement) : null;
}
}
}
new ImgAzyobuziNet(autoupdate: true),
// ImgUr
- new SimpleThumbnailService(@"^http://(?:i\.)?imgur\.com/(\w+)(?:\..{3})?$", "http://img.imgur.com/${1}l.jpg"),
+ new SimpleThumbnailService(
+ @"^http://(?:i\.)?imgur\.com/(\w+)(?:\..{3})?$",
+ "http://img.imgur.com/${1}l.jpg",
+ "http://img.imgur.com/${1}.jpg"),
// Twitpic
- new SimpleThumbnailService(@"^http://(www\.)?twitpic\.com/(?<photoId>\w+)(/full/?)?$", "http://twitpic.com/show/thumb/${photoId}"),
+ new SimpleThumbnailService(
+ @"^http://(www\.)?twitpic\.com/(?<photoId>\w+)(/full/?)?$",
+ "http://twitpic.com/show/thumb/${photoId}",
+ "http://twitpic.com/show/large/${photoId}"),
// yfrog
- new SimpleThumbnailService(@"^http://yfrog\.com/(\w+)$", "${0}:small"),
+ new SimpleThumbnailService(
+ @"^http://yfrog\.com/(\w+)$",
+ "${0}:small",
+ "${0}"),
// Lockerz
- new SimpleThumbnailService(@"^http://(tweetphoto\.com/[0-9]+|pic\.gd/[a-z0-9]+|(lockerz|plixi)\.com/[ps]/[0-9]+)$", "http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=${0}"),
+ new SimpleThumbnailService(
+ @"^http://(tweetphoto\.com/[0-9]+|pic\.gd/[a-z0-9]+|(lockerz|plixi)\.com/[ps]/[0-9]+)$",
+ "http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=${0}",
+ "http://api.plixi.com/api/tpapi.svc/imagefromurl?size=big&url=${0}"),
// MobyPicture
- new SimpleThumbnailService(@"^http://moby\.to/(\w+)$", "http://mobypicture.com/?${1}:small"),
+ new SimpleThumbnailService(
+ @"^http://moby\.to/(\w+)$",
+ "http://mobypicture.com/?${1}:small",
+ "http://mobypicture.com/?${1}:full"),
// 携帯百景
- new SimpleThumbnailService(@"^http://movapic\.com/pic/(\w+)$", "http://image.movapic.com/pic/s_${1}.jpeg"),
+ new SimpleThumbnailService(
+ @"^http://movapic\.com/pic/(\w+)$",
+ "http://image.movapic.com/pic/s_${1}.jpeg",
+ "http://image.movapic.com/pic/m_${1}.jpeg"),
// はてなフォトライフ
- new SimpleThumbnailService(@"^http://f\.hatena\.ne\.jp/(([a-z])[a-z0-9_-]{1,30}[a-z0-9])/((\d{8})\d+)$", "http://img.f.hatena.ne.jp/images/fotolife/${2}/${1}/${4}/${3}_120.jpg"),
+ new SimpleThumbnailService(
+ @"^http://f\.hatena\.ne\.jp/(([a-z])[a-z0-9_-]{1,30}[a-z0-9])/((\d{8})\d+)$",
+ "http://img.f.hatena.ne.jp/images/fotolife/${2}/${1}/${4}/${3}_120.jpg",
+ "http://img.f.hatena.ne.jp/images/fotolife/${2}/${1}/${4}/${3}.jpg"),
// PhotoShare
new SimpleThumbnailService(@"^http://(?:www\.)?bcphotoshare\.com/photos/\d+/(\d+)$", "http://images.bcphotoshare.com/storages/${1}/thumb180.jpg"),
new PhotoShareShortlink(@"^http://bctiny\.com/p(\w+)$"),
// img.ly
- new SimpleThumbnailService(@"^http://img\.ly/(\w+)$", "http://img.ly/show/thumb/${1}"),
+ new SimpleThumbnailService(@"^http://img\.ly/(\w+)$",
+ "http://img.ly/show/thumb/${1}",
+ "http://img.ly/show/full/${1}"),
// Twitgoo
- new SimpleThumbnailService(@"^http://twitgoo\.com/(\w+)$", "http://twitgoo.com/${1}/mini"),
+ new SimpleThumbnailService(@"^http://twitgoo\.com/(\w+)$",
+ "http://twitgoo.com/${1}/mini",
+ "http://twitgoo.com/${1}/img"),
// youtube
new Youtube(@"^http://(?:(www\.youtube\.com)|(youtu\.be))/(watch\?v=)?(?<videoid>([\w\-]+))", "http://i.ytimg.com/vi/${videoid}/default.jpg"),
new Nicovideo(@"^http://(?:(www|ext)\.nicovideo\.jp/watch|nico\.ms)/(?:sm|nm)?([0-9]+)(\?.+)?$", "http://www.nicovideo.jp/api/getthumbinfo/${id}"),
// ニコニコ静画
- new SimpleThumbnailService(@"^http://(?:seiga\.nicovideo\.jp/seiga/|nico\.ms/)im(?<id>\d+)", "http://lohas.nicoseiga.jp/thumb/${id}q?"),
+ new SimpleThumbnailService(
+ @"^http://(?:seiga\.nicovideo\.jp/seiga/|nico\.ms/)im(?<id>\d+)",
+ "http://lohas.nicoseiga.jp/thumb/${id}q?",
+ "http://lohas.nicoseiga.jp/thumb/${id}l?"),
// pixiv
new MetaThumbnailService(@"^http://www\.pixiv\.net/(member_illust|index)\.php\?(?=.*mode=(medium|big))(?=.*illust_id=(?<illustId>[0-9]+)).*$"),
new MetaThumbnailService(@"^http://www\.flickr\.com/.+$"),
// フォト蔵
- new SimpleThumbnailService(@"^http://photozou\.jp/photo/show/(?<userId>[0-9]+)/(?<photoId>[0-9]+)", "http://photozou.jp/p/thumb/${photoId}"),
+ new SimpleThumbnailService(
+ @"^http://photozou\.jp/photo/show/(?<userId>[0-9]+)/(?<photoId>[0-9]+)",
+ "http://photozou.jp/p/thumb/${photoId}",
+ "http://photozou.jp/p/img/${photoId}"),
// TwitVideo
new SimpleThumbnailService(@"^http://twitvideo\.jp/(\w+)$", "http://twitvideo.jp/img/thumb/${1}"),
new SimpleThumbnailService(@"^http://c[0-9]+\.cdn[0-9]+\.cloudfiles\.rackspacecloud\.com/[a-z_0-9]+", "${0}"),
// Instagram
- new SimpleThumbnailService(@"^http://instagr\.am/p/.+/", "${0}media/?size=m"),
+ new SimpleThumbnailService(
+ @"^http://instagr\.am/p/.+/",
+ "${0}media/?size=m",
+ "${0}media/?size=l"),
// pikubo
- new SimpleThumbnailService(@"^http://pikubo\.me/([a-z0-9-_]+)", "http://pikubo.me/q/${1}"),
+ new SimpleThumbnailService(
+ @"^http://pikubo\.me/([a-z0-9-_]+)",
+ "http://pikubo.me/q/${1}",
+ "http://pikubo.me/l/${1}"),
// Foursquare
new Services.Foursquare(@"^https?://(4sq|foursquare)\.com/.+"),
new Tinami(@"^http://www\.tinami\.com/view/(?<ContentId>\d+)$", "http://api.tinami.com/content/info?cont_id=${ContentId}&api_key=" + ApplicationSettings.TINAMIApiKey),
// pic.twitter.com
- new SimpleThumbnailService(@"^https?://p\.twimg\.com/.*$", "${0}:thumb"),
+ new SimpleThumbnailService(
+ @"^https?://p\.twimg\.com/.*$",
+ "${0}:thumb",
+ "${0}"),
// TwitrPix
- new SimpleThumbnailService(@"^http://twitrpix\.com/(\w+)$", "http://img.twitrpix.com/thumb/${1}"),
+ new SimpleThumbnailService(
+ @"^http://twitrpix\.com/(\w+)$",
+ "http://img.twitrpix.com/thumb/${1}",
+ "http://img.twitrpix.com/${1}"),
// Pckles
- new SimpleThumbnailService(@"^https?://pckles\.com/\w+/\w+$", "${0}.resize.jpg"),
+ new SimpleThumbnailService(
+ @"^https?://pckles\.com/\w+/\w+$",
+ "${0}.resize.jpg",
+ "${0}.jpg"),
// via.me
new ViaMe(@"^https?://via\.me/-(\w+)$", "http://via.me/api/v1/posts/$1"),
public string ImageUrl { get; set; }
public string ThumbnailUrl { get; set; }
public string TooltipText { get; set; }
+ public string FullSizeImageUrl { get; set; }
}
}
this.ToolTip1.SetToolTip(this.tweetThumbnail1, resources.GetString("tweetThumbnail1.ToolTip"));
this.tweetThumbnail1.ThumbnailLoading += new System.EventHandler(this.tweetThumbnail1_ThumbnailLoading);
this.tweetThumbnail1.ThumbnailDoubleClick += new System.EventHandler<OpenTween.ThumbnailDoubleClickEventArgs>(this.tweetThumbnail1_ThumbnailDoubleClick);
+ this.tweetThumbnail1.ThumbnailImageSearchClick += new System.EventHandler<OpenTween.ThumbnailImageSearchEventArgs>(this.tweetThumbnail1_ThumbnailImageSearchClick);
//
// MenuStrip1
//
private void CheckNewVersion(bool startup = false)
{
+ if (ApplicationSettings.VersionInfoUrl == null)
+ return; // 更新チェック無効化
+
if (string.IsNullOrEmpty(MyCommon.fileVersion))
{
return;
if (MyCommon._endingFlag) return;
- //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
- if (SettingDialog.StartupVersion)
- CheckNewVersion(true);
+ if (ApplicationSettings.VersionInfoUrl != null)
+ {
+ //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
+ if (SettingDialog.StartupVersion)
+ CheckNewVersion(true);
+ }
+ else
+ {
+ // ApplicationSetting.cs の設定により更新チェックが無効化されている場合
+ this.VerUpMenuItem.Enabled = false;
+ this.VerUpMenuItem.Available = false;
+ this.ToolStripSeparator16.Available = false; // VerUpMenuItem の一つ上にあるセパレータ
+ }
// 取得失敗の場合は再試行する
if (!tw.GetFollowersSuccess && SettingDialog.StartupFollowers)
{
ImageSelectedPicture.Image = ImageSelectedPicture.InitialImage;
ImageSelectedPicture.Tag = MyCommon.UploadFileType.Invalid;
+ TimelinePanel.Visible = true;
+ TimelinePanel.Enabled = true;
ImageSelectionPanel.Visible = false;
ImageSelectionPanel.Enabled = false;
((DetailsListView)ListTab.SelectedTab.Tag).Focus();
private void ImageCancelButton_Click(object sender, EventArgs e)
{
ImagefilePathText.CausesValidation = false;
+ TimelinePanel.Visible = true;
+ TimelinePanel.Enabled = true;
ImageSelectionPanel.Visible = false;
ImageSelectionPanel.Enabled = false;
((DetailsListView)ListTab.SelectedTab.Tag).Focus();
this.OpenThumbnailPicture(e.Thumbnail);
}
+ private void tweetThumbnail1_ThumbnailImageSearchClick(object sender, ThumbnailImageSearchEventArgs e)
+ {
+ this.OpenUriAsync(e.ImageUrl);
+ }
+
private void OpenThumbnailPicture(ThumbnailInfo thumbnail)
{
this.OpenUriAsync(Uri.EscapeUriString(thumbnail.ImageUrl));
public event EventHandler ThumbnailLoading;
public event EventHandler<ThumbnailDoubleClickEventArgs> ThumbnailDoubleClick;
+ public event EventHandler<ThumbnailImageSearchEventArgs> ThumbnailImageSearchClick;
public ThumbnailInfo Thumbnail
{
picbox.Tag = thumb;
picbox.LoadAsync(thumb.ThumbnailUrl);
+ picbox.ContextMenu = CreateContextMenu(thumb);
var tooltipText = thumb.TooltipText;
if (!string.IsNullOrEmpty(tooltipText))
return this.task;
}
+ private ContextMenu CreateContextMenu(ThumbnailInfo thumb)
+ {
+ var contextMenu = new ContextMenu();
+ contextMenu.MenuItems.Add(CreateImageSearchMenuItem(thumb));
+ return contextMenu;
+ }
+
+ private MenuItem CreateImageSearchMenuItem(ThumbnailInfo thumb)
+ {
+ var item = new MenuItem();
+ item.Text = Properties.Resources.SearchSimilarImageText;
+ string search_targe_url =
+ thumb.FullSizeImageUrl != null
+ ? thumb.FullSizeImageUrl
+ : thumb.ThumbnailUrl != null
+ ? thumb.ThumbnailUrl
+ : null;
+
+ if (search_targe_url != null)
+ {
+ item.Click += (sender, e) =>
+ {
+ string uri = GetImageSearchUri(search_targe_url);
+ if (this.ThumbnailImageSearchClick != null)
+ {
+ this.ThumbnailImageSearchClick(this, new ThumbnailImageSearchEventArgs(uri));
+ }
+ };
+ }
+ else
+ {
+ item.Enabled = false;
+ }
+
+ return item;
+ }
+
+ private string GetImageSearchUri(string image_uri)
+ {
+ return @"https://www.google.com/searchbyimage?image_url=" + Uri.EscapeDataString(image_uri);
+ }
+
protected virtual List<ThumbnailInfo> GetThumbailInfo(PostClass post)
{
return ThumbnailGenerator.GetThumbnails(post);
this.Thumbnail = thumbnail;
}
}
+
+ public class ThumbnailImageSearchEventArgs : EventArgs
+ {
+ public string ImageUrl { get; private set; }
+
+ public ThumbnailImageSearchEventArgs(string url)
+ {
+ this.ImageUrl = url;
+ }
+ }
}
return CreatePostsFromJson(content, MyCommon.WORKERTYPE.List, tab, read, count, ref tab.OldestId);
}
-
- private PostClass CheckReplyToPost(List<PostClass> relPosts)
+ /// <summary>
+ /// startStatusId からリプライ先の発言を辿る。発言は posts 以外からは検索しない。
+ /// </summary>
+ /// <returns>posts の中から検索されたリプライチェインの末端</returns>
+ internal static PostClass FindTopOfReplyChain(IDictionary<Int64, PostClass> posts, Int64 startStatusId)
{
- var tmpPost = relPosts[0];
- PostClass lastPost = null;
- while (tmpPost != null)
- {
- if (tmpPost.InReplyToStatusId == 0) return null;
- lastPost = tmpPost;
- var replyToPost = from p in relPosts
- where p.StatusId == tmpPost.InReplyToStatusId
- select p;
- tmpPost = replyToPost.FirstOrDefault();
- }
- return lastPost;
+ if (!posts.ContainsKey(startStatusId))
+ throw new ArgumentException("startStatusId (" + startStatusId + ") が posts の中から見つかりませんでした。");
+
+ var nextPost = posts[startStatusId];
+ while (nextPost.InReplyToStatusId != 0)
+ {
+ if (!posts.ContainsKey(nextPost.InReplyToStatusId))
+ break;
+ nextPost = posts[nextPost.InReplyToStatusId];
+ }
+
+ return nextPost;
}
public string GetRelatedResult(bool read, TabClass tab)
{
var rslt = "";
- var relPosts = new List<PostClass>();
+ var relPosts = new Dictionary<Int64, PostClass>();
if (tab.RelationTargetPost.TextFromApi.Contains("@") && tab.RelationTargetPost.InReplyToStatusId == 0)
{
//検索結果対応
tab.RelationTargetPost = p;
}
}
- relPosts.Add(tab.RelationTargetPost.Clone());
- var tmpPost = relPosts[0];
+ relPosts.Add(tab.RelationTargetPost.StatusId, tab.RelationTargetPost.Clone());
+
+ // 一周目: 非公式な related_results API を使用してリプライチェインを辿る
+ var nextPost = relPosts[tab.RelationTargetPost.StatusId];
+ var loopCount = 1;
do
{
- rslt = this.GetRelatedResultsApi(read, tmpPost, tab, relPosts);
+ rslt = this.GetRelatedResultsApi(nextPost, relPosts);
if (!string.IsNullOrEmpty(rslt)) break;
- tmpPost = CheckReplyToPost(relPosts);
- } while (tmpPost != null);
+ nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId);
+ } while (nextPost.InReplyToStatusId != 0 && loopCount++ <= 5);
+
+ // 二周目: in_reply_to_status_id を使用してリプライチェインを辿る
+ nextPost = FindTopOfReplyChain(relPosts, tab.RelationTargetPost.StatusId);
+ loopCount = 1;
+ while (nextPost.InReplyToStatusId != 0 && loopCount++ <= 20)
+ {
+ var inReplyToId = nextPost.InReplyToStatusId;
+
+ var inReplyToPost = TabInformations.GetInstance()[inReplyToId];
+ if (inReplyToPost != null)
+ {
+ inReplyToPost = inReplyToPost.Clone();
+ }
+ else
+ {
+ var errorText = this.GetStatusApi(read, inReplyToId, ref inReplyToPost);
+ if (!string.IsNullOrEmpty(errorText))
+ {
+ rslt = errorText;
+ break;
+ }
+ }
+
+ relPosts.Add(inReplyToPost.StatusId, inReplyToPost);
+
+ nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId);
+ }
+
+ //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む
+ var text = tab.RelationTargetPost.Text;
+ var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>()
+ .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>());
+ foreach (var _match in ma)
+ {
+ Int64 _statusId;
+ if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId))
+ {
+ if (relPosts.ContainsKey(_statusId))
+ continue;
+
+ PostClass p = null;
+ var _post = TabInformations.GetInstance()[_statusId];
+ if (_post == null)
+ {
+ this.GetStatusApi(read, _statusId, ref p);
+ }
+ else
+ {
+ p = _post.Clone();
+ }
+
+ if (p != null)
+ relPosts.Add(p.StatusId, p);
+ }
+ }
+
+ relPosts.Values.ToList().ForEach(p =>
+ {
+ if (p.IsMe && !read && this._readOwnPost)
+ p.IsRead = true;
+ else
+ p.IsRead = read;
+
+ p.RelTabName = tab.TabName;
+ TabInformations.GetInstance().AddPost(p);
+ });
- relPosts.ForEach(p => TabInformations.GetInstance().AddPost(p));
return rslt;
}
- private string GetRelatedResultsApi(bool read,
- PostClass post,
- TabClass tab,
- List<PostClass> relatedPosts)
+ private string GetRelatedResultsApi(PostClass post,
+ IDictionary<Int64, PostClass> relatedPosts)
{
if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return "";
return "Invalid Json!";
}
- var targetItem = post;
- if (targetItem == null)
- {
- return "";
- }
- else
- {
- targetItem = targetItem.Clone();
- }
- targetItem.RelTabName = tab.TabName;
- TabInformations.GetInstance().AddPost(targetItem);
-
- PostClass replyToItem = null;
- var replyToUserName = targetItem.InReplyToUser;
- if (targetItem.InReplyToStatusId > 0 && TabInformations.GetInstance()[targetItem.InReplyToStatusId] != null)
- {
- replyToItem = TabInformations.GetInstance()[targetItem.InReplyToStatusId].Clone();
- replyToItem.IsRead = read;
- if (replyToItem.IsMe && !read && _readOwnPost) replyToItem.IsRead = true;
- replyToItem.RelTabName = tab.TabName;
- }
-
- var replyAdded = false;
foreach (var relatedData in items)
{
foreach (var result in relatedData.Results)
{
var item = CreatePostsFromStatusData(result.Status);
if (item == null) continue;
- if (targetItem.InReplyToStatusId == item.StatusId)
- {
- replyToItem = null;
- replyAdded = true;
- }
- item.IsRead = read;
- if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
- if (tab != null) item.RelTabName = tab.TabName;
//非同期アイコン取得&StatusDictionaryに追加
- relatedPosts.Add(item);
- }
- }
- if (replyToItem != null)
- {
- relatedPosts.Add(replyToItem);
- }
- else if (targetItem.InReplyToStatusId > 0 && !replyAdded)
- {
- PostClass p = null;
- var rslt = "";
- rslt = GetStatusApi(read, targetItem.InReplyToStatusId, ref p);
- if (string.IsNullOrEmpty(rslt))
- {
- p.IsRead = read;
- p.RelTabName = tab.TabName;
- relatedPosts.Add(p);
+ if (!relatedPosts.ContainsKey(item.StatusId))
+ relatedPosts.Add(item.StatusId, item);
}
- return rslt;
}
- //発言者・返信先ユーザーの直近10発言取得
- //var rslt = this.GetUserTimelineApi(read, 10, "", tab);
- //if (!string.IsNullOrEmpty(rslt)) return rslt;
- //if (!string.IsNullOrEmpty(replyToUserName))
- // rslt = this.GetUserTimelineApi(read, 10, replyToUserName, tab);
- //}
- //return rslt;
-
- //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む
- var text = tab.RelationTargetPost.Text;
- var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>()
- .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>());
- foreach (var _match in ma)
- {
- Int64 _statusId;
- if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId))
- {
- PostClass p = null;
- var _post = TabInformations.GetInstance()[_statusId];
- if (_post == null)
- {
- var rslt = this.GetStatusApi(read, _statusId, ref p);
- }
- else
- {
- p = _post.Clone();
- }
- if (p != null)
- {
- p.IsRead = read;
- p.RelTabName = tab.TabName;
- relatedPosts.Add(p);
- }
- }
- }
return "";
}
var mS = Regex.Match(post.Source, ">(?<source>.+)<");
if (mS.Success)
{
- post.SourceHtml = string.Copy(ShortUrl.Resolve(PreProcessUrl(post.Source), false));
+ post.SourceHtml = ShortUrl.Resolve(PreProcessUrl(post.Source), false);
post.Source = WebUtility.HtmlDecode(mS.Result("${source}"));
}
else
}
else
{
- post.SourceHtml = string.Copy(post.Source);
+ post.SourceHtml = post.Source;
}
}
}
}
}
#endregion
+
#region "タスクトレイアイコンのクリック"
// 指定されたクラス名およびウィンドウ名と一致するトップレベルウィンドウのハンドルを取得します
[DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
}
#endregion
- //画面をブリンクするためのWin32API。起動時に10ページ読み取りごとに継続確認メッセージを表示する際の通知強調用
- [DllImport("user32.dll")]
- private static extern int FlashWindow(
- IntPtr hwnd,
- bool bInvert);
-
#region "画面ブリンク用"
public static bool FlashMyWindow(IntPtr hwnd,
FlashSpecification flashType,
private const Int32 FLASHW_TIMERNOFG = 0xC;
#endregion
- [DllImport("user32.dll")]
- public static extern bool ValidateRect(
- IntPtr hwnd,
- IntPtr rect);
-
#region "スクリーンセーバー起動中か判定"
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(
// returns non-zero value if function succeeds
//スクリーンセーバーが起動中かを取得する定数
- private const int SPI_GETSCREENSAVERRUNNING = 0x61;
+ private const int SPI_GETSCREENSAVERRUNNING = 0x0072;
public static bool IsScreenSaverRunning()
{
- var ret = 0;
var isRunning = false;
-
- ret = SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0);
+ SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0);
return isRunning;
}
#endregion