Skip to content

Commit

Permalink
Merge branch 'develop' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
upsilon committed Nov 28, 2023
2 parents eefa7bb + 84f2000 commit 0ef26fd
Show file tree
Hide file tree
Showing 57 changed files with 3,197 additions and 199 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Build
shell: pwsh
run: |
msbuild /target:restore,build "/p:Configuration=$($env:CONFIGURATION)" /verbosity:minimal
msbuild /target:restore,build "/p:Configuration=$($env:CONFIGURATION)" /p:DebugType=None /verbosity:minimal
- name: Upload build result
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -171,12 +171,10 @@ jobs:
$destPath = 'OpenTween.zip'
$headCommit = '${{ github.event.pull_request.head.sha }}'
.\tools\build-zip-archive.ps1 -BinDir $binDir -DestPath $destPath -HeadCommit $headCommit
Copy-Item ($binDir + 'OpenTween.pdb') -Destination '.\'
- name: Upload build result
uses: actions/upload-artifact@v3
with:
name: package
path: |
./OpenTween.zip
./OpenTween.pdb
11 changes: 11 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
更新履歴

==== Ver 3.8.0(2023/11/29)
* NEW: graphqlエンドポイントを使用した検索タイムラインの取得に対応
* NEW: graphqlエンドポイントを使用したプロフィール情報の取得に対応
* NEW: graphqlエンドポイントを使用したユーザータイムラインの取得に対応
* CHG: タイムライン更新が停止する不具合が報告される件への暫定的な対処
- タイムライン更新に30秒以上掛かっている場合は完了を待機せず次のタイマーを開始させる
- タイムライン更新の次回実行が1時間以上先になる場合は異常値としてタイマーをリセットする
* FIX: 動画のサムネイル表示時に再生可能であることを示すアイコンが表示されない不具合を修正
* FIX: リスト更新時に発生したネットワークエラーが適切に処理されない不具合を修正
* FIX: 起動直後にタイムラインの取得が重複して行われる不具合を修正

==== Ver 3.7.1(2023/07/20)
* FIX: Cookie使用時に複数回ツイートを投稿するとDelaying?のエラーが表示される不具合を修正
* FIX: 自己リプライのスレッドに含まれるツイートを発言一覧で選択するとエラーが発生する不具合を修正
Expand Down
8 changes: 8 additions & 0 deletions OpenTween.Tests/Api/GraphQL/ErrorResponseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,13 @@ public void ThrowIfError_SuccessResponseTest()
ErrorResponse.ThrowIfError(responseText);
// 通常のレスポンスに対しては WebApiException を発生させない
}

[Fact]
public void ThrowIfError_SuccessWithErrorResponseTest()
{
var responseText = File.ReadAllText("Resources/Responses/SearchTimeline_SuccessWithError.json");
ErrorResponse.ThrowIfError(responseText);
// errors と data の両方を含むレスポンスに対してはエラーを無視する
}
}
}
94 changes: 94 additions & 0 deletions OpenTween.Tests/Api/GraphQL/SearchTimelineRequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// OpenTween - Client of Twitter
// Copyright (c) 2023 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.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using OpenTween.Connection;
using Xunit;

namespace OpenTween.Api.GraphQL
{
public class SearchTimelineRequestTest
{
[Fact]
public async Task Send_Test()
{
using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json");

var mock = new Mock<IApiConnection>();
mock.Setup(x =>
x.GetStreamAsync(It.IsAny<Uri>(), It.IsAny<IDictionary<string, string>>())
)
.Callback<Uri, IDictionary<string, string>>((url, param) =>
{
Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), url);
Assert.Equal(2, param.Count);
Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest"}""", param["variables"]);
Assert.True(param.ContainsKey("features"));
})
.ReturnsAsync(responseStream);

var request = new SearchTimelineRequest(rawQuery: "#OpenTween")
{
Count = 20,
};

var response = await request.Send(mock.Object).ConfigureAwait(false);
Assert.Single(response.Tweets);
Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorBottom);

mock.VerifyAll();
}

[Fact]
public async Task Send_RequestCursor_Test()
{
using var responseStream = File.OpenRead("Resources/Responses/SearchTimeline_SimpleTweet.json");

var mock = new Mock<IApiConnection>();
mock.Setup(x =>
x.GetStreamAsync(It.IsAny<Uri>(), It.IsAny<IDictionary<string, string>>())
)
.Callback<Uri, IDictionary<string, string>>((url, param) =>
{
Assert.Equal(new("https://twitter.com/i/api/graphql/lZ0GCEojmtQfiUQa5oJSEw/SearchTimeline"), url);
Assert.Equal(2, param.Count);
Assert.Equal("""{"rawQuery":"#OpenTween","count":20,"product":"Latest","cursor":"aaa"}""", param["variables"]);
Assert.True(param.ContainsKey("features"));
})
.ReturnsAsync(responseStream);

var request = new SearchTimelineRequest(rawQuery: "#OpenTween")
{
Count = 20,
Cursor = "aaa",
};

await request.Send(mock.Object).ConfigureAwait(false);
mock.VerifyAll();
}
}
}
30 changes: 30 additions & 0 deletions OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
Expand Down Expand Up @@ -76,6 +77,7 @@ public void ToStatus_WithTwitterPostFactory_SimpleTweet_Test()

Assert.Equal("1613784711020826626", post.StatusId.Id);
Assert.Equal(40480664L, post.UserId);
Assert.False(post.IsPromoted);
}

[Fact]
Expand Down Expand Up @@ -136,5 +138,33 @@ public void ToStatus_WithTwitterPostFactory_SelfThread_Test()
Assert.Equal("1511751702684499968", post.StatusId.Id);
Assert.Equal(40480664L, post.UserId);
}

[Fact]
public void ToStatus_WithTwitterPostFactory_PromotedTweet_Test()
{
var rootElm = this.LoadResponseDocument("TimelineTweet_PromotedTweet.json");
var timelineTweet = new TimelineTweet(rootElm);
var status = timelineTweet.ToTwitterStatus();
var postFactory = new TwitterPostFactory(this.CreateTabInfo());
var post = postFactory.CreateFromStatus(status, selfUserId: 1L, new HashSet<long>());

Assert.Equal("1674737917363888129", post.StatusId.Id);
Assert.Equal(2941313791L, post.UserId);
Assert.True(post.IsPromoted);
Assert.Matches(new Regex(@"^\[Promoted\]\n"), post.TextFromApi);
}

[Fact]
public void ToStatus_TweetTombstone_Test()
{
var rootElm = this.LoadResponseDocument("TimelineTweet_TweetTombstone.json");
var timelineTweet = new TimelineTweet(rootElm);

Assert.True(timelineTweet.IsTombstone);
var ex = Assert.Throws<WebApiException>(
() => timelineTweet.ToTwitterStatus()
);
Assert.Equal("This Post is from a suspended account. Learn more", ex.Message);
}
}
}
55 changes: 55 additions & 0 deletions OpenTween.Tests/Api/GraphQL/TwitterGraphqlUserTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// OpenTween - Client of Twitter
// Copyright (c) 2023 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.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Xunit;

namespace OpenTween.Api.GraphQL
{
public class TwitterGraphqlUserTest
{
private XElement LoadResponseDocument(string filename)
{
using var stream = File.OpenRead($"Resources/Responses/{filename}");
using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max);
return XElement.Load(jsonReader);
}

[Fact]
public void ToTwitterUser_Test()
{
var userElm = this.LoadResponseDocument("User_Simple.json");
var graphqlUser = new TwitterGraphqlUser(userElm);
var user = graphqlUser.ToTwitterUser();

Assert.Equal("514241801", user.IdStr);
Assert.Equal("opentween", user.ScreenName);
}
}
}
87 changes: 87 additions & 0 deletions OpenTween.Tests/Api/GraphQL/UserByScreenNameRequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// OpenTween - Client of Twitter
// Copyright (c) 2023 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.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using OpenTween.Connection;
using Xunit;

namespace OpenTween.Api.GraphQL
{
public class UserByScreenNameRequestTest
{
[Fact]
public async Task Send_Test()
{
using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName.json");

var mock = new Mock<IApiConnection>();
mock.Setup(x =>
x.GetStreamAsync(It.IsAny<Uri>(), It.IsAny<IDictionary<string, string>>())
)
.Callback<Uri, IDictionary<string, string>>((url, param) =>
{
Assert.Equal(new("https://twitter.com/i/api/graphql/xc8f1g7BYqr6VTzTbvNlGw/UserByScreenName"), url);
Assert.Contains(@"""screen_name"":""opentween""", param["variables"]);
})
.ReturnsAsync(responseStream);

var request = new UserByScreenNameRequest
{
ScreenName = "opentween",
};

var user = await request.Send(mock.Object).ConfigureAwait(false);
Assert.Equal("514241801", user.ToTwitterUser().IdStr);

mock.VerifyAll();
}

[Fact]
public async Task Send_UserUnavailableTest()
{
using var responseStream = File.OpenRead("Resources/Responses/UserByScreenName_Suspended.json");

var mock = new Mock<IApiConnection>();
mock.Setup(x =>
x.GetStreamAsync(It.IsAny<Uri>(), It.IsAny<IDictionary<string, string>>())
)
.ReturnsAsync(responseStream);

var request = new UserByScreenNameRequest
{
ScreenName = "elonmusk",
};

var ex = await Assert.ThrowsAsync<WebApiException>(
() => request.Send(mock.Object)
);
Assert.Equal("User is suspended", ex.Message);

mock.VerifyAll();
}
}
}
Loading

0 comments on commit 0ef26fd

Please sign in to comment.