{
var mock = new Mock<MicrosoftTranslatorApi>(http);
mock.Setup(x => x.GetAccessTokenAsync())
- .ReturnsAsync(Tuple.Create("1234abcd", TimeSpan.FromSeconds(1000)));
+ .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
var translateApi = mock.Object;
{
var mock = new Mock<MicrosoftTranslatorApi>();
mock.Setup(x => x.GetAccessTokenAsync())
- .ReturnsAsync(Tuple.Create("1234abcd", TimeSpan.FromSeconds(1000)));
+ .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
var translateApi = mock.Object;
{
var mock = new Mock<MicrosoftTranslatorApi>();
mock.Setup(x => x.GetAccessTokenAsync())
- .ReturnsAsync(Tuple.Create("5678efgh", TimeSpan.FromSeconds(1000)));
+ .ReturnsAsync(("5678efgh", TimeSpan.FromSeconds(1000)));
var translateApi = mock.Object;
translateApi.AccessToken = "1234abcd";
var result = await translateApi.GetAccessTokenAsync()
.ConfigureAwait(false);
- var expectedToken = Tuple.Create(@"12345acbde", TimeSpan.FromSeconds(3600));
+ var expectedToken = (@"12345acbde", TimeSpan.FromSeconds(3600));
Assert.Equal(expectedToken, result);
Assert.Equal(0, mockHandler.QueueCount);
""expires_in"": 3600
}
");
- var expected = Tuple.Create(@"12345acbde", TimeSpan.FromSeconds(3600));
+ var expected = (@"12345acbde", TimeSpan.FromSeconds(3600));
Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
}
""token_type"": ""bearer""
}
");
- var expected = Tuple.Create(@"12345acbde", TimeSpan.Zero);
+ var expected = (@"12345acbde", TimeSpan.Zero);
Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
}
}
{
var data = new DataObject("text/x-moz-url", memstream);
- var expected = new Tuple<string, string>("https://twitter.com/", "Twitter");
+ var expected = ("https://twitter.com/", "Twitter");
Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
}
}
{
var data = new DataObject("IESiteModeToUrl", memstream);
- var expected = new Tuple<string, string>("https://twitter.com/", "Twitter");
+ var expected = ("https://twitter.com/", "Twitter");
Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
}
}
{
var data = new DataObject("UniformResourceLocatorW", memstream);
- var expected = new Tuple<string, string>("https://twitter.com/", null);
+ var expected = ("https://twitter.com/", (string)null);
Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
}
}
{
var sourceHtml = "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("Twitter Web Client", result.Item1);
- Assert.Equal(new Uri("http://twitter.com/"), result.Item2);
+ var expected = ("Twitter Web Client", new Uri("http://twitter.com/"));
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
{
var sourceHtml = "web";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("web", result.Item1);
- Assert.Equal(null, result.Item2);
+ var expected = ("web", (Uri)null);
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
// 参照: https://twitter.com/kim_upsilon/status/477796052049752064
var sourceHtml = "<a href=\"erased_45416\" rel=\"nofollow\">erased_45416</a>";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("erased_45416", result.Item1);
- Assert.Equal(new Uri("https://twitter.com/erased_45416"), result.Item2);
+ var expected = ("erased_45416", new Uri("https://twitter.com/erased_45416"));
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
// 参照: https://twitter.com/kim_upsilon/status/595156014032244738
var sourceHtml = "";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("", result.Item1);
- Assert.Equal(null, result.Item2);
+ var expected = ("", (Uri)null);
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
{
string sourceHtml = null;
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("", result.Item1);
- Assert.Equal(null, result.Item2);
+ var expected = ("", (Uri)null);
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
{
string sourceHtml = "<a href=\"http://example.com/?aaa=123&bbb=456\" rel=\"nofollow\"><<hogehoge>></a>";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("<<hogehoge>>", result.Item1);
- Assert.Equal(new Uri("http://example.com/?aaa=123&bbb=456"), result.Item2);
+ var expected = ("<<hogehoge>>", new Uri("http://example.com/?aaa=123&bbb=456"));
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
{
string sourceHtml = "<<hogehoge>>";
- var result = Twitter.ParseSource(sourceHtml);
- Assert.Equal("<<hogehoge>>", result.Item1);
- Assert.Equal(null, result.Item2);
+ var expected = ("<<hogehoge>>", (Uri)null);
+ Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
}
[Fact]
if (this.AccessToken != null && this.RefreshAccessTokenAt > DateTime.Now)
return;
- var accessToken = await this.GetAccessTokenAsync()
+ var (accessToken, expiresIn) = await this.GetAccessTokenAsync()
.ConfigureAwait(false);
- this.AccessToken = accessToken.Item1;
+ this.AccessToken = accessToken;
// expires_in の示す時刻より 30 秒早めに再発行する
- this.RefreshAccessTokenAt = DateTime.Now + accessToken.Item2 - TimeSpan.FromSeconds(30);
+ this.RefreshAccessTokenAt = DateTime.Now + expiresIn - TimeSpan.FromSeconds(30);
}
- internal virtual async Task<Tuple<string, TimeSpan>> GetAccessTokenAsync()
+ internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
{
var param = new Dictionary<string, string>
{
}
}
- internal static Tuple<string, TimeSpan> ParseOAuthCredential(byte[] responseBytes)
+ internal static (string AccessToken, TimeSpan ExpiresIn) ParseOAuthCredential(byte[] responseBytes)
{
using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max))
{
expiresInSeconds = 0;
}
- return Tuple.Create(accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));
+ return (accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));
}
}
}
var rateLimits =
from res in json.Resources
from item in res.Value
- select new {
- endpointName = item.Key,
- limit = new ApiLimit(
+ select (
+ EndpointName: item.Key,
+ Limit: new ApiLimit(
item.Value.Limit,
item.Value.Remaining,
UnixEpoch.AddSeconds(item.Value.Reset).ToLocalTime()
- ),
- };
+ )
+ );
- this.AccessLimit.AddAll(rateLimits.ToDictionary(x => x.endpointName, x => x.limit));
+ this.AccessLimit.AddAll(rateLimits.ToDictionary(x => x.EndpointName, x => x.Limit));
}
protected virtual void OnAccessLimitUpdated(AccessLimitUpdatedEventArgs e)
<DependentUpon>TweetDetailsView.cs</DependentUpon>
</Compile>
<Compile Include="TweetExtractor.cs" />
+ <Compile Include="ValueTuple.cs" />
<Compile Include="WaitingDialog.cs">
<SubType>Form</SubType>
</Compile>
}
private Stack<ReplyChain> replyChains; //[, ]でのリプライ移動の履歴
- private Stack<Tuple<TabPage, PostClass>> selectPostChains = new Stack<Tuple<TabPage, PostClass>>(); //ポスト選択履歴
+ private Stack<ValueTuple<TabPage, PostClass>> selectPostChains = new Stack<ValueTuple<TabPage, PostClass>>(); //ポスト選択履歴
//検索処理タイプ
internal enum SEARCHTYPE
try
{
this.selectPostChains.Pop();
- var tabPostPair = this.selectPostChains.Peek();
+ var (tabPage, post) = this.selectPostChains.Peek();
- if (!this.ListTab.TabPages.Contains(tabPostPair.Item1)) continue; //該当タブが存在しないので無視
+ if (!this.ListTab.TabPages.Contains(tabPage)) continue; //該当タブが存在しないので無視
- if (tabPostPair.Item2 != null)
+ if (post != null)
{
- idx = this._statuses.Tabs[tabPostPair.Item1.Text].IndexOf(tabPostPair.Item2.StatusId);
+ idx = this._statuses.Tabs[tabPage.Text].IndexOf(post.StatusId);
if (idx == -1) continue; //該当ポストが存在しないので無視
}
- tp = tabPostPair.Item1;
+ tp = tabPage;
this.selectPostChains.Pop();
}
int count = this.selectPostChains.Count;
if (count > 0)
{
- var p = this.selectPostChains.Peek();
- if (p.Item1 == this._curTab)
+ var (tabPage, post) = this.selectPostChains.Peek();
+ if (tabPage == this._curTab)
{
- if (p.Item2 == this._curPost) return; //最新の履歴と同一
- if (p.Item2 == null) this.selectPostChains.Pop(); //置き換えるため削除
+ if (post == this._curPost) return; //最新の履歴と同一
+ if (post == null) this.selectPostChains.Pop(); //置き換えるため削除
}
}
if (count >= 2500) TrimPostChain();
- this.selectPostChains.Push(Tuple.Create(this._curTab, this._curPost));
+ this.selectPostChains.Push((this._curTab, this._curPost));
}
private void TrimPostChain()
{
if (this.selectPostChains.Count <= 2000) return;
- var p = new Stack<Tuple<TabPage, PostClass>>(2000);
+ var p = new Stack<ValueTuple<TabPage, PostClass>>(2000);
for (int i = 0; i < 2000; i++)
{
p.Push(this.selectPostChains.Pop());
}
else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
{
- var url = GetUrlFromDataObject(e.Data);
+ var (url, title) = GetUrlFromDataObject(e.Data);
string appendText;
- if (url.Item2 == null)
- appendText = url.Item1;
+ if (title == null)
+ appendText = url;
else
- appendText = url.Item2 + " " + url.Item1;
+ appendText = title + " " + url;
if (this.StatusText.TextLength == 0)
this.StatusText.Text = appendText;
/// </remarks>
/// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
/// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
- internal static Tuple<string, string> GetUrlFromDataObject(IDataObject data)
+ internal static (string Url, string Title) GetUrlFromDataObject(IDataObject data)
{
if (data.GetDataPresent("text/x-moz-url"))
{
if (lines.Length < 2)
throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
- return new Tuple<string, string>(lines[0], lines[1]);
+ return (lines[0], lines[1]);
}
}
else if (data.GetDataPresent("IESiteModeToUrl"))
if (lines.Length < 2)
throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
- return new Tuple<string, string>(lines[0], lines[1]);
+ return (lines[0], lines[1]);
}
}
else if (data.GetDataPresent("UniformResourceLocatorW"))
using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
{
var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
- return new Tuple<string, string>(url, null);
+ return (url, null);
}
}
post.RetweetedBy = post.RetweetedBy != null ? string.Intern(post.RetweetedBy) : null;
//Source整形
- var source = ParseSource(sourceHtml);
- post.Source = string.Intern(source.Item1);
- post.SourceUri = source.Item2;
+ var (sourceText, sourceUri) = ParseSource(sourceHtml);
+ post.Source = string.Intern(sourceText);
+ post.SourceUri = sourceUri;
post.IsReply = post.ReplyToList.Contains(_uname);
post.IsExcludeReply = false;
/// <summary>
/// Twitter APIから得たHTML形式のsource文字列を分析し、source名とURLに分離します
/// </summary>
- public static Tuple<string, Uri> ParseSource(string sourceHtml)
+ internal static (string SourceText, Uri SourceUri) ParseSource(string sourceHtml)
{
if (string.IsNullOrEmpty(sourceHtml))
- return Tuple.Create<string, Uri>("", null);
+ return ("", null);
string sourceText;
Uri sourceUri;
sourceUri = null;
}
- return Tuple.Create(sourceText, sourceUri);
+ return (sourceText, sourceUri);
}
public async Task<TwitterApiStatus> GetInfoApi()
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+// ValueTuple が .NET Framework に追加されるまでの繋ぎのための定義
+
+namespace System
+{
+ internal struct ValueTuple<T1, T2> : IStructuralEquatable
+ {
+ public T1 Item1;
+ public T2 Item2;
+
+ public ValueTuple(T1 item1, T2 item2)
+ {
+ this.Item1 = item1;
+ this.Item2 = item2;
+ }
+
+ bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+ {
+ if (other is ValueTuple<T1, T2> otherTuple)
+ {
+ return comparer.Equals(this.Item1, otherTuple.Item1)
+ && comparer.Equals(this.Item2, otherTuple.Item2);
+ }
+
+ return false;
+ }
+
+ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+ => comparer.GetHashCode(this.Item1) ^ comparer.GetHashCode(this.Item2);
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct)]
+ internal class TupleElementNamesAttribute : Attribute
+ {
+ public IList<string> TransformNames { get; }
+
+ public TupleElementNamesAttribute(string[] names)
+ => this.TransformNames = names;
+ }
+}
\ No newline at end of file