• R/O
  • HTTP
  • SSH
  • HTTPS

open-tween: Commit

開発に使用するリポジトリ


Commit MetaInfo

Revision1d8a7c757656863a48bfdaf7b0be6c84568f8d6f (tree)
Time2022-01-22 23:34:20
Authorupsilon <kim.upsilon@bucy...>
CommiterGitHub

Log Message

Merge pull request #89 from opentween/encrypt-api-keys

アプリケーションに埋め込むAPIキーの暗号化に対応

Change Summary

Incremental Difference

--- a/OpenTween.Tests/Api/BitlyApiTest.cs
+++ b/OpenTween.Tests/Api/BitlyApiTest.cs
@@ -38,7 +38,7 @@ namespace OpenTween.Api
3838 {
3939 using var mockHandler = new HttpMessageHandlerMock();
4040 using var http = new HttpClient(mockHandler);
41- var bitly = new BitlyApi(http);
41+ var bitly = new BitlyApi(ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"), http);
4242
4343 mockHandler.Enqueue(x =>
4444 {
@@ -72,7 +72,7 @@ namespace OpenTween.Api
7272 {
7373 using var mockHandler = new HttpMessageHandlerMock();
7474 using var http = new HttpClient(mockHandler);
75- var bitly = new BitlyApi(http);
75+ var bitly = new BitlyApi(ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"), http);
7676
7777 mockHandler.Enqueue(x =>
7878 {
@@ -108,7 +108,7 @@ namespace OpenTween.Api
108108 {
109109 using var mockHandler = new HttpMessageHandlerMock();
110110 using var http = new HttpClient(mockHandler);
111- var bitly = new BitlyApi(http);
111+ var bitly = new BitlyApi(ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"), http);
112112
113113 mockHandler.Enqueue(async x =>
114114 {
@@ -117,8 +117,10 @@ namespace OpenTween.Api
117117 x.RequestUri.GetLeftPart(UriPartial.Path));
118118
119119 Assert.Equal("Basic", x.Headers.Authorization.Scheme);
120- Assert.Equal(ApplicationSettings.BitlyClientId + ":" + ApplicationSettings.BitlyClientSecret,
121- Encoding.UTF8.GetString(Convert.FromBase64String(x.Headers.Authorization.Parameter)));
120+ Assert.Equal(
121+ Convert.ToBase64String(Encoding.UTF8.GetBytes("fake_client_id:fake_client_secret")),
122+ x.Headers.Authorization.Parameter
123+ );
122124
123125 var body = await x.Content.ReadAsStringAsync()
124126 .ConfigureAwait(false);
@@ -146,7 +148,7 @@ namespace OpenTween.Api
146148 {
147149 using var mockHandler = new HttpMessageHandlerMock();
148150 using var http = new HttpClient(mockHandler);
149- var bitly = new BitlyApi(http);
151+ var bitly = new BitlyApi(ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"), http);
150152
151153 mockHandler.Enqueue(x =>
152154 {
@@ -161,5 +163,17 @@ namespace OpenTween.Api
161163
162164 Assert.Equal(0, mockHandler.QueueCount);
163165 }
166+
167+ [Fact]
168+ public async Task GetAccessTokenAsync_ApiKeyErrorTest()
169+ {
170+ using var mockHandler = new HttpMessageHandlerMock();
171+ using var http = new HttpClient(mockHandler);
172+ var bitly = new BitlyApi(ApiKey.Create("%e%INVALID_API_KEY"), ApiKey.Create("%e%INVALID_API_KEY"), http);
173+
174+ await Assert.ThrowsAsync<WebApiException>(
175+ () => bitly.GetAccessTokenAsync("hogehoge", "tetete")
176+ );
177+ }
164178 }
165179 }
--- a/OpenTween.Tests/Api/ImgurApiTest.cs
+++ b/OpenTween.Tests/Api/ImgurApiTest.cs
@@ -82,7 +82,7 @@ namespace OpenTween.Api
8282 };
8383 });
8484
85- var imgurApi = new ImgurApi("fake_api_key", http);
85+ var imgurApi = new ImgurApi(ApiKey.Create("fake_api_key"), http);
8686 using var mediaItem = TestUtils.CreateDummyMediaItem();
8787 var uploadedUrl = await imgurApi.UploadFileAsync(mediaItem, "てすと")
8888 .ConfigureAwait(false);
@@ -113,7 +113,7 @@ namespace OpenTween.Api
113113 };
114114 });
115115
116- var imgurApi = new ImgurApi("fake_api_key", http);
116+ var imgurApi = new ImgurApi(ApiKey.Create("fake_api_key"), http);
117117 using var mediaItem = TestUtils.CreateDummyMediaItem();
118118 await Assert.ThrowsAsync<WebApiException>(
119119 () => imgurApi.UploadFileAsync(mediaItem, "てすと")
@@ -139,7 +139,22 @@ namespace OpenTween.Api
139139 };
140140 });
141141
142- var imgurApi = new ImgurApi("fake_api_key", http);
142+ var imgurApi = new ImgurApi(ApiKey.Create("fake_api_key"), http);
143+ using var mediaItem = TestUtils.CreateDummyMediaItem();
144+ await Assert.ThrowsAsync<WebApiException>(
145+ () => imgurApi.UploadFileAsync(mediaItem, "てすと")
146+ );
147+
148+ Assert.Equal(0, mockHandler.QueueCount);
149+ }
150+
151+ [Fact]
152+ public async Task UploadFileAsync_ApiKeyErrorTest()
153+ {
154+ using var mockHandler = new HttpMessageHandlerMock();
155+ using var http = new HttpClient(mockHandler);
156+
157+ var imgurApi = new ImgurApi(ApiKey.Create("%e%INVALID_API_KEY"), http);
143158 using var mediaItem = TestUtils.CreateDummyMediaItem();
144159 await Assert.ThrowsAsync<WebApiException>(
145160 () => imgurApi.UploadFileAsync(mediaItem, "てすと")
--- a/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs
+++ b/OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs
@@ -42,7 +42,7 @@ namespace OpenTween.Api
4242 using var mockHandler = new HttpMessageHandlerMock();
4343 using var http = new HttpClient(mockHandler);
4444
45- var mock = new Mock<MicrosoftTranslatorApi>(http);
45+ var mock = new Mock<MicrosoftTranslatorApi>(ApiKey.Create("fake_api_key"), http);
4646 mock.Setup(x => x.GetAccessTokenAsync())
4747 .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
4848
@@ -95,9 +95,26 @@ namespace OpenTween.Api
9595 }
9696
9797 [Fact]
98+ public async Task TranslateAsync_ApiKeyErrorTest()
99+ {
100+ using var mockHandler = new HttpMessageHandlerMock();
101+ using var http = new HttpClient(mockHandler);
102+
103+ var mock = new Mock<MicrosoftTranslatorApi>(ApiKey.Create("%e%INVALID_API_KEY"), http);
104+ var translateApi = mock.Object;
105+
106+ await Assert.ThrowsAsync<WebApiException>(
107+ () => translateApi.TranslateAsync("hogehoge", langTo: "ja", langFrom: "en")
108+ );
109+
110+ mock.Verify(x => x.GetAccessTokenAsync(), Times.Never());
111+ Assert.Equal(0, mockHandler.QueueCount);
112+ }
113+
114+ [Fact]
98115 public async Task UpdateAccessTokenIfExpired_FirstCallTest()
99116 {
100- var mock = new Mock<MicrosoftTranslatorApi>();
117+ var mock = new Mock<MicrosoftTranslatorApi>(ApiKey.Create("fake_api_key"), null);
101118 mock.Setup(x => x.GetAccessTokenAsync())
102119 .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
103120
@@ -116,7 +133,7 @@ namespace OpenTween.Api
116133 [Fact]
117134 public async Task UpdateAccessTokenIfExpired_NotExpiredTest()
118135 {
119- var mock = new Mock<MicrosoftTranslatorApi>();
136+ var mock = new Mock<MicrosoftTranslatorApi>(ApiKey.Create("fake_api_key"), null);
120137
121138 var translateApi = mock.Object;
122139 translateApi.AccessToken = "1234abcd";
@@ -132,7 +149,7 @@ namespace OpenTween.Api
132149 [Fact]
133150 public async Task UpdateAccessTokenIfExpired_ExpiredTest()
134151 {
135- var mock = new Mock<MicrosoftTranslatorApi>();
152+ var mock = new Mock<MicrosoftTranslatorApi>(ApiKey.Create("fake_api_key"), null);
136153 mock.Setup(x => x.GetAccessTokenAsync())
137154 .ReturnsAsync(("5678efgh", TimeSpan.FromSeconds(1000)));
138155
@@ -155,7 +172,7 @@ namespace OpenTween.Api
155172 {
156173 using var mockHandler = new HttpMessageHandlerMock();
157174 using var http = new HttpClient(mockHandler);
158- var translateApi = new MicrosoftTranslatorApi(http);
175+ var translateApi = new MicrosoftTranslatorApi(ApiKey.Create("fake_api_key"), http);
159176
160177 mockHandler.Enqueue(x =>
161178 {
@@ -163,7 +180,7 @@ namespace OpenTween.Api
163180 Assert.Equal(MicrosoftTranslatorApi.IssueTokenEndpoint, x.RequestUri);
164181
165182 var keyHeader = x.Headers.First(y => y.Key == "Ocp-Apim-Subscription-Key");
166- Assert.Equal(ApplicationSettings.TranslatorSubscriptionKey, keyHeader.Value.Single());
183+ Assert.Equal("fake_api_key", keyHeader.Value.Single());
167184
168185 return new HttpResponseMessage(HttpStatusCode.OK)
169186 {
--- a/OpenTween.Tests/Api/MobypictureApiTest.cs
+++ b/OpenTween.Tests/Api/MobypictureApiTest.cs
@@ -30,7 +30,7 @@ using Xunit;
3030
3131 namespace OpenTween.Api
3232 {
33- public class MobypictureApiTest
33+ public class MobypictureApiText
3434 {
3535 [Fact]
3636 public async Task UploadFileAsync_Test()
@@ -54,7 +54,7 @@ namespace OpenTween.Api
5454 };
5555 });
5656
57- var mobypictureApi = new MobypictureApi("fake_api_key", http);
57+ var mobypictureApi = new MobypictureApi(ApiKey.Create("fake_api_key"), http);
5858 using var mediaItem = TestUtils.CreateDummyMediaItem();
5959 var uploadedUrl = await mobypictureApi.UploadFileAsync(mediaItem, "てすと")
6060 .ConfigureAwait(false);
@@ -80,7 +80,22 @@ namespace OpenTween.Api
8080 };
8181 });
8282
83- var mobypictureApi = new MobypictureApi("fake_api_key", http);
83+ var mobypictureApi = new MobypictureApi(ApiKey.Create("fake_api_key"), http);
84+ using var mediaItem = TestUtils.CreateDummyMediaItem();
85+ await Assert.ThrowsAsync<WebApiException>(
86+ () => mobypictureApi.UploadFileAsync(mediaItem, "てすと")
87+ );
88+
89+ Assert.Equal(0, mockHandler.QueueCount);
90+ }
91+
92+ [Fact]
93+ public async Task UploadFileAsync_ApiKeyErrorTest()
94+ {
95+ using var mockHandler = new HttpMessageHandlerMock();
96+ using var http = new HttpClient(mockHandler);
97+
98+ var mobypictureApi = new MobypictureApi(ApiKey.Create("%e%INVALID_API_KEY"), http);
8499 using var mediaItem = TestUtils.CreateDummyMediaItem();
85100 await Assert.ThrowsAsync<WebApiException>(
86101 () => mobypictureApi.UploadFileAsync(mediaItem, "てすと")
--- a/OpenTween.Tests/Api/TwitterApiTest.cs
+++ b/OpenTween.Tests/Api/TwitterApiTest.cs
@@ -51,7 +51,7 @@ namespace OpenTween.Api
5151 [Fact]
5252 public void Initialize_Test()
5353 {
54- using var twitterApi = new TwitterApi();
54+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
5555 Assert.Null(twitterApi.apiConnection);
5656
5757 twitterApi.Initialize("*** AccessToken ***", "*** AccessSecret ***", userId: 100L, screenName: "hogehoge");
@@ -100,7 +100,7 @@ namespace OpenTween.Api
100100 )
101101 .ReturnsAsync(Array.Empty<TwitterStatus>());
102102
103- using var twitterApi = new TwitterApi();
103+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
104104 twitterApi.apiConnection = mock.Object;
105105
106106 await twitterApi.StatusesHomeTimeline(200, maxId: 900L, sinceId: 100L)
@@ -128,7 +128,7 @@ namespace OpenTween.Api
128128 )
129129 .ReturnsAsync(Array.Empty<TwitterStatus>());
130130
131- using var twitterApi = new TwitterApi();
131+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
132132 twitterApi.apiConnection = mock.Object;
133133
134134 await twitterApi.StatusesMentionsTimeline(200, maxId: 900L, sinceId: 100L)
@@ -158,7 +158,7 @@ namespace OpenTween.Api
158158 )
159159 .ReturnsAsync(Array.Empty<TwitterStatus>());
160160
161- using var twitterApi = new TwitterApi();
161+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
162162 twitterApi.apiConnection = mock.Object;
163163
164164 await twitterApi.StatusesUserTimeline("twitterapi", count: 200, maxId: 900L, sinceId: 100L)
@@ -184,7 +184,7 @@ namespace OpenTween.Api
184184 )
185185 .ReturnsAsync(new TwitterStatus { Id = 100L });
186186
187- using var twitterApi = new TwitterApi();
187+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
188188 twitterApi.apiConnection = mock.Object;
189189
190190 await twitterApi.StatusesShow(statusId: 100L)
@@ -214,7 +214,7 @@ namespace OpenTween.Api
214214 )
215215 .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
216216
217- using var twitterApi = new TwitterApi();
217+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
218218 twitterApi.apiConnection = mock.Object;
219219
220220 await twitterApi.StatusesUpdate("hogehoge", replyToId: 100L, mediaIds: new[] { 10L, 20L },
@@ -243,7 +243,7 @@ namespace OpenTween.Api
243243 )
244244 .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
245245
246- using var twitterApi = new TwitterApi();
246+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
247247 twitterApi.apiConnection = mock.Object;
248248
249249 await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, excludeReplyUserIds: Array.Empty<long>())
@@ -264,7 +264,7 @@ namespace OpenTween.Api
264264 )
265265 .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
266266
267- using var twitterApi = new TwitterApi();
267+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
268268 twitterApi.apiConnection = mock.Object;
269269
270270 await twitterApi.StatusesDestroy(statusId: 100L)
@@ -290,7 +290,7 @@ namespace OpenTween.Api
290290 )
291291 .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
292292
293- using var twitterApi = new TwitterApi();
293+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
294294 twitterApi.apiConnection = mock.Object;
295295
296296 await twitterApi.StatusesRetweet(100L)
@@ -322,7 +322,7 @@ namespace OpenTween.Api
322322 )
323323 .ReturnsAsync(new TwitterSearchResult());
324324
325- using var twitterApi = new TwitterApi();
325+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
326326 twitterApi.apiConnection = mock.Object;
327327
328328 await twitterApi.SearchTweets("from:twitterapi", "en", count: 200, maxId: 900L, sinceId: 100L)
@@ -347,7 +347,7 @@ namespace OpenTween.Api
347347 )
348348 .ReturnsAsync(new TwitterLists());
349349
350- using var twitterApi = new TwitterApi();
350+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
351351 twitterApi.apiConnection = mock.Object;
352352
353353 await twitterApi.ListsOwnerships("twitterapi", cursor: -1L, count: 100)
@@ -372,7 +372,7 @@ namespace OpenTween.Api
372372 )
373373 .ReturnsAsync(new TwitterLists());
374374
375- using var twitterApi = new TwitterApi();
375+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
376376 twitterApi.apiConnection = mock.Object;
377377
378378 await twitterApi.ListsSubscriptions("twitterapi", cursor: -1L, count: 100)
@@ -398,7 +398,7 @@ namespace OpenTween.Api
398398 )
399399 .ReturnsAsync(new TwitterLists());
400400
401- using var twitterApi = new TwitterApi();
401+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
402402 twitterApi.apiConnection = mock.Object;
403403
404404 await twitterApi.ListsMemberships("twitterapi", cursor: -1L, count: 100, filterToOwnedLists: true)
@@ -422,7 +422,7 @@ namespace OpenTween.Api
422422 )
423423 .ReturnsAsync(LazyJson.Create(new TwitterList()));
424424
425- using var twitterApi = new TwitterApi();
425+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
426426 twitterApi.apiConnection = mock.Object;
427427
428428 await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true)
@@ -448,7 +448,7 @@ namespace OpenTween.Api
448448 )
449449 .ReturnsAsync(LazyJson.Create(new TwitterList()));
450450
451- using var twitterApi = new TwitterApi();
451+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
452452 twitterApi.apiConnection = mock.Object;
453453
454454 await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @private: true)
@@ -471,7 +471,7 @@ namespace OpenTween.Api
471471 )
472472 .ReturnsAsync(LazyJson.Create(new TwitterList()));
473473
474- using var twitterApi = new TwitterApi();
474+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
475475 twitterApi.apiConnection = mock.Object;
476476
477477 await twitterApi.ListsDestroy(12345L)
@@ -502,7 +502,7 @@ namespace OpenTween.Api
502502 )
503503 .ReturnsAsync(Array.Empty<TwitterStatus>());
504504
505- using var twitterApi = new TwitterApi();
505+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
506506 twitterApi.apiConnection = mock.Object;
507507
508508 await twitterApi.ListsStatuses(12345L, count: 200, maxId: 900L, sinceId: 100L, includeRTs: true)
@@ -529,7 +529,7 @@ namespace OpenTween.Api
529529 )
530530 .ReturnsAsync(new TwitterUsers());
531531
532- using var twitterApi = new TwitterApi();
532+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
533533 twitterApi.apiConnection = mock.Object;
534534
535535 await twitterApi.ListsMembers(12345L, cursor: -1)
@@ -556,7 +556,7 @@ namespace OpenTween.Api
556556 )
557557 .ReturnsAsync(new TwitterUser());
558558
559- using var twitterApi = new TwitterApi();
559+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
560560 twitterApi.apiConnection = mock.Object;
561561
562562 await twitterApi.ListsMembersShow(12345L, "twitterapi")
@@ -582,7 +582,7 @@ namespace OpenTween.Api
582582 )
583583 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
584584
585- using var twitterApi = new TwitterApi();
585+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
586586 twitterApi.apiConnection = mock.Object;
587587
588588 await twitterApi.ListsMembersCreate(12345L, "twitterapi")
@@ -609,7 +609,7 @@ namespace OpenTween.Api
609609 )
610610 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
611611
612- using var twitterApi = new TwitterApi();
612+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
613613 twitterApi.apiConnection = mock.Object;
614614
615615 await twitterApi.ListsMembersDestroy(12345L, "twitterapi")
@@ -634,7 +634,7 @@ namespace OpenTween.Api
634634 )
635635 .ReturnsAsync(new TwitterMessageEventList());
636636
637- using var twitterApi = new TwitterApi();
637+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
638638 twitterApi.apiConnection = mock.Object;
639639
640640 await twitterApi.DirectMessagesEventsList(count: 50, cursor: "12345abcdefg")
@@ -672,7 +672,7 @@ namespace OpenTween.Api
672672 )
673673 .ReturnsAsync(LazyJson.Create(new TwitterMessageEventSingle()));
674674
675- using var twitterApi = new TwitterApi();
675+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
676676 twitterApi.apiConnection = mock.Object;
677677
678678 await twitterApi.DirectMessagesEventsNew(recipientId: 12345L, text: "hogehoge", mediaId: 67890L)
@@ -691,7 +691,7 @@ namespace OpenTween.Api
691691 )
692692 .Returns(Task.CompletedTask);
693693
694- using var twitterApi = new TwitterApi();
694+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
695695 twitterApi.apiConnection = mock.Object;
696696
697697 await twitterApi.DirectMessagesEventsDestroy(eventId: "100")
@@ -717,7 +717,7 @@ namespace OpenTween.Api
717717 )
718718 .ReturnsAsync(new TwitterUser { ScreenName = "twitterapi" });
719719
720- using var twitterApi = new TwitterApi();
720+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
721721 twitterApi.apiConnection = mock.Object;
722722
723723 await twitterApi.UsersShow(screenName: "twitterapi")
@@ -743,7 +743,7 @@ namespace OpenTween.Api
743743 )
744744 .ReturnsAsync(Array.Empty<TwitterUser>());
745745
746- using var twitterApi = new TwitterApi();
746+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
747747 twitterApi.apiConnection = mock.Object;
748748
749749 await twitterApi.UsersLookup(userIds: new[] { "11111", "22222" })
@@ -766,7 +766,7 @@ namespace OpenTween.Api
766766 )
767767 .ReturnsAsync(LazyJson.Create(new TwitterUser { ScreenName = "twitterapi" }));
768768
769- using var twitterApi = new TwitterApi();
769+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
770770 twitterApi.apiConnection = mock.Object;
771771
772772 await twitterApi.UsersReportSpam(screenName: "twitterapi")
@@ -795,7 +795,7 @@ namespace OpenTween.Api
795795 )
796796 .ReturnsAsync(Array.Empty<TwitterStatus>());
797797
798- using var twitterApi = new TwitterApi();
798+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
799799 twitterApi.apiConnection = mock.Object;
800800
801801 await twitterApi.FavoritesList(200, maxId: 900L, sinceId: 100L)
@@ -818,7 +818,7 @@ namespace OpenTween.Api
818818 )
819819 .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
820820
821- using var twitterApi = new TwitterApi();
821+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
822822 twitterApi.apiConnection = mock.Object;
823823
824824 await twitterApi.FavoritesCreate(statusId: 100L)
@@ -842,7 +842,7 @@ namespace OpenTween.Api
842842 )
843843 .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
844844
845- using var twitterApi = new TwitterApi();
845+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
846846 twitterApi.apiConnection = mock.Object;
847847
848848 await twitterApi.FavoritesDestroy(statusId: 100L)
@@ -864,7 +864,7 @@ namespace OpenTween.Api
864864 )
865865 .ReturnsAsync(new TwitterFriendship());
866866
867- using var twitterApi = new TwitterApi();
867+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
868868 twitterApi.apiConnection = mock.Object;
869869
870870 await twitterApi.FriendshipsShow(sourceScreenName: "twitter", targetScreenName: "twitterapi")
@@ -884,7 +884,7 @@ namespace OpenTween.Api
884884 )
885885 .ReturnsAsync(LazyJson.Create(new TwitterFriendship()));
886886
887- using var twitterApi = new TwitterApi();
887+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
888888 twitterApi.apiConnection = mock.Object;
889889
890890 await twitterApi.FriendshipsCreate(screenName: "twitterapi")
@@ -905,7 +905,7 @@ namespace OpenTween.Api
905905 )
906906 .ReturnsAsync(LazyJson.Create(new TwitterFriendship()));
907907
908- using var twitterApi = new TwitterApi();
908+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
909909 twitterApi.apiConnection = mock.Object;
910910
911911 await twitterApi.FriendshipsDestroy(screenName: "twitterapi")
@@ -927,7 +927,7 @@ namespace OpenTween.Api
927927 )
928928 .ReturnsAsync(Array.Empty<long>());
929929
930- using var twitterApi = new TwitterApi();
930+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
931931 twitterApi.apiConnection = mock.Object;
932932
933933 await twitterApi.NoRetweetIds()
@@ -948,7 +948,7 @@ namespace OpenTween.Api
948948 )
949949 .ReturnsAsync(new TwitterIds());
950950
951- using var twitterApi = new TwitterApi();
951+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
952952 twitterApi.apiConnection = mock.Object;
953953
954954 await twitterApi.FollowersIds(cursor: -1L)
@@ -969,7 +969,7 @@ namespace OpenTween.Api
969969 )
970970 .ReturnsAsync(new TwitterIds());
971971
972- using var twitterApi = new TwitterApi();
972+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
973973 twitterApi.apiConnection = mock.Object;
974974
975975 await twitterApi.MutesUsersIds(cursor: -1L)
@@ -990,7 +990,7 @@ namespace OpenTween.Api
990990 )
991991 .ReturnsAsync(new TwitterIds());
992992
993- using var twitterApi = new TwitterApi();
993+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
994994 twitterApi.apiConnection = mock.Object;
995995
996996 await twitterApi.BlocksIds(cursor: -1L)
@@ -1013,7 +1013,7 @@ namespace OpenTween.Api
10131013 )
10141014 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
10151015
1016- using var twitterApi = new TwitterApi();
1016+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
10171017 twitterApi.apiConnection = mock.Object;
10181018
10191019 await twitterApi.BlocksCreate(screenName: "twitterapi")
@@ -1037,7 +1037,7 @@ namespace OpenTween.Api
10371037 )
10381038 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
10391039
1040- using var twitterApi = new TwitterApi();
1040+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
10411041 twitterApi.apiConnection = mock.Object;
10421042
10431043 await twitterApi.BlocksDestroy(screenName: "twitterapi")
@@ -1067,7 +1067,7 @@ namespace OpenTween.Api
10671067 ScreenName = "opentween",
10681068 });
10691069
1070- using var twitterApi = new TwitterApi();
1070+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
10711071 twitterApi.apiConnection = mock.Object;
10721072
10731073 await twitterApi.AccountVerifyCredentials()
@@ -1098,7 +1098,7 @@ namespace OpenTween.Api
10981098 )
10991099 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
11001100
1101- using var twitterApi = new TwitterApi();
1101+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
11021102 twitterApi.apiConnection = mock.Object;
11031103
11041104 await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", location: "Location", description: "<script>alert(1)</script>")
@@ -1126,7 +1126,7 @@ namespace OpenTween.Api
11261126 )
11271127 .ReturnsAsync(LazyJson.Create(new TwitterUser()));
11281128
1129- using var twitterApi = new TwitterApi();
1129+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
11301130 twitterApi.apiConnection = mock.Object;
11311131
11321132 await twitterApi.AccountUpdateProfileImage(media)
@@ -1148,7 +1148,7 @@ namespace OpenTween.Api
11481148 )
11491149 .ReturnsAsync(new TwitterRateLimits());
11501150
1151- using var twitterApi = new TwitterApi();
1151+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
11521152 twitterApi.apiConnection = mock.Object;
11531153
11541154 await twitterApi.ApplicationRateLimitStatus()
@@ -1169,7 +1169,7 @@ namespace OpenTween.Api
11691169 )
11701170 .ReturnsAsync(new TwitterConfiguration());
11711171
1172- using var twitterApi = new TwitterApi();
1172+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
11731173 twitterApi.apiConnection = mock.Object;
11741174
11751175 await twitterApi.Configuration()
@@ -1194,7 +1194,7 @@ namespace OpenTween.Api
11941194 )
11951195 .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaInit()));
11961196
1197- using var twitterApi = new TwitterApi();
1197+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
11981198 twitterApi.apiConnection = mock.Object;
11991199
12001200 await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", mediaCategory: "dm_image")
@@ -1222,7 +1222,7 @@ namespace OpenTween.Api
12221222 )
12231223 .Returns(Task.CompletedTask);
12241224
1225- using var twitterApi = new TwitterApi();
1225+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
12261226 twitterApi.apiConnection = mock.Object;
12271227
12281228 await twitterApi.MediaUploadAppend(mediaId: 11111L, segmentIndex: 1, media: media)
@@ -1245,7 +1245,7 @@ namespace OpenTween.Api
12451245 )
12461246 .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaResult()));
12471247
1248- using var twitterApi = new TwitterApi();
1248+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
12491249 twitterApi.apiConnection = mock.Object;
12501250
12511251 await twitterApi.MediaUploadFinalize(mediaId: 11111L)
@@ -1270,7 +1270,7 @@ namespace OpenTween.Api
12701270 )
12711271 .ReturnsAsync(new TwitterUploadMediaResult());
12721272
1273- using var twitterApi = new TwitterApi();
1273+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
12741274 twitterApi.apiConnection = mock.Object;
12751275
12761276 await twitterApi.MediaUploadStatus(mediaId: 11111L)
@@ -1290,7 +1290,7 @@ namespace OpenTween.Api
12901290 )
12911291 .Returns(Task.CompletedTask);
12921292
1293- using var twitterApi = new TwitterApi();
1293+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
12941294 twitterApi.apiConnection = mock.Object;
12951295
12961296 await twitterApi.MediaMetadataCreate(mediaId: 12345L, altText: "hogehoge")
@@ -1313,7 +1313,7 @@ namespace OpenTween.Api
13131313 )
13141314 .ReturnsAsync(new MemoryStream());
13151315
1316- using var twitterApi = new TwitterApi();
1316+ using var twitterApi = new TwitterApi(ApiKey.Create("fake_consumer_key"), ApiKey.Create("fake_consumer_secret"));
13171317 twitterApi.apiConnection = mock.Object;
13181318
13191319 var observable = twitterApi.UserStreams(replies: "all", track: "OpenTween");
--- /dev/null
+++ b/OpenTween.Tests/ApiKeyTest.cs
@@ -0,0 +1,115 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2022 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3+// All rights reserved.
4+//
5+// This file is part of OpenTween.
6+//
7+// This program is free software; you can redistribute it and/or modify it
8+// under the terms of the GNU General public License as published by the Free
9+// Software Foundation; either version 3 of the License, or (at your option)
10+// any later version.
11+//
12+// This program is distributed in the hope that it will be useful, but
13+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
15+// for more details.
16+//
17+// You should have received a copy of the GNU General public License along
18+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20+// Boston, MA 02110-1301, USA.
21+
22+using System;
23+using System.Collections.Generic;
24+using System.Linq;
25+using System.Text;
26+using System.Threading.Tasks;
27+using Xunit;
28+
29+namespace OpenTween
30+{
31+ public class ApiKeyTest
32+ {
33+ [Fact]
34+ public void Encrypt_FormatTest()
35+ {
36+ var password = "hoge";
37+ var plainText = "aaaaaaa";
38+ var encrypted = ApiKey.Encrypt(password, plainText);
39+ Assert.StartsWith("%e%", encrypted);
40+ Assert.Equal(5, encrypted.Split('%').Length);
41+ }
42+
43+ [Fact]
44+ public void Encrypt_NonceTest()
45+ {
46+ // 同じ平文に対する暗号文を繰り返し生成しても出力は毎回変化する
47+ var password = "hoge";
48+ var plainText = "aaaaaaa";
49+ var encrypted1 = ApiKey.Encrypt(password, plainText);
50+ var encrypted2 = ApiKey.Encrypt(password, plainText);
51+ Assert.NotEqual(encrypted1, encrypted2);
52+ }
53+
54+ [Fact]
55+ public void Decrypt_Test()
56+ {
57+ var password = "password";
58+ var encrypted = "%e%m6EH2dECH7HWT9SFE0SK4Q==%mAAWPhPALf48s32s/yQarg==%zoCs8crMqZN6Nfj8ALkl2R3kbD/FORecuepU1LJ3CK0=";
59+ var decrypted = ApiKey.Decrypt(password, encrypted);
60+ Assert.Equal("hogehoge", decrypted);
61+ }
62+
63+ [Fact]
64+ public void Decrypt_PlainTextTest()
65+ {
66+ // %e% から始まっていない文字列は平文として何もせずに返す
67+ var password = "password";
68+ var plainText = "plaintext";
69+ var decrypted = ApiKey.Decrypt(password, plainText);
70+ Assert.Equal("plaintext", decrypted);
71+ }
72+
73+ [Fact]
74+ public void Decrypt_InvalidFormatTest()
75+ {
76+ var password = "password";
77+ var encrypted = "%e%INVALID_FORMAT";
78+ Assert.Throws<ApiKeyDecryptException>(() => ApiKey.Decrypt(password, encrypted));
79+ }
80+
81+ [Fact]
82+ public void Decrypt_InvalidBase64Test()
83+ {
84+ var password = "password";
85+ var encrypted = "%e%!!!!!!!!!!%!!!!!!!!!!%!!!!!!!!!!";
86+ Assert.Throws<ApiKeyDecryptException>(() => ApiKey.Decrypt(password, encrypted));
87+ }
88+
89+ [Fact]
90+ public void Decrypt_InvalidHMACTest()
91+ {
92+ var password = "password";
93+ var encrypted = "%e%m6EH2dECH7HWT9SFE0SK4Q==%mAAWPhPALf48s32s/yQarg==%AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
94+ Assert.Throws<ApiKeyDecryptException>(() => ApiKey.Decrypt(password, encrypted));
95+ }
96+
97+ [Fact]
98+ public void Decrypt_InvalidHMACLengthTest()
99+ {
100+ // HMAC が途中まで一致しているが長さが足りない場合
101+ var password = "password";
102+ var encrypted = "%e%m6EH2dECH7HWT9SFE0SK4Q==%mAAWPhPALf48s32s/yQarg==%zoCs8";
103+ Assert.Throws<ApiKeyDecryptException>(() => ApiKey.Decrypt(password, encrypted));
104+ }
105+
106+ [Fact]
107+ public void EncryptAndDecrypt_Test()
108+ {
109+ var password = "hoge";
110+ var plainText = "aaaaaaa";
111+ var encrypted = ApiKey.Encrypt(password, plainText);
112+ Assert.Equal(plainText, ApiKey.Decrypt(password, encrypted));
113+ }
114+ }
115+}
--- a/OpenTween.Tests/Connection/OAuthUtilityTest.cs
+++ b/OpenTween.Tests/Connection/OAuthUtilityTest.cs
@@ -34,7 +34,7 @@ namespace OpenTween.Connection
3434 [Fact]
3535 public void GetOAuthParameter_Test()
3636 {
37- var param = OAuthUtility.GetOAuthParameter("ConsumerKey", "Token");
37+ var param = OAuthUtility.GetOAuthParameter(ApiKey.Create("ConsumerKey"), "Token");
3838
3939 Assert.Equal("ConsumerKey", param["oauth_consumer_key"]);
4040 Assert.Equal("HMAC-SHA1", param["oauth_signature_method"]);
@@ -53,7 +53,7 @@ namespace OpenTween.Connection
5353 {
5454 // GET http://example.com/hoge?aaa=foo に対する署名を生成
5555 // 実際の param は oauth_consumer_key などのパラメーターが加わった状態で渡される
56- var oauthSignature = OAuthUtility.CreateSignature("ConsumerSecret", "TokenSecret",
56+ var oauthSignature = OAuthUtility.CreateSignature(ApiKey.Create("ConsumerSecret"), "TokenSecret",
5757 "GET", new Uri("http://example.com/hoge"), new Dictionary<string, string> { ["aaa"] = "foo" });
5858
5959 var expectSignatureBase = "GET&http%3A%2F%2Fexample.com%2Fhoge&aaa%3Dfoo";
@@ -69,7 +69,7 @@ namespace OpenTween.Connection
6969 {
7070 // GET http://example.com/hoge?aaa=foo&bbb=bar に対する署名を生成
7171 // 複数のパラメータが渡される場合は name 順でソートされる
72- var oauthSignature = OAuthUtility.CreateSignature("ConsumerSecret", "TokenSecret",
72+ var oauthSignature = OAuthUtility.CreateSignature(ApiKey.Create("ConsumerSecret"), "TokenSecret",
7373 "GET", new Uri("http://example.com/hoge"), new Dictionary<string, string> {
7474 ["bbb"] = "bar",
7575 ["aaa"] = "foo",
@@ -88,7 +88,7 @@ namespace OpenTween.Connection
8888 {
8989 // GET http://example.com/hoge?aaa=foo に対する署名を生成
9090 // リクエストトークンの発行時は tokenSecret が空の状態で署名を生成することになる
91- var oauthSignature = OAuthUtility.CreateSignature("ConsumerSecret", null,
91+ var oauthSignature = OAuthUtility.CreateSignature(ApiKey.Create("ConsumerSecret"), null,
9292 "GET", new Uri("http://example.com/hoge"), new Dictionary<string, string> { ["aaa"] = "foo" });
9393
9494 var expectSignatureBase = "GET&http%3A%2F%2Fexample.com%2Fhoge&aaa%3Dfoo";
@@ -104,7 +104,7 @@ namespace OpenTween.Connection
104104 {
105105 var authorization = OAuthUtility.CreateAuthorization(
106106 "GET", new Uri("http://example.com/hoge"), new Dictionary<string, string> { ["aaa"] = "hoge" },
107- "ConsumerKey", "ConsumerSecret", "AccessToken", "AccessSecret", "Realm");
107+ ApiKey.Create("ConsumerKey"), ApiKey.Create("ConsumerSecret"), "AccessToken", "AccessSecret", "Realm");
108108
109109 Assert.StartsWith("OAuth ", authorization, StringComparison.Ordinal);
110110
--- a/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs
+++ b/OpenTween.Tests/Connection/TwitterApiConnectionTest.cs
@@ -56,7 +56,7 @@ namespace OpenTween.Connection
5656 {
5757 using var mockHandler = new HttpMessageHandlerMock();
5858 using var http = new HttpClient(mockHandler);
59- using var apiConnection = new TwitterApiConnection("", "");
59+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
6060 apiConnection.http = http;
6161
6262 mockHandler.Enqueue(x =>
@@ -95,7 +95,7 @@ namespace OpenTween.Connection
9595 {
9696 using var mockHandler = new HttpMessageHandlerMock();
9797 using var http = new HttpClient(mockHandler);
98- using var apiConnection = new TwitterApiConnection("", "");
98+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
9999 apiConnection.http = http;
100100
101101 mockHandler.Enqueue(x =>
@@ -133,7 +133,7 @@ namespace OpenTween.Connection
133133 {
134134 using var mockHandler = new HttpMessageHandlerMock();
135135 using var http = new HttpClient(mockHandler);
136- using var apiConnection = new TwitterApiConnection("", "");
136+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
137137 apiConnection.http = http;
138138
139139 mockHandler.Enqueue(x =>
@@ -174,7 +174,7 @@ namespace OpenTween.Connection
174174 {
175175 using var mockHandler = new HttpMessageHandlerMock();
176176 using var http = new HttpClient(mockHandler);
177- using var apiConnection = new TwitterApiConnection("", "");
177+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
178178 apiConnection.http = http;
179179
180180 mockHandler.Enqueue(x =>
@@ -202,7 +202,7 @@ namespace OpenTween.Connection
202202 {
203203 using var mockHandler = new HttpMessageHandlerMock();
204204 using var http = new HttpClient(mockHandler);
205- using var apiConnection = new TwitterApiConnection("", "");
205+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
206206 apiConnection.http = http;
207207
208208 mockHandler.Enqueue(x =>
@@ -232,7 +232,7 @@ namespace OpenTween.Connection
232232 {
233233 using var mockHandler = new HttpMessageHandlerMock();
234234 using var http = new HttpClient(mockHandler);
235- using var apiConnection = new TwitterApiConnection("", "");
235+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
236236 using var image = TestUtils.CreateDummyImage();
237237 apiConnection.http = http;
238238
@@ -279,7 +279,7 @@ namespace OpenTween.Connection
279279 {
280280 using var mockHandler = new HttpMessageHandlerMock();
281281 using var http = new HttpClient(mockHandler);
282- using var apiConnection = new TwitterApiConnection("", "");
282+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
283283 apiConnection.http = http;
284284
285285 mockHandler.Enqueue(async x =>
@@ -322,7 +322,7 @@ namespace OpenTween.Connection
322322 {
323323 using var mockHandler = new HttpMessageHandlerMock();
324324 using var http = new HttpClient(mockHandler);
325- using var apiConnection = new TwitterApiConnection("", "");
325+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
326326 apiConnection.httpUpload = http;
327327
328328 using var image = TestUtils.CreateDummyImage();
@@ -394,7 +394,7 @@ namespace OpenTween.Connection
394394 {
395395 using var mockHandler = new HttpMessageHandlerMock();
396396 using var http = new HttpClient(mockHandler);
397- using var apiConnection = new TwitterApiConnection("", "");
397+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
398398 apiConnection.httpUpload = http;
399399
400400 mockHandler.Enqueue(async x =>
@@ -441,7 +441,7 @@ namespace OpenTween.Connection
441441 {
442442 using var mockHandler = new HttpMessageHandlerMock();
443443 using var http = new HttpClient(mockHandler);
444- using var apiConnection = new TwitterApiConnection("", "");
444+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
445445 apiConnection.http = http;
446446
447447 mockHandler.Enqueue(async x =>
@@ -473,7 +473,7 @@ namespace OpenTween.Connection
473473 {
474474 using var mockHandler = new HttpMessageHandlerMock();
475475 using var http = new HttpClient(mockHandler);
476- using var apiConnection = new TwitterApiConnection("", "");
476+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
477477 apiConnection.http = http;
478478
479479 mockHandler.Enqueue(async x =>
@@ -513,7 +513,7 @@ namespace OpenTween.Connection
513513 {
514514 using var mockHandler = new HttpMessageHandlerMock();
515515 using var http = new HttpClient(mockHandler);
516- using var apiConnection = new TwitterApiConnection("", "");
516+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
517517 apiConnection.http = http;
518518
519519 mockHandler.Enqueue(x =>
--- a/OpenTween.Tests/MediaSelectorTest.cs
+++ b/OpenTween.Tests/MediaSelectorTest.cs
@@ -9,6 +9,7 @@ using System.Runtime.InteropServices;
99 using System.Text;
1010 using System.Text.RegularExpressions;
1111 using Moq;
12+using OpenTween.Api;
1213 using OpenTween.Api.DataModel;
1314 using Xunit;
1415
@@ -30,7 +31,8 @@ namespace OpenTween
3031 [Fact]
3132 public void Initialize_TwitterTest()
3233 {
33- using var twitter = new Twitter();
34+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
35+ using var twitter = new Twitter(twitterApi);
3436 using var mediaSelector = new MediaSelector();
3537 twitter.Initialize("", "", "", 0L);
3638 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -51,7 +53,8 @@ namespace OpenTween
5153 [Fact]
5254 public void Initialize_ImgurTest()
5355 {
54- using var twitter = new Twitter();
56+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
57+ using var twitter = new Twitter(twitterApi);
5558 using var mediaSelector = new MediaSelector();
5659 twitter.Initialize("", "", "", 0L);
5760 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Imgur");
@@ -70,7 +73,8 @@ namespace OpenTween
7073 [Fact]
7174 public void BeginSelection_BlankTest()
7275 {
73- using var twitter = new Twitter();
76+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
77+ using var twitter = new Twitter(twitterApi);
7478 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
7579 twitter.Initialize("", "", "", 0L);
7680 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -97,7 +101,8 @@ namespace OpenTween
97101 [Fact]
98102 public void BeginSelection_FilePathTest()
99103 {
100- using var twitter = new Twitter();
104+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
105+ using var twitter = new Twitter(twitterApi);
101106 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
102107 twitter.Initialize("", "", "", 0L);
103108 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -129,7 +134,8 @@ namespace OpenTween
129134 [Fact]
130135 public void BeginSelection_MemoryImageTest()
131136 {
132- using var twitter = new Twitter();
137+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
138+ using var twitter = new Twitter(twitterApi);
133139 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
134140 twitter.Initialize("", "", "", 0L);
135141 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -164,7 +170,8 @@ namespace OpenTween
164170 [Fact]
165171 public void BeginSelection_MultiImageTest()
166172 {
167- using var twitter = new Twitter();
173+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
174+ using var twitter = new Twitter(twitterApi);
168175 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
169176 twitter.Initialize("", "", "", 0L);
170177 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -188,7 +195,8 @@ namespace OpenTween
188195 [Fact]
189196 public void EndSelection_Test()
190197 {
191- using var twitter = new Twitter();
198+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
199+ using var twitter = new Twitter(twitterApi);
192200 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
193201 twitter.Initialize("", "", "", 0L);
194202 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -211,7 +219,8 @@ namespace OpenTween
211219 [Fact]
212220 public void PageChange_Test()
213221 {
214- using var twitter = new Twitter();
222+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
223+ using var twitter = new Twitter(twitterApi);
215224 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
216225 twitter.Initialize("", "", "", 0L);
217226 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -254,7 +263,8 @@ namespace OpenTween
254263 [Fact]
255264 public void PageChange_AlternativeTextTest()
256265 {
257- using var twitter = new Twitter();
266+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
267+ using var twitter = new Twitter(twitterApi);
258268 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
259269 twitter.Initialize("", "", "", 0L);
260270 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -291,7 +301,8 @@ namespace OpenTween
291301 [Fact]
292302 public void PageChange_ImageDisposeTest()
293303 {
294- using var twitter = new Twitter();
304+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
305+ using var twitter = new Twitter(twitterApi);
295306 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
296307 twitter.Initialize("", "", "", 0L);
297308 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -319,7 +330,8 @@ namespace OpenTween
319330 [Fact]
320331 public void ImagePathInput_Test()
321332 {
322- using var twitter = new Twitter();
333+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
334+ using var twitter = new Twitter(twitterApi);
323335 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
324336 twitter.Initialize("", "", "", 0L);
325337 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -344,7 +356,8 @@ namespace OpenTween
344356 [Fact]
345357 public void ImagePathInput_ReplaceFileMediaItemTest()
346358 {
347- using var twitter = new Twitter();
359+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
360+ using var twitter = new Twitter(twitterApi);
348361 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
349362 twitter.Initialize("", "", "", 0L);
350363 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -372,7 +385,8 @@ namespace OpenTween
372385 [Fact]
373386 public void ImagePathInput_ReplaceMemoryImageMediaItemTest()
374387 {
375- using var twitter = new Twitter();
388+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
389+ using var twitter = new Twitter(twitterApi);
376390 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
377391 twitter.Initialize("", "", "", 0L);
378392 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
@@ -411,7 +425,8 @@ namespace OpenTween
411425 [Fact]
412426 public void ImageServiceChange_Test()
413427 {
414- using var twitter = new Twitter();
428+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
429+ using var twitter = new Twitter(twitterApi);
415430 using var mediaSelector = new MediaSelector { Visible = false, Enabled = false };
416431 twitter.Initialize("", "", "", 0L);
417432 mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter");
--- a/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs
+++ b/OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs
@@ -71,7 +71,7 @@ namespace OpenTween.Thumbnail.Services
7171 var handler = new HttpMessageHandlerMock();
7272 using (var http = new HttpClient(handler))
7373 {
74- var service = new FoursquareCheckin(http);
74+ var service = new FoursquareCheckin(http, ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"));
7575
7676 handler.Enqueue(x =>
7777 {
@@ -81,8 +81,8 @@ namespace OpenTween.Thumbnail.Services
8181
8282 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
8383
84- Assert.Equal(ApplicationSettings.FoursquareClientId, query["client_id"]);
85- Assert.Equal(ApplicationSettings.FoursquareClientSecret, query["client_secret"]);
84+ Assert.Equal("fake_client_id", query["client_id"]);
85+ Assert.Equal("fake_client_secret", query["client_secret"]);
8686 Assert.NotNull(query["v"]);
8787 Assert.Equal("xxxxxxxx", query["shortId"]);
8888
@@ -109,7 +109,7 @@ namespace OpenTween.Thumbnail.Services
109109 var handler = new HttpMessageHandlerMock();
110110 using (var http = new HttpClient(handler))
111111 {
112- var service = new FoursquareCheckin(http);
112+ var service = new FoursquareCheckin(http, ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"));
113113
114114 handler.Enqueue(x =>
115115 {
@@ -119,8 +119,8 @@ namespace OpenTween.Thumbnail.Services
119119
120120 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
121121
122- Assert.Equal(ApplicationSettings.FoursquareClientId, query["client_id"]);
123- Assert.Equal(ApplicationSettings.FoursquareClientSecret, query["client_secret"]);
122+ Assert.Equal("fake_client_id", query["client_id"]);
123+ Assert.Equal("fake_client_secret", query["client_secret"]);
124124 Assert.NotNull(query["v"]);
125125 Assert.Null(query["signature"]);
126126
@@ -147,7 +147,7 @@ namespace OpenTween.Thumbnail.Services
147147 var handler = new HttpMessageHandlerMock();
148148 using (var http = new HttpClient(handler))
149149 {
150- var service = new FoursquareCheckin(http);
150+ var service = new FoursquareCheckin(http, ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"));
151151
152152 handler.Enqueue(x =>
153153 {
@@ -157,8 +157,8 @@ namespace OpenTween.Thumbnail.Services
157157
158158 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
159159
160- Assert.Equal(ApplicationSettings.FoursquareClientId, query["client_id"]);
161- Assert.Equal(ApplicationSettings.FoursquareClientSecret, query["client_secret"]);
160+ Assert.Equal("fake_client_id", query["client_id"]);
161+ Assert.Equal("fake_client_secret", query["client_secret"]);
162162 Assert.NotNull(query["v"]);
163163 Assert.Equal("aaaaaaa", query["signature"]);
164164
@@ -185,7 +185,7 @@ namespace OpenTween.Thumbnail.Services
185185 var handler = new HttpMessageHandlerMock();
186186 using (var http = new HttpClient(handler))
187187 {
188- var service = new FoursquareCheckin(http);
188+ var service = new FoursquareCheckin(http, ApiKey.Create("fake_client_id"), ApiKey.Create("fake_client_secret"));
189189
190190 handler.Enqueue(x =>
191191 {
@@ -209,6 +209,20 @@ namespace OpenTween.Thumbnail.Services
209209 }
210210
211211 [Fact]
212+ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest()
213+ {
214+ var handler = new HttpMessageHandlerMock();
215+ using var http = new HttpClient(handler);
216+ var service = new FoursquareCheckin(http, ApiKey.Create("%e%INVALID_API_KEY"), ApiKey.Create("%e%INVALID_API_KEY"));
217+
218+ var post = new PostClass();
219+ var thumb = await service.GetThumbnailInfoAsync("https://www.swarmapp.com/c/xxxxxxxx", post, CancellationToken.None)
220+ .ConfigureAwait(false);
221+
222+ Assert.Null(thumb);
223+ }
224+
225+ [Fact]
212226 public void ParseInLocation_Test()
213227 {
214228 var json = @"{
--- a/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs
+++ b/OpenTween.Tests/Thumbnail/Services/TinamiTest.cs
@@ -43,11 +43,11 @@ namespace OpenTween.Thumbnail.Services
4343 public string FakeXml { get; set; } = "";
4444
4545 public TestTinami()
46- : base(null)
46+ : base(ApiKey.Create("fake_api_key"), null)
4747 {
4848 }
4949
50- protected override Task<XDocument> FetchContentInfoApiAsync(string contentId, CancellationToken token)
50+ protected override Task<XDocument> FetchContentInfoApiAsync(string apiKey, string contentId, CancellationToken token)
5151 => Task.FromResult(XDocument.Parse(this.FakeXml));
5252 }
5353
@@ -92,5 +92,16 @@ namespace OpenTween.Thumbnail.Services
9292
9393 Assert.Null(thumbinfo);
9494 }
95+
96+ [Fact]
97+ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest()
98+ {
99+ var service = new Tinami(ApiKey.Create("%e%INVALID_API_KEY"), null);
100+
101+ var thumbinfo = await service.GetThumbnailInfoAsync("http://www.tinami.com/view/12345", new PostClass(), CancellationToken.None)
102+ .ConfigureAwait(false);
103+
104+ Assert.Null(thumbinfo);
105+ }
95106 }
96107 }
--- a/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs
+++ b/OpenTween.Tests/Thumbnail/Services/TumblrTest.cs
@@ -70,7 +70,7 @@ namespace OpenTween.Thumbnail.Services
7070
7171 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
7272
73- Assert.Equal(ApplicationSettings.TumblrConsumerKey, query["api_key"]);
73+ Assert.Equal("fake_api_key", query["api_key"]);
7474 Assert.Equal("1234567", query["id"]);
7575
7676 return new HttpResponseMessage(HttpStatusCode.OK)
@@ -84,7 +84,7 @@ namespace OpenTween.Thumbnail.Services
8484
8585 using (var http = new HttpClient(handler))
8686 {
87- var service = new Tumblr(http);
87+ var service = new Tumblr(ApiKey.Create("fake_api_key"), http);
8888
8989 var url = "http://hoge.tumblr.com/post/1234567/tetetete";
9090 await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None)
@@ -107,7 +107,7 @@ namespace OpenTween.Thumbnail.Services
107107
108108 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
109109
110- Assert.Equal(ApplicationSettings.TumblrConsumerKey, query["api_key"]);
110+ Assert.Equal("fake_api_key", query["api_key"]);
111111 Assert.Equal("1234567", query["id"]);
112112
113113 return new HttpResponseMessage(HttpStatusCode.OK)
@@ -121,7 +121,7 @@ namespace OpenTween.Thumbnail.Services
121121
122122 using (var http = new HttpClient(handler))
123123 {
124- var service = new Tumblr(http);
124+ var service = new Tumblr(ApiKey.Create("fake_api_key"), http);
125125
126126 // Tumblrのカスタムドメイン名を使ってるっぽいURL
127127 var url = "http://tumblr.example.com/post/1234567/tetetete";
@@ -131,5 +131,19 @@ namespace OpenTween.Thumbnail.Services
131131
132132 Assert.Equal(0, handler.QueueCount);
133133 }
134+
135+ [Fact]
136+ public async Task GetThumbnailInfoAsync_ApiKeyErrorTest()
137+ {
138+ var handler = new HttpMessageHandlerMock();
139+
140+ using var http = new HttpClient(handler);
141+ var service = new Tumblr(ApiKey.Create("%e%INVALID_API_KEY"), http);
142+
143+ var url = "http://hoge.tumblr.com/post/1234567/tetetete";
144+ var thumb = await service.GetThumbnailInfoAsync(url, new PostClass(), CancellationToken.None)
145+ .ConfigureAwait(false);
146+ Assert.Null(thumb);
147+ }
134148 }
135149 }
--- a/OpenTween.Tests/TwitterTest.cs
+++ b/OpenTween.Tests/TwitterTest.cs
@@ -24,6 +24,7 @@ using System.Collections.Generic;
2424 using System.Linq;
2525 using System.Text;
2626 using System.Text.RegularExpressions;
27+using OpenTween.Api;
2728 using OpenTween.Api.DataModel;
2829 using OpenTween.Models;
2930 using OpenTween.Setting;
@@ -541,108 +542,108 @@ namespace OpenTween
541542 [Fact]
542543 public void GetTextLengthRemain_Test()
543544 {
544- using (var twitter = new Twitter())
545- {
546- Assert.Equal(280, twitter.GetTextLengthRemain(""));
547- Assert.Equal(272, twitter.GetTextLengthRemain("hogehoge"));
548- }
545+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
546+ using var twitter = new Twitter(twitterApi);
547+
548+ Assert.Equal(280, twitter.GetTextLengthRemain(""));
549+ Assert.Equal(272, twitter.GetTextLengthRemain("hogehoge"));
549550 }
550551
551552 [Fact]
552553 public void GetTextLengthRemain_DirectMessageTest()
553554 {
554- using (var twitter = new Twitter())
555- {
556- // 2015年8月から DM の文字数上限が 10,000 文字に変更された
557- // https://twittercommunity.com/t/41348
558- twitter.Configuration.DmTextCharacterLimit = 10000;
555+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
556+ using var twitter = new Twitter(twitterApi);
559557
560- Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
561- Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
558+ // 2015年8月から DM の文字数上限が 10,000 文字に変更された
559+ // https://twittercommunity.com/t/41348
560+ twitter.Configuration.DmTextCharacterLimit = 10000;
562561
563- // t.co に短縮される分の文字数を考慮
564- twitter.Configuration.ShortUrlLength = 20;
565- Assert.Equal(9971, twitter.GetTextLengthRemain("D twitter hogehoge http://example.com/"));
562+ Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
563+ Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
566564
567- twitter.Configuration.ShortUrlLengthHttps = 21;
568- Assert.Equal(9970, twitter.GetTextLengthRemain("D twitter hogehoge https://example.com/"));
569- }
565+ // t.co に短縮される分の文字数を考慮
566+ twitter.Configuration.ShortUrlLength = 20;
567+ Assert.Equal(9971, twitter.GetTextLengthRemain("D twitter hogehoge http://example.com/"));
568+
569+ twitter.Configuration.ShortUrlLengthHttps = 21;
570+ Assert.Equal(9970, twitter.GetTextLengthRemain("D twitter hogehoge https://example.com/"));
570571 }
571572
572573 [Fact]
573574 public void GetTextLengthRemain_UrlTest()
574575 {
575- using (var twitter = new Twitter())
576- {
577- // t.co に短縮される分の文字数を考慮
578- twitter.TextConfiguration.TransformedURLLength = 20;
579- Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/"));
580- Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
581- Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
582-
583- Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/"));
584- Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
585- Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
586- }
576+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
577+ using var twitter = new Twitter(twitterApi);
578+
579+ // t.co に短縮される分の文字数を考慮
580+ twitter.TextConfiguration.TransformedURLLength = 20;
581+ Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/"));
582+ Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
583+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
584+
585+ Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/"));
586+ Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
587+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
587588 }
588589
589590 [Fact]
590591 public void GetTextLengthRemain_UrlWithoutSchemeTest()
591592 {
592- using (var twitter = new Twitter())
593- {
594- // t.co に短縮される分の文字数を考慮
595- twitter.TextConfiguration.TransformedURLLength = 20;
596- Assert.Equal(260, twitter.GetTextLengthRemain("example.com"));
597- Assert.Equal(260, twitter.GetTextLengthRemain("example.com/hogehoge"));
598- Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge example.com"));
599-
600- // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
601- Assert.Equal(270, twitter.GetTextLengthRemain("example.jp"));
602- // ただし、末尾にパスが続く場合は t.co に短縮される
603- Assert.Equal(260, twitter.GetTextLengthRemain("example.jp/hogehoge"));
604- }
593+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
594+ using var twitter = new Twitter(twitterApi);
595+
596+ // t.co に短縮される分の文字数を考慮
597+ twitter.TextConfiguration.TransformedURLLength = 20;
598+ Assert.Equal(260, twitter.GetTextLengthRemain("example.com"));
599+ Assert.Equal(260, twitter.GetTextLengthRemain("example.com/hogehoge"));
600+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge example.com"));
601+
602+ // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
603+ Assert.Equal(270, twitter.GetTextLengthRemain("example.jp"));
604+ // ただし、末尾にパスが続く場合は t.co に短縮される
605+ Assert.Equal(260, twitter.GetTextLengthRemain("example.jp/hogehoge"));
605606 }
606607
607608 [Fact]
608609 public void GetTextLengthRemain_SurrogatePairTest()
609610 {
610- using (var twitter = new Twitter())
611- {
612- Assert.Equal(278, twitter.GetTextLengthRemain("🍣"));
613- Assert.Equal(267, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
614- }
611+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
612+ using var twitter = new Twitter(twitterApi);
613+
614+ Assert.Equal(278, twitter.GetTextLengthRemain("🍣"));
615+ Assert.Equal(267, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
615616 }
616617
617618 [Fact]
618619 public void GetTextLengthRemain_EmojiTest()
619620 {
620- using (var twitter = new Twitter())
621- {
622- // 絵文字の文字数カウントの仕様変更に対するテストケース
623- // https://twittercommunity.com/t/114607
624-
625- Assert.Equal(279, twitter.GetTextLengthRemain("©")); // 基本多言語面の絵文字
626- Assert.Equal(277, twitter.GetTextLengthRemain("©\uFE0E")); // 異字体セレクタ付き (text style)
627- Assert.Equal(279, twitter.GetTextLengthRemain("©\uFE0F")); // 異字体セレクタ付き (emoji style)
628- Assert.Equal(278, twitter.GetTextLengthRemain("🍣")); // 拡張面の絵文字
629- Assert.Equal(279, twitter.GetTextLengthRemain("#⃣")); // 合字で表現される絵文字
630- Assert.Equal(278, twitter.GetTextLengthRemain("👦\U0001F3FF")); // Emoji modifier 付きの絵文字
631- Assert.Equal(278, twitter.GetTextLengthRemain("\U0001F3FF")); // Emoji modifier 単体
632- Assert.Equal(278, twitter.GetTextLengthRemain("👨\u200D🎨")); // ZWJ で結合された絵文字
633- Assert.Equal(278, twitter.GetTextLengthRemain("🏃\u200D♀\uFE0F")); // ZWJ と異字体セレクタを含む絵文字
634- }
621+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
622+ using var twitter = new Twitter(twitterApi);
623+
624+ // 絵文字の文字数カウントの仕様変更に対するテストケース
625+ // https://twittercommunity.com/t/114607
626+
627+ Assert.Equal(279, twitter.GetTextLengthRemain("©")); // 基本多言語面の絵文字
628+ Assert.Equal(277, twitter.GetTextLengthRemain("©\uFE0E")); // 異字体セレクタ付き (text style)
629+ Assert.Equal(279, twitter.GetTextLengthRemain("©\uFE0F")); // 異字体セレクタ付き (emoji style)
630+ Assert.Equal(278, twitter.GetTextLengthRemain("🍣")); // 拡張面の絵文字
631+ Assert.Equal(279, twitter.GetTextLengthRemain("#⃣")); // 合字で表現される絵文字
632+ Assert.Equal(278, twitter.GetTextLengthRemain("👦\U0001F3FF")); // Emoji modifier 付きの絵文字
633+ Assert.Equal(278, twitter.GetTextLengthRemain("\U0001F3FF")); // Emoji modifier 単体
634+ Assert.Equal(278, twitter.GetTextLengthRemain("👨\u200D🎨")); // ZWJ で結合された絵文字
635+ Assert.Equal(278, twitter.GetTextLengthRemain("🏃\u200D♀\uFE0F")); // ZWJ と異字体セレクタを含む絵文字
635636 }
636637
637638 [Fact]
638639 public void GetTextLengthRemain_BrokenSurrogateTest()
639640 {
640- using (var twitter = new Twitter())
641- {
642- // 投稿欄に IME から絵文字を入力すると HighSurrogate のみ入力された状態で TextChanged イベントが呼ばれることがある
643- Assert.Equal(278, twitter.GetTextLengthRemain("\ud83d"));
644- Assert.Equal(9999, twitter.GetTextLengthRemain("D twitter \ud83d"));
645- }
641+ using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create(""));
642+ using var twitter = new Twitter(twitterApi);
643+
644+ // 投稿欄に IME から絵文字を入力すると HighSurrogate のみ入力された状態で TextChanged イベントが呼ばれることがある
645+ Assert.Equal(278, twitter.GetTextLengthRemain("\ud83d"));
646+ Assert.Equal(9999, twitter.GetTextLengthRemain("D twitter \ud83d"));
646647 }
647648 }
648649 }
--- a/OpenTween/Api/BitlyApi.cs
+++ b/OpenTween/Api/BitlyApi.cs
@@ -45,16 +45,23 @@ namespace OpenTween.Api
4545 public string EndUserLoginName { get; set; } = "";
4646 public string EndUserApiKey { get; set; } = "";
4747
48+ private readonly ApiKey clientId;
49+ private readonly ApiKey clientSecret;
50+
4851 private HttpClient http => this.localHttpClient ?? Networking.Http;
4952 private readonly HttpClient? localHttpClient;
5053
5154 public BitlyApi()
52- : this(null)
55+ : this(ApplicationSettings.BitlyClientId, ApplicationSettings.BitlyClientSecret, null)
5356 {
5457 }
5558
56- public BitlyApi(HttpClient? http)
57- => this.localHttpClient = http;
59+ public BitlyApi(ApiKey clientId, ApiKey clientSecret, HttpClient? http)
60+ {
61+ this.clientId = clientId;
62+ this.clientSecret = clientSecret;
63+ this.localHttpClient = http;
64+ }
5865
5966 public async Task<Uri> ShortenAsync(Uri srcUri, string? domain = null)
6067 {
@@ -104,7 +111,12 @@ namespace OpenTween.Api
104111 using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
105112 using var postContent = new FormUrlEncodedContent(param);
106113
107- var authzParam = ApplicationSettings.BitlyClientId + ":" + ApplicationSettings.BitlyClientSecret;
114+ if (!(this.clientId, this.clientSecret).TryGetValue(out var keyPair))
115+ throw new WebApiException("bit.ly APIキーが使用できません");
116+
117+ var (clientId, clientSecret) = keyPair;
118+
119+ var authzParam = clientId + ":" + clientSecret;
108120 request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(authzParam)));
109121
110122 request.Content = postContent;
--- a/OpenTween/Api/ImgurApi.cs
+++ b/OpenTween/Api/ImgurApi.cs
@@ -37,17 +37,17 @@ namespace OpenTween.Api
3737 {
3838 public class ImgurApi : IImgurApi
3939 {
40- private readonly string clientId;
40+ private readonly ApiKey clientId;
4141 private readonly HttpClient http;
4242
4343 public static readonly Uri UploadEndpoint = new Uri("https://api.imgur.com/3/image.xml");
4444
4545 public ImgurApi()
46- : this(ApplicationSettings.ImgurClientID, null)
46+ : this(ApplicationSettings.ImgurClientId, null)
4747 {
4848 }
4949
50- public ImgurApi(string clientId, HttpClient? http)
50+ public ImgurApi(ApiKey clientId, HttpClient? http)
5151 {
5252 this.clientId = clientId;
5353
@@ -97,6 +97,9 @@ namespace OpenTween.Api
9797
9898 private async Task<HttpResponseMessage> SendRequestAsync(IMediaItem item, string title)
9999 {
100+ if (!this.clientId.TryGetValue(out var clientId))
101+ throw new WebApiException("Err:imgur APIキーが使用できません");
102+
100103 using var content = new MultipartFormDataContent();
101104 using var mediaStream = item.OpenRead();
102105 using var mediaContent = new StreamContent(mediaStream);
@@ -106,7 +109,7 @@ namespace OpenTween.Api
106109 content.Add(titleContent, "title");
107110
108111 using var request = new HttpRequestMessage(HttpMethod.Post, UploadEndpoint);
109- request.Headers.Authorization = new AuthenticationHeaderValue("Client-ID", this.clientId);
112+ request.Headers.Authorization = new AuthenticationHeaderValue("Client-ID", clientId);
110113 request.Content = content;
111114
112115 return await this.http.SendAsync(request)
--- a/OpenTween/Api/MicrosoftTranslatorApi.cs
+++ b/OpenTween/Api/MicrosoftTranslatorApi.cs
@@ -44,19 +44,27 @@ namespace OpenTween.Api
4444 public string AccessToken { get; internal set; } = "";
4545 public DateTimeUtc RefreshAccessTokenAt { get; internal set; } = DateTimeUtc.MinValue;
4646
47+ private readonly ApiKey subscriptionKey;
48+
4749 private HttpClient Http => this.localHttpClient ?? Networking.Http;
4850 private readonly HttpClient? localHttpClient;
4951
5052 public MicrosoftTranslatorApi()
51- : this(null)
53+ : this(ApplicationSettings.TranslatorSubscriptionKey, null)
5254 {
5355 }
5456
55- public MicrosoftTranslatorApi(HttpClient? http)
56- => this.localHttpClient = http;
57+ public MicrosoftTranslatorApi(ApiKey subscriptionKey, HttpClient? http)
58+ {
59+ this.subscriptionKey = subscriptionKey;
60+ this.localHttpClient = http;
61+ }
5762
5863 public async Task<string> TranslateAsync(string text, string langTo, string? langFrom = null)
5964 {
65+ if (!this.subscriptionKey.TryGetValue(out _))
66+ throw new WebApiException("APIキーが使用できません");
67+
6068 await this.UpdateAccessTokenIfExpired()
6169 .ConfigureAwait(false);
6270
@@ -83,7 +91,8 @@ namespace OpenTween.Api
8391 using var response = await this.Http.SendAsync(request)
8492 .ConfigureAwait(false);
8593
86- response.EnsureSuccessStatusCode();
94+ if (!response.IsSuccessStatusCode)
95+ throw new WebApiException(response.StatusCode.ToString());
8796
8897 var responseJson = await response.Content.ReadAsByteArrayAsync()
8998 .ConfigureAwait(false);
@@ -112,7 +121,7 @@ namespace OpenTween.Api
112121 internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
113122 {
114123 using var request = new HttpRequestMessage(HttpMethod.Post, IssueTokenEndpoint);
115- request.Headers.Add("Ocp-Apim-Subscription-Key", ApplicationSettings.TranslatorSubscriptionKey);
124+ request.Headers.Add("Ocp-Apim-Subscription-Key", this.subscriptionKey.Value);
116125
117126 using var response = await this.Http.SendAsync(request)
118127 .ConfigureAwait(false);
--- a/OpenTween/Api/MobypictureApi.cs
+++ b/OpenTween/Api/MobypictureApi.cs
@@ -36,7 +36,7 @@ namespace OpenTween.Api
3636 {
3737 public class MobypictureApi : IMobypictureApi
3838 {
39- private readonly string apiKey;
39+ private readonly ApiKey apiKey;
4040 private readonly HttpClient http;
4141
4242 public static readonly Uri UploadEndpoint = new Uri("https://api.mobypicture.com/2.0/upload.xml");
@@ -49,7 +49,7 @@ namespace OpenTween.Api
4949 {
5050 }
5151
52- public MobypictureApi(string apiKey, TwitterApi twitterApi)
52+ public MobypictureApi(ApiKey apiKey, TwitterApi twitterApi)
5353 {
5454 this.apiKey = apiKey;
5555
@@ -58,7 +58,7 @@ namespace OpenTween.Api
5858 this.http.Timeout = Networking.UploadImageTimeout;
5959 }
6060
61- public MobypictureApi(string apiKey, HttpClient http)
61+ public MobypictureApi(ApiKey apiKey, HttpClient http)
6262 {
6363 this.apiKey = apiKey;
6464 this.http = http;
@@ -98,13 +98,16 @@ namespace OpenTween.Api
9898
9999 private async Task<HttpResponseMessage> SendRequestAsync(IMediaItem item, string message)
100100 {
101+ if (!this.apiKey.TryGetValue(out var apiKey))
102+ throw new WebApiException("Err:Mobypicture APIキーが使用できません");
103+
101104 // 参照: http://developers.mobypicture.com/documentation/2-0/upload/
102105
103106 using var request = new HttpRequestMessage(HttpMethod.Post, UploadEndpoint);
104107 using var multipart = new MultipartFormDataContent();
105108 request.Content = multipart;
106109
107- using var apiKeyContent = new StringContent(this.apiKey);
110+ using var apiKeyContent = new StringContent(apiKey);
108111 using var messageContent = new StringContent(message);
109112 using var mediaStream = item.OpenRead();
110113 using var mediaContent = new StreamContent(mediaStream);
--- a/OpenTween/Api/TwitterApi.cs
+++ b/OpenTween/Api/TwitterApi.cs
@@ -42,9 +42,18 @@ namespace OpenTween.Api
4242
4343 internal IApiConnection? apiConnection;
4444
45+ private readonly ApiKey consumerKey;
46+ private readonly ApiKey consumerSecret;
47+
48+ public TwitterApi(ApiKey consumerKey, ApiKey consumerSecret)
49+ {
50+ this.consumerKey = consumerKey;
51+ this.consumerSecret = consumerSecret;
52+ }
53+
4554 public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
4655 {
47- var newInstance = new TwitterApiConnection(accessToken, accessSecret);
56+ var newInstance = new TwitterApiConnection(this.consumerKey, this.consumerSecret, accessToken, accessSecret);
4857 var oldInstance = Interlocked.Exchange(ref this.apiConnection, newInstance);
4958 oldInstance?.Dispose();
5059
--- /dev/null
+++ b/OpenTween/ApiKey.cs
@@ -0,0 +1,240 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2022 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3+// All rights reserved.
4+//
5+// This file is part of OpenTween.
6+//
7+// This program is free software; you can redistribute it and/or modify it
8+// under the terms of the GNU General public License as published by the Free
9+// Software Foundation; either version 3 of the License, or (at your option)
10+// any later version.
11+//
12+// This program is distributed in the hope that it will be useful, but
13+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
15+// for more details.
16+//
17+// You should have received a copy of the GNU General public License along
18+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20+// Boston, MA 02110-1301, USA.
21+
22+#nullable enable
23+
24+using System;
25+using System.Collections.Generic;
26+using System.Diagnostics.CodeAnalysis;
27+using System.IO;
28+using System.Linq;
29+using System.Runtime.Serialization;
30+using System.Security.Cryptography;
31+using System.Text;
32+using System.Threading.Tasks;
33+
34+namespace OpenTween
35+{
36+ public class ApiKey
37+ {
38+ private static readonly string EncryptionPrefix = "%e%";
39+ private static readonly int SaltSize = 16;
40+ private static readonly int KeySize = 32;
41+ private static readonly int BlockSize = 16;
42+ private static readonly int IterationCount = 10_000;
43+ private static readonly HashAlgorithmName HashAlgorithm = HashAlgorithmName.SHA256;
44+
45+ private readonly string rawKey;
46+ private readonly Lazy<string> decryptLazy;
47+
48+ /// <summary>
49+ /// 平文の API キー
50+ /// </summary>
51+ /// <exception cref="ApiKeyDecryptException" />
52+ public string Value => this.decryptLazy.Value;
53+
54+ private ApiKey(string password, string rawKey)
55+ {
56+ this.rawKey = rawKey;
57+ this.decryptLazy = new Lazy<string>(
58+ () => Decrypt(password, this.rawKey)
59+ );
60+ }
61+
62+ /// <summary>
63+ /// 平文の API キーを返します
64+ /// </summary>
65+ /// <returns>
66+ /// 成功した場合は true、暗号化された API キーの復号に失敗した場合は false を返します
67+ /// </returns>
68+ public bool TryGetValue([NotNullWhen(true)]out string output)
69+ {
70+ try
71+ {
72+ output = this.Value;
73+ return true;
74+ }
75+ catch (ApiKeyDecryptException)
76+ {
77+ output = null!;
78+ return false;
79+ }
80+ }
81+
82+ /// <summary>
83+ /// <see cref="ApiKey"/> インスタンスを作成します
84+ /// </summary>
85+ public static ApiKey Create(string rawKey)
86+ => Create(ApplicationSettings.EncryptionPassword, rawKey);
87+
88+ /// <summary>
89+ /// <see cref="ApiKey"/> インスタンスを作成します
90+ /// </summary>
91+ public static ApiKey Create(string password, string rawKey)
92+ => new ApiKey(password, rawKey);
93+
94+ /// <summary>
95+ /// 指定された文字列を暗号化して返します
96+ /// </summary>
97+ public static string Encrypt(string password, string plainText)
98+ {
99+ var salt = GenerateSalt();
100+ var (encryptionKey, iv, macKey) = GenerateKeyAndIV(password, salt);
101+
102+ using var aes = CreateAes();
103+ aes.Key = encryptionKey;
104+ aes.IV = iv;
105+
106+ var plainBytes = Encoding.UTF8.GetBytes(plainText);
107+ using var encryptor = aes.CreateEncryptor();
108+ using var memoryStream = new MemoryStream(plainBytes);
109+ using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read);
110+
111+ using var cipherStream = new MemoryStream(capacity: plainBytes.Length + BlockSize + SaltSize);
112+ cryptoStream.CopyTo(cipherStream);
113+
114+ var cipherBytes = cipherStream.ToArray();
115+ var macBytes = GenerateMAC(plainBytes, macKey);
116+ var saltText = Convert.ToBase64String(salt);
117+ var cipherText = Convert.ToBase64String(cipherBytes);
118+ var macText = Convert.ToBase64String(macBytes);
119+
120+ return $"{EncryptionPrefix}{saltText}%{cipherText}%{macText}";
121+ }
122+
123+ /// <summary>
124+ /// 暗号化された文字列を復号します
125+ /// </summary>
126+ /// <exception cref="ApiKeyDecryptException" />
127+ public static string Decrypt(string password, string encryptedText)
128+ {
129+ // 先頭が "%e%" から始まっていない場合、APIキーは平文のまま書かれているものとして扱う
130+ if (!encryptedText.StartsWith(EncryptionPrefix))
131+ return encryptedText;
132+
133+ var splitted = encryptedText.Split('%');
134+ if (splitted.Length != 5)
135+ throw new ApiKeyDecryptException("暗号文のフォーマットが不正です");
136+
137+ byte[] salt, cipherBytes, macBytes;
138+ try
139+ {
140+ // e.g. "%e%...salt...%...cipher...%...mac..."
141+ salt = Convert.FromBase64String(splitted[2]);
142+ cipherBytes = Convert.FromBase64String(splitted[3]);
143+ macBytes = Convert.FromBase64String(splitted[4]);
144+ }
145+ catch (FormatException ex)
146+ {
147+ throw new ApiKeyDecryptException("不正な Base64 フォーマットです", ex);
148+ }
149+
150+ var (encryptionKey, iv, macKey) = GenerateKeyAndIV(password, salt);
151+ using var aes = CreateAes();
152+ aes.Key = encryptionKey;
153+ aes.IV = iv;
154+
155+ byte[] decryptedBytes;
156+ try
157+ {
158+ using var decryptor = aes.CreateDecryptor();
159+ using var memoryStream = new MemoryStream(cipherBytes);
160+ using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
161+
162+ using var decryptedStream = new MemoryStream(capacity: cipherBytes.Length);
163+ cryptoStream.CopyTo(decryptedStream);
164+
165+ decryptedBytes = decryptedStream.ToArray();
166+ }
167+ catch (CryptographicException ex)
168+ {
169+ throw new ApiKeyDecryptException("API キーの復号に失敗しました", ex);
170+ }
171+
172+ var macBytesExpected = GenerateMAC(decryptedBytes, macKey);
173+
174+ var isValid = macBytes.Length == macBytesExpected.Length &&
175+ Enumerable.Zip(macBytes, macBytesExpected, (x, y) => x == y).All(x => x);
176+ if (!isValid)
177+ throw new ApiKeyDecryptException("ダイジェストが一致しません");
178+
179+ return Encoding.UTF8.GetString(decryptedBytes);
180+ }
181+
182+ private static byte[] GenerateSalt()
183+ {
184+ using var random = new RNGCryptoServiceProvider();
185+ var salt = new byte[SaltSize];
186+ random.GetBytes(salt);
187+ return salt;
188+ }
189+
190+ private static (byte[], byte[], byte[]) GenerateKeyAndIV(string password, byte[] salt)
191+ {
192+ using var generator = new Rfc2898DeriveBytes(password, salt, IterationCount, HashAlgorithm);
193+ var encryptionKey = generator.GetBytes(KeySize);
194+ var iv = generator.GetBytes(BlockSize);
195+ var macKey = generator.GetBytes(KeySize);
196+ return (encryptionKey, iv, macKey);
197+ }
198+
199+ private static byte[] GenerateMAC(byte[] source, byte[] key)
200+ {
201+ using var hmac = new HMACSHA256(key);
202+ return hmac.ComputeHash(source);
203+ }
204+
205+ private static Aes CreateAes()
206+ {
207+ var aes = Aes.Create();
208+ aes.KeySize = KeySize * 8;
209+ aes.BlockSize = BlockSize * 8;
210+ aes.Mode = CipherMode.CBC;
211+ aes.Padding = PaddingMode.PKCS7;
212+ return aes;
213+ }
214+ }
215+
216+ public static class ApiKeyExtensions
217+ {
218+ public static bool TryGetValue(this ValueTuple<ApiKey, ApiKey> apiKeys, out ValueTuple<string, string> decryptedKeys)
219+ {
220+ var (apiKey1, apiKey2) = apiKeys;
221+ if (apiKey1.TryGetValue(out var decrypted1) && apiKey2.TryGetValue(out var decrypted2))
222+ {
223+ decryptedKeys = (decrypted1, decrypted2);
224+ return true;
225+ }
226+
227+ decryptedKeys = ("", "");
228+ return false;
229+ }
230+ }
231+
232+ [Serializable]
233+ public class ApiKeyDecryptException : Exception
234+ {
235+ public ApiKeyDecryptException() { }
236+ public ApiKeyDecryptException(string message) : base(message) { }
237+ public ApiKeyDecryptException(string message, Exception innerException) : base(message, innerException) { }
238+ protected ApiKeyDecryptException(SerializationInfo info, StreamingContext context) : base(info, context) { }
239+ }
240+}
--- a/OpenTween/ApplicationSettings.cs
+++ b/OpenTween/ApplicationSettings.cs
@@ -23,6 +23,7 @@
2323
2424 using System;
2525 using System.Collections.Generic;
26+using System.IO;
2627 using System.Linq;
2728 using System.Text;
2829 using System.Windows.Forms;
@@ -105,14 +106,26 @@ namespace OpenTween
105106 public static readonly string VersionInfoUrl = "https://www.opentween.org/status/version.txt";
106107
107108 //=====================================================================
109+ // 暗号化キー
110+
111+ /// <summary>
112+ /// APIキーの暗号化・復号に使用するパスワード
113+ /// </summary>
114+ public static readonly string EncryptionPassword = ApplicationName;
115+
116+ //=====================================================================
108117 // Twitter
109- // https://dev.twitter.com/ から取得できます。
118+ // https://developer.twitter.com/ から取得できます。
119+
120+ /// <summary>
121+ /// Twitter API Key
122+ /// </summary>
123+ public static readonly ApiKey TwitterConsumerKey = ApiKey.Create("zIoJPq3FsuViPTAs89FetDHYz");
110124
111125 /// <summary>
112- /// Twitter コンシューマーキー
126+ /// Twitter API Key Secret
113127 /// </summary>
114- public const string TwitterConsumerKey = "zIoJPq3FsuViPTAs89FetDHYz";
115- public const string TwitterConsumerSecret = "prTAs2fqLv12nHxlMoLQZT8AkpZt0yYb8A7ktGS2VYeRj0TddS";
128+ public static readonly ApiKey TwitterConsumerSecret = ApiKey.Create("prTAs2fqLv12nHxlMoLQZT8AkpZt0yYb8A7ktGS2VYeRj0TddS");
116129
117130 //=====================================================================
118131 // Foursquare
@@ -121,12 +134,12 @@ namespace OpenTween
121134 /// <summary>
122135 /// Foursquare Client Id
123136 /// </summary>
124- public const string FoursquareClientId = "5H3K5YQPT55DNQUFEOAJFNJA5D01ZJGO2ITEAJ3ASRIDONUB";
137+ public static readonly ApiKey FoursquareClientId = ApiKey.Create("5H3K5YQPT55DNQUFEOAJFNJA5D01ZJGO2ITEAJ3ASRIDONUB");
125138
126139 /// <summary>
127140 /// Foursquare Client Secret
128141 /// </summary>
129- public const string FoursquareClientSecret = "JFRHP1L451M3AEPF11UZLTIIUZCZTZRVHVOWB5TQ0AJOVPBB";
142+ public static readonly ApiKey FoursquareClientSecret = ApiKey.Create("JFRHP1L451M3AEPF11UZLTIIUZCZTZRVHVOWB5TQ0AJOVPBB");
130143
131144 //=====================================================================
132145 // bit.ly
@@ -135,12 +148,12 @@ namespace OpenTween
135148 /// <summary>
136149 /// bit.ly Client ID
137150 /// </summary>
138- public const string BitlyClientId = "ddab8ec50f4459c315cbde9d923cf490923b6d2e";
151+ public static readonly ApiKey BitlyClientId = ApiKey.Create("ddab8ec50f4459c315cbde9d923cf490923b6d2e");
139152
140153 /// <summary>
141154 /// bit.ly Client Secret
142155 /// </summary>
143- public const string BitlyClientSecret = "485c9d03dd264f8eeb4fc65d38e2762c4420cee7";
156+ public static readonly ApiKey BitlyClientSecret = ApiKey.Create("485c9d03dd264f8eeb4fc65d38e2762c4420cee7");
144157
145158 //=====================================================================
146159 // TINAMI
@@ -149,7 +162,7 @@ namespace OpenTween
149162 /// <summary>
150163 /// TINAMI APIキー
151164 /// </summary>
152- public const string TINAMIApiKey = "4f48bb4858d36";
165+ public static readonly ApiKey TINAMIApiKey = ApiKey.Create("4f48bb4858d36");
153166
154167 //=====================================================================
155168 // Microsoft Translator API (Cognitive Service)
@@ -158,7 +171,7 @@ namespace OpenTween
158171 /// <summary>
159172 /// Translator Text API Subscription Key
160173 /// </summary>
161- public readonly static string TranslatorSubscriptionKey = "6c47d2ea341148bf856bdbfafd429db7";
174+ public static readonly ApiKey TranslatorSubscriptionKey = ApiKey.Create("6c47d2ea341148bf856bdbfafd429db7");
162175
163176 //=====================================================================
164177 // Imgur
@@ -167,12 +180,12 @@ namespace OpenTween
167180 /// <summary>
168181 /// Imgur Client ID
169182 /// </summary>
170- public readonly static string ImgurClientID = "a5fff36fb83568c";
183+ public static readonly ApiKey ImgurClientId = ApiKey.Create("a5fff36fb83568c");
171184
172185 /// <summary>
173186 /// Imgur Client Secret
174187 /// </summary>
175- public readonly static string ImgurClientSecret = "af5d668a9aa83b34a8f0f735e12073edafbc9a5d";
188+ public static readonly ApiKey ImgurClientSecret = ApiKey.Create("af5d668a9aa83b34a8f0f735e12073edafbc9a5d");
176189
177190 //=====================================================================
178191 // Mobypicture
@@ -181,7 +194,7 @@ namespace OpenTween
181194 /// <summary>
182195 /// Mobypicture Developer Key
183196 /// </summary>
184- public readonly static string MobypictureKey = "quPWTX0UrPHxqdH7";
197+ public static readonly ApiKey MobypictureKey = ApiKey.Create("quPWTX0UrPHxqdH7");
185198
186199 //=====================================================================
187200 // Tumblr
@@ -190,6 +203,6 @@ namespace OpenTween
190203 /// <summary>
191204 /// Tumblr OAuth Consumer Key
192205 /// </summary>
193- public readonly static string TumblrConsumerKey = "Nsk62V6wMIqVNbiGyN0g3aDGBlgU7Fcb9GJ8Se0z2MUDHAY15l";
206+ public static readonly ApiKey TumblrConsumerKey = ApiKey.Create("Nsk62V6wMIqVNbiGyN0g3aDGBlgU7Fcb9GJ8Se0z2MUDHAY15l");
194207 }
195208 }
--- a/OpenTween/Bing.cs
+++ b/OpenTween/Bing.cs
@@ -170,12 +170,7 @@ namespace OpenTween
170170 private readonly MicrosoftTranslatorApi translatorApi;
171171
172172 public Bing()
173- : this(null)
174- {
175- }
176-
177- public Bing(HttpClient? http)
178- => this.translatorApi = new MicrosoftTranslatorApi(http);
173+ => this.translatorApi = new MicrosoftTranslatorApi();
179174
180175 /// <summary>
181176 /// Microsoft Translator API を使用した翻訳を非同期に行います
--- a/OpenTween/Connection/OAuthEchoHandler.cs
+++ b/OpenTween/Connection/OAuthEchoHandler.cs
@@ -53,7 +53,7 @@ namespace OpenTween.Connection
5353 }
5454
5555 public static OAuthEchoHandler CreateHandler(HttpMessageHandler innerHandler, Uri authServiceProvider,
56- string consumerKey, string consumerSecret, string accessToken, string accessSecret, Uri? realm = null)
56+ ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret, Uri? realm = null)
5757 {
5858 var credential = OAuthUtility.CreateAuthorization("GET", authServiceProvider, null,
5959 consumerKey, consumerSecret, accessToken, accessSecret, realm?.AbsoluteUri);
--- a/OpenTween/Connection/OAuthHandler.cs
+++ b/OpenTween/Connection/OAuthHandler.cs
@@ -38,12 +38,12 @@ namespace OpenTween.Connection
3838 /// </summary>
3939 public class OAuthHandler : DelegatingHandler
4040 {
41- public string ConsumerKey { get; }
42- public string ConsumerSecret { get; }
41+ public ApiKey ConsumerKey { get; }
42+ public ApiKey ConsumerSecret { get; }
4343 public string AccessToken { get; }
4444 public string AccessSecret { get; }
4545
46- public OAuthHandler(HttpMessageHandler innerHandler, string consumerKey, string consumerSecret, string accessToken, string accessSecret)
46+ public OAuthHandler(HttpMessageHandler innerHandler, ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret)
4747 : base(innerHandler)
4848 {
4949 this.ConsumerKey = consumerKey;
--- a/OpenTween/Connection/OAuthUtility.cs
+++ b/OpenTween/Connection/OAuthUtility.cs
@@ -56,7 +56,7 @@ namespace OpenTween.Connection
5656 /// <param name="tokenSecret">アクセストークンシークレット。認証処理では空文字列</param>
5757 /// <param name="realm">realm (必要な場合のみ)</param>
5858 public static string CreateAuthorization(string httpMethod, Uri requestUri, IEnumerable<KeyValuePair<string, string>>? query,
59- string consumerKey, string consumerSecret, string token, string tokenSecret,
59+ ApiKey consumerKey, ApiKey consumerSecret, string token, string tokenSecret,
6060 string? realm = null)
6161 {
6262 // OAuth共通情報取得
@@ -86,11 +86,11 @@ namespace OpenTween.Connection
8686 /// </summary>
8787 /// <param name="token">アクセストークン、もしくはリクエストトークン。未取得なら空文字列</param>
8888 /// <returns>OAuth情報のディクショナリ</returns>
89- public static Dictionary<string, string> GetOAuthParameter(string consumerKey, string token)
89+ public static Dictionary<string, string> GetOAuthParameter(ApiKey consumerKey, string token)
9090 {
9191 var parameter = new Dictionary<string, string>
9292 {
93- ["oauth_consumer_key"] = consumerKey,
93+ ["oauth_consumer_key"] = consumerKey.Value,
9494 ["oauth_signature_method"] = "HMAC-SHA1",
9595 ["oauth_timestamp"] = DateTimeUtc.Now.ToUnixTime().ToString(), // epoch秒
9696 ["oauth_nonce"] = NonceRandom.Next(123400, 9999999).ToString(),
@@ -109,7 +109,7 @@ namespace OpenTween.Connection
109109 /// <param name="uri">アクセス先Uri</param>
110110 /// <param name="parameter">クエリ、もしくはPOSTデータ</param>
111111 /// <returns>署名文字列</returns>
112- public static string CreateSignature(string consumerSecret, string? tokenSecret, string method, Uri uri, Dictionary<string, string> parameter)
112+ public static string CreateSignature(ApiKey consumerSecret, string? tokenSecret, string method, Uri uri, Dictionary<string, string> parameter)
113113 {
114114 // パラメタをソート済みディクショナリに詰替(OAuthの仕様)
115115 var sorted = new SortedDictionary<string, string>(parameter);
@@ -120,7 +120,7 @@ namespace OpenTween.Connection
120120 // 署名のベース文字列生成(&区切り)。クエリ形式文字列は再エンコードする
121121 var signatureBase = string.Format("{0}&{1}&{2}", method, MyCommon.UrlEncode(url), MyCommon.UrlEncode(paramString));
122122 // 署名鍵の文字列をコンシューマー秘密鍵とアクセストークン秘密鍵から生成(&区切り。アクセストークン秘密鍵なくても&残すこと)
123- var key = MyCommon.UrlEncode(consumerSecret) + "&";
123+ var key = MyCommon.UrlEncode(consumerSecret.Value) + "&";
124124 if (!MyCommon.IsNullOrEmpty(tokenSecret))
125125 key += MyCommon.UrlEncode(tokenSecret);
126126 // 鍵生成&署名生成
--- a/OpenTween/Connection/TwitterApiConnection.cs
+++ b/OpenTween/Connection/TwitterApiConnection.cs
@@ -58,8 +58,13 @@ namespace OpenTween.Connection
5858 internal HttpClient httpUpload = null!;
5959 internal HttpClient httpStreaming = null!;
6060
61- public TwitterApiConnection(string accessToken, string accessSecret)
61+ private readonly ApiKey consumerKey;
62+ private readonly ApiKey consumerSecret;
63+
64+ public TwitterApiConnection(ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret)
6265 {
66+ this.consumerKey = consumerKey;
67+ this.consumerSecret = consumerSecret;
6368 this.AccessToken = accessToken;
6469 this.AccessSecret = accessSecret;
6570
@@ -69,12 +74,12 @@ namespace OpenTween.Connection
6974
7075 private void InitializeHttpClients()
7176 {
72- this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
77+ this.http = InitializeHttpClient(this.consumerKey, this.consumerSecret, this.AccessToken, this.AccessSecret);
7378
74- this.httpUpload = InitializeHttpClient(this.AccessToken, this.AccessSecret);
79+ this.httpUpload = InitializeHttpClient(this.consumerKey, this.consumerSecret, this.AccessToken, this.AccessSecret);
7580 this.httpUpload.Timeout = Networking.UploadImageTimeout;
7681
77- this.httpStreaming = InitializeHttpClient(this.AccessToken, this.AccessSecret, disableGzip: true);
82+ this.httpStreaming = InitializeHttpClient(this.consumerKey, this.consumerSecret, this.AccessToken, this.AccessSecret, disableGzip: true);
7883 this.httpStreaming.Timeout = Timeout.InfiniteTimeSpan;
7984 }
8085
@@ -433,8 +438,7 @@ namespace OpenTween.Connection
433438 var uri = new Uri(RestApiBase, authServiceProvider);
434439
435440 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
436- ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
437- this.AccessToken, this.AccessSecret, realm);
441+ this.consumerKey, this.consumerSecret, this.AccessToken, this.AccessSecret, realm);
438442 }
439443
440444 public void Dispose()
@@ -465,13 +469,16 @@ namespace OpenTween.Connection
465469 private void Networking_WebProxyChanged(object sender, EventArgs e)
466470 => this.InitializeHttpClients();
467471
468- public static async Task<(string Token, string TokenSecret)> GetRequestTokenAsync()
472+ public static Task<(string Token, string TokenSecret)> GetRequestTokenAsync()
473+ => GetRequestTokenAsync(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret);
474+
475+ public static async Task<(string Token, string TokenSecret)> GetRequestTokenAsync(ApiKey consumerKey, ApiKey consumerSecret)
469476 {
470477 var param = new Dictionary<string, string>
471478 {
472479 ["oauth_callback"] = "oob",
473480 };
474- var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
481+ var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, consumerKey, consumerSecret, oauthToken: null)
475482 .ConfigureAwait(false);
476483
477484 return (response["oauth_token"], response["oauth_token_secret"]);
@@ -490,26 +497,29 @@ namespace OpenTween.Connection
490497 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
491498 }
492499
493- public static async Task<IDictionary<string, string>> GetAccessTokenAsync((string Token, string TokenSecret) requestToken, string verifier)
500+ public static Task<IDictionary<string, string>> GetAccessTokenAsync((string Token, string TokenSecret) requestToken, string verifier)
501+ => GetAccessTokenAsync(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, requestToken, verifier);
502+
503+ public static async Task<IDictionary<string, string>> GetAccessTokenAsync(ApiKey consumerKey, ApiKey consumerSecret, (string Token, string TokenSecret) requestToken, string verifier)
494504 {
495505 var param = new Dictionary<string, string>
496506 {
497507 ["oauth_verifier"] = verifier,
498508 };
499- var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
509+ var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, consumerKey, consumerSecret, requestToken)
500510 .ConfigureAwait(false);
501511
502512 return response;
503513 }
504514
505515 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
506- (string Token, string TokenSecret)? oauthToken)
516+ ApiKey consumerKey, ApiKey consumerSecret, (string Token, string TokenSecret)? oauthToken)
507517 {
508518 HttpClient authorizeClient;
509519 if (oauthToken != null)
510- authorizeClient = InitializeHttpClient(oauthToken.Value.Token, oauthToken.Value.TokenSecret);
520+ authorizeClient = InitializeHttpClient(consumerKey, consumerSecret, oauthToken.Value.Token, oauthToken.Value.TokenSecret);
511521 else
512- authorizeClient = InitializeHttpClient("", "");
522+ authorizeClient = InitializeHttpClient(consumerKey, consumerSecret, "", "");
513523
514524 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
515525
@@ -541,7 +551,7 @@ namespace OpenTween.Connection
541551 }
542552 }
543553
544- private static HttpClient InitializeHttpClient(string accessToken, string accessSecret, bool disableGzip = false)
554+ private static HttpClient InitializeHttpClient(ApiKey consumerKey, ApiKey consumerSecret, string accessToken, string accessSecret, bool disableGzip = false)
545555 {
546556 var innerHandler = Networking.CreateHttpClientHandler();
547557 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -549,9 +559,7 @@ namespace OpenTween.Connection
549559 if (disableGzip)
550560 innerHandler.AutomaticDecompression = DecompressionMethods.None;
551561
552- var handler = new OAuthHandler(innerHandler,
553- ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
554- accessToken, accessSecret);
562+ var handler = new OAuthHandler(innerHandler, consumerKey, consumerSecret, accessToken, accessSecret);
555563
556564 return Networking.CreateHttpClient(handler);
557565 }
--- /dev/null
+++ b/OpenTween/EncryptApiKeyDialog.Designer.cs
@@ -0,0 +1,131 @@
1+namespace OpenTween
2+{
3+ partial class EncryptApiKeyDialog
4+ {
5+ /// <summary>
6+ /// Required designer variable.
7+ /// </summary>
8+ private System.ComponentModel.IContainer components = null;
9+
10+ /// <summary>
11+ /// Clean up any resources being used.
12+ /// </summary>
13+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
14+ protected override void Dispose(bool disposing)
15+ {
16+ if (disposing && (components != null))
17+ {
18+ components.Dispose();
19+ }
20+ base.Dispose(disposing);
21+ }
22+
23+ #region Windows Form Designer generated code
24+
25+ /// <summary>
26+ /// Required method for Designer support - do not modify
27+ /// the contents of this method with the code editor.
28+ /// </summary>
29+ private void InitializeComponent()
30+ {
31+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EncryptApiKeyDialog));
32+ this.label1 = new System.Windows.Forms.Label();
33+ this.textBoxPlainText = new System.Windows.Forms.TextBox();
34+ this.textBoxPassword = new System.Windows.Forms.TextBox();
35+ this.textBoxEncryptedText = new System.Windows.Forms.TextBox();
36+ this.label2 = new System.Windows.Forms.Label();
37+ this.label3 = new System.Windows.Forms.Label();
38+ this.buttonEncrypt = new System.Windows.Forms.Button();
39+ this.buttonClose = new System.Windows.Forms.Button();
40+ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
41+ this.tableLayoutPanel1.SuspendLayout();
42+ this.SuspendLayout();
43+ //
44+ // label1
45+ //
46+ resources.ApplyResources(this.label1, "label1");
47+ this.label1.Name = "label1";
48+ //
49+ // textBoxPlainText
50+ //
51+ resources.ApplyResources(this.textBoxPlainText, "textBoxPlainText");
52+ this.textBoxPlainText.Name = "textBoxPlainText";
53+ //
54+ // textBoxPassword
55+ //
56+ resources.ApplyResources(this.textBoxPassword, "textBoxPassword");
57+ this.textBoxPassword.Name = "textBoxPassword";
58+ this.textBoxPassword.ReadOnly = true;
59+ //
60+ // textBoxEncryptedText
61+ //
62+ resources.ApplyResources(this.textBoxEncryptedText, "textBoxEncryptedText");
63+ this.textBoxEncryptedText.Name = "textBoxEncryptedText";
64+ //
65+ // label2
66+ //
67+ resources.ApplyResources(this.label2, "label2");
68+ this.label2.Name = "label2";
69+ //
70+ // label3
71+ //
72+ resources.ApplyResources(this.label3, "label3");
73+ this.label3.Name = "label3";
74+ //
75+ // buttonEncrypt
76+ //
77+ resources.ApplyResources(this.buttonEncrypt, "buttonEncrypt");
78+ this.tableLayoutPanel1.SetColumnSpan(this.buttonEncrypt, 2);
79+ this.buttonEncrypt.Name = "buttonEncrypt";
80+ this.buttonEncrypt.UseVisualStyleBackColor = true;
81+ this.buttonEncrypt.Click += new System.EventHandler(this.ButtonEncrypt_Click);
82+ //
83+ // buttonClose
84+ //
85+ resources.ApplyResources(this.buttonClose, "buttonClose");
86+ this.buttonClose.DialogResult = System.Windows.Forms.DialogResult.Cancel;
87+ this.buttonClose.Name = "buttonClose";
88+ this.buttonClose.UseVisualStyleBackColor = true;
89+ //
90+ // tableLayoutPanel1
91+ //
92+ resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1");
93+ this.tableLayoutPanel1.Controls.Add(this.label2, 0, 0);
94+ this.tableLayoutPanel1.Controls.Add(this.buttonClose, 1, 4);
95+ this.tableLayoutPanel1.Controls.Add(this.textBoxPlainText, 1, 0);
96+ this.tableLayoutPanel1.Controls.Add(this.textBoxEncryptedText, 1, 3);
97+ this.tableLayoutPanel1.Controls.Add(this.label3, 0, 3);
98+ this.tableLayoutPanel1.Controls.Add(this.buttonEncrypt, 0, 2);
99+ this.tableLayoutPanel1.Controls.Add(this.label1, 0, 1);
100+ this.tableLayoutPanel1.Controls.Add(this.textBoxPassword, 1, 1);
101+ this.tableLayoutPanel1.Name = "tableLayoutPanel1";
102+ //
103+ // EncryptApiKeyDialog
104+ //
105+ resources.ApplyResources(this, "$this");
106+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
107+ this.CancelButton = this.buttonClose;
108+ this.Controls.Add(this.tableLayoutPanel1);
109+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
110+ this.MaximizeBox = false;
111+ this.Name = "EncryptApiKeyDialog";
112+ this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
113+ this.tableLayoutPanel1.ResumeLayout(false);
114+ this.tableLayoutPanel1.PerformLayout();
115+ this.ResumeLayout(false);
116+
117+ }
118+
119+ #endregion
120+
121+ private System.Windows.Forms.Label label1;
122+ private System.Windows.Forms.TextBox textBoxPlainText;
123+ private System.Windows.Forms.TextBox textBoxPassword;
124+ private System.Windows.Forms.TextBox textBoxEncryptedText;
125+ private System.Windows.Forms.Label label2;
126+ private System.Windows.Forms.Label label3;
127+ private System.Windows.Forms.Button buttonEncrypt;
128+ private System.Windows.Forms.Button buttonClose;
129+ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
130+ }
131+}
\ No newline at end of file
--- /dev/null
+++ b/OpenTween/EncryptApiKeyDialog.cs
@@ -0,0 +1,49 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2022 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3+// All rights reserved.
4+//
5+// This file is part of OpenTween.
6+//
7+// This program is free software; you can redistribute it and/or modify it
8+// under the terms of the GNU General public License as published by the Free
9+// Software Foundation; either version 3 of the License, or (at your option)
10+// any later version.
11+//
12+// This program is distributed in the hope that it will be useful, but
13+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
15+// for more details.
16+//
17+// You should have received a copy of the GNU General public License along
18+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20+// Boston, MA 02110-1301, USA.
21+
22+#nullable enable
23+
24+using System;
25+using System.Collections.Generic;
26+using System.ComponentModel;
27+using System.Data;
28+using System.Drawing;
29+using System.Linq;
30+using System.Text;
31+using System.Threading.Tasks;
32+using System.Windows.Forms;
33+
34+namespace OpenTween
35+{
36+ public partial class EncryptApiKeyDialog : OTBaseForm
37+ {
38+ public EncryptApiKeyDialog()
39+ {
40+ this.InitializeComponent();
41+ this.textBoxPassword.Text = ApplicationSettings.EncryptionPassword;
42+ }
43+
44+ private void ButtonEncrypt_Click(object sender, EventArgs e)
45+ {
46+ this.textBoxEncryptedText.Text = ApiKey.Encrypt(ApplicationSettings.EncryptionPassword, this.textBoxPlainText.Text);
47+ }
48+ }
49+}
--- /dev/null
+++ b/OpenTween/EncryptApiKeyDialog.resx
@@ -0,0 +1,426 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<root>
3+ <!--
4+ Microsoft ResX Schema
5+
6+ Version 2.0
7+
8+ The primary goals of this format is to allow a simple XML format
9+ that is mostly human readable. The generation and parsing of the
10+ various data types are done through the TypeConverter classes
11+ associated with the data types.
12+
13+ Example:
14+
15+ ... ado.net/XML headers & schema ...
16+ <resheader name="resmimetype">text/microsoft-resx</resheader>
17+ <resheader name="version">2.0</resheader>
18+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23+ <value>[base64 mime encoded serialized .NET Framework object]</value>
24+ </data>
25+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27+ <comment>This is a comment</comment>
28+ </data>
29+
30+ There are any number of "resheader" rows that contain simple
31+ name/value pairs.
32+
33+ Each data row contains a name, and value. The row also contains a
34+ type or mimetype. Type corresponds to a .NET class that support
35+ text/value conversion through the TypeConverter architecture.
36+ Classes that don't support this are serialized and stored with the
37+ mimetype set.
38+
39+ The mimetype is used for serialized objects, and tells the
40+ ResXResourceReader how to depersist the object. This is currently not
41+ extensible. For a given mimetype the value must be set accordingly:
42+
43+ Note - application/x-microsoft.net.object.binary.base64 is the format
44+ that the ResXResourceWriter will generate, however the reader can
45+ read any of the formats listed below.
46+
47+ mimetype: application/x-microsoft.net.object.binary.base64
48+ value : The object must be serialized with
49+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50+ : and then encoded with base64 encoding.
51+
52+ mimetype: application/x-microsoft.net.object.soap.base64
53+ value : The object must be serialized with
54+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55+ : and then encoded with base64 encoding.
56+
57+ mimetype: application/x-microsoft.net.object.bytearray.base64
58+ value : The object must be serialized into a byte array
59+ : using a System.ComponentModel.TypeConverter
60+ : and then encoded with base64 encoding.
61+ -->
62+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64+ <xsd:element name="root" msdata:IsDataSet="true">
65+ <xsd:complexType>
66+ <xsd:choice maxOccurs="unbounded">
67+ <xsd:element name="metadata">
68+ <xsd:complexType>
69+ <xsd:sequence>
70+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
71+ </xsd:sequence>
72+ <xsd:attribute name="name" use="required" type="xsd:string" />
73+ <xsd:attribute name="type" type="xsd:string" />
74+ <xsd:attribute name="mimetype" type="xsd:string" />
75+ <xsd:attribute ref="xml:space" />
76+ </xsd:complexType>
77+ </xsd:element>
78+ <xsd:element name="assembly">
79+ <xsd:complexType>
80+ <xsd:attribute name="alias" type="xsd:string" />
81+ <xsd:attribute name="name" type="xsd:string" />
82+ </xsd:complexType>
83+ </xsd:element>
84+ <xsd:element name="data">
85+ <xsd:complexType>
86+ <xsd:sequence>
87+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89+ </xsd:sequence>
90+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93+ <xsd:attribute ref="xml:space" />
94+ </xsd:complexType>
95+ </xsd:element>
96+ <xsd:element name="resheader">
97+ <xsd:complexType>
98+ <xsd:sequence>
99+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100+ </xsd:sequence>
101+ <xsd:attribute name="name" type="xsd:string" use="required" />
102+ </xsd:complexType>
103+ </xsd:element>
104+ </xsd:choice>
105+ </xsd:complexType>
106+ </xsd:element>
107+ </xsd:schema>
108+ <resheader name="resmimetype">
109+ <value>text/microsoft-resx</value>
110+ </resheader>
111+ <resheader name="version">
112+ <value>2.0</value>
113+ </resheader>
114+ <resheader name="reader">
115+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116+ </resheader>
117+ <resheader name="writer">
118+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119+ </resheader>
120+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
121+ <data name="label1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
122+ <value>Left, Right</value>
123+ </data>
124+ <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
125+ <data name="label1.AutoSize" type="System.Boolean, mscorlib">
126+ <value>True</value>
127+ </data>
128+ <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
129+ <data name="label1.Location" type="System.Drawing.Point, System.Drawing">
130+ <value>3, 33</value>
131+ </data>
132+ <data name="label1.Size" type="System.Drawing.Size, System.Drawing">
133+ <value>105, 12</value>
134+ </data>
135+ <data name="label1.TabIndex" type="System.Int32, mscorlib">
136+ <value>2</value>
137+ </data>
138+ <data name="label1.Text" xml:space="preserve">
139+ <value>暗号化パスワード</value>
140+ </data>
141+ <data name="&gt;&gt;label1.Name" xml:space="preserve">
142+ <value>label1</value>
143+ </data>
144+ <data name="&gt;&gt;label1.Type" xml:space="preserve">
145+ <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
146+ </data>
147+ <data name="&gt;&gt;label1.Parent" xml:space="preserve">
148+ <value>tableLayoutPanel1</value>
149+ </data>
150+ <data name="&gt;&gt;label1.ZOrder" xml:space="preserve">
151+ <value>6</value>
152+ </data>
153+ <data name="textBoxPlainText.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
154+ <value>Left, Right</value>
155+ </data>
156+ <data name="textBoxPlainText.Location" type="System.Drawing.Point, System.Drawing">
157+ <value>114, 5</value>
158+ </data>
159+ <data name="textBoxPlainText.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
160+ <value>3, 3, 3, 0</value>
161+ </data>
162+ <data name="textBoxPlainText.Size" type="System.Drawing.Size, System.Drawing">
163+ <value>221, 19</value>
164+ </data>
165+ <data name="textBoxPlainText.TabIndex" type="System.Int32, mscorlib">
166+ <value>1</value>
167+ </data>
168+ <data name="&gt;&gt;textBoxPlainText.Name" xml:space="preserve">
169+ <value>textBoxPlainText</value>
170+ </data>
171+ <data name="&gt;&gt;textBoxPlainText.Type" xml:space="preserve">
172+ <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
173+ </data>
174+ <data name="&gt;&gt;textBoxPlainText.Parent" xml:space="preserve">
175+ <value>tableLayoutPanel1</value>
176+ </data>
177+ <data name="&gt;&gt;textBoxPlainText.ZOrder" xml:space="preserve">
178+ <value>2</value>
179+ </data>
180+ <data name="textBoxPassword.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
181+ <value>Left, Right</value>
182+ </data>
183+ <data name="textBoxPassword.Location" type="System.Drawing.Point, System.Drawing">
184+ <value>114, 31</value>
185+ </data>
186+ <data name="textBoxPassword.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
187+ <value>3, 3, 3, 0</value>
188+ </data>
189+ <data name="textBoxPassword.Size" type="System.Drawing.Size, System.Drawing">
190+ <value>221, 19</value>
191+ </data>
192+ <data name="textBoxPassword.TabIndex" type="System.Int32, mscorlib">
193+ <value>3</value>
194+ </data>
195+ <data name="&gt;&gt;textBoxPassword.Name" xml:space="preserve">
196+ <value>textBoxPassword</value>
197+ </data>
198+ <data name="&gt;&gt;textBoxPassword.Type" xml:space="preserve">
199+ <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
200+ </data>
201+ <data name="&gt;&gt;textBoxPassword.Parent" xml:space="preserve">
202+ <value>tableLayoutPanel1</value>
203+ </data>
204+ <data name="&gt;&gt;textBoxPassword.ZOrder" xml:space="preserve">
205+ <value>7</value>
206+ </data>
207+ <data name="textBoxEncryptedText.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
208+ <value>Left, Right</value>
209+ </data>
210+ <data name="textBoxEncryptedText.Location" type="System.Drawing.Point, System.Drawing">
211+ <value>114, 83</value>
212+ </data>
213+ <data name="textBoxEncryptedText.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
214+ <value>3, 3, 3, 0</value>
215+ </data>
216+ <data name="textBoxEncryptedText.Size" type="System.Drawing.Size, System.Drawing">
217+ <value>221, 19</value>
218+ </data>
219+ <data name="textBoxEncryptedText.TabIndex" type="System.Int32, mscorlib">
220+ <value>6</value>
221+ </data>
222+ <data name="&gt;&gt;textBoxEncryptedText.Name" xml:space="preserve">
223+ <value>textBoxEncryptedText</value>
224+ </data>
225+ <data name="&gt;&gt;textBoxEncryptedText.Type" xml:space="preserve">
226+ <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
227+ </data>
228+ <data name="&gt;&gt;textBoxEncryptedText.Parent" xml:space="preserve">
229+ <value>tableLayoutPanel1</value>
230+ </data>
231+ <data name="&gt;&gt;textBoxEncryptedText.ZOrder" xml:space="preserve">
232+ <value>3</value>
233+ </data>
234+ <data name="label2.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
235+ <value>Left, Right</value>
236+ </data>
237+ <data name="label2.AutoSize" type="System.Boolean, mscorlib">
238+ <value>True</value>
239+ </data>
240+ <data name="label2.Location" type="System.Drawing.Point, System.Drawing">
241+ <value>3, 7</value>
242+ </data>
243+ <data name="label2.Size" type="System.Drawing.Size, System.Drawing">
244+ <value>105, 12</value>
245+ </data>
246+ <data name="label2.TabIndex" type="System.Int32, mscorlib">
247+ <value>0</value>
248+ </data>
249+ <data name="label2.Text" xml:space="preserve">
250+ <value>暗号化する文字列</value>
251+ </data>
252+ <data name="&gt;&gt;label2.Name" xml:space="preserve">
253+ <value>label2</value>
254+ </data>
255+ <data name="&gt;&gt;label2.Type" xml:space="preserve">
256+ <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
257+ </data>
258+ <data name="&gt;&gt;label2.Parent" xml:space="preserve">
259+ <value>tableLayoutPanel1</value>
260+ </data>
261+ <data name="&gt;&gt;label2.ZOrder" xml:space="preserve">
262+ <value>0</value>
263+ </data>
264+ <data name="label3.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
265+ <value>Left, Right</value>
266+ </data>
267+ <data name="label3.AutoSize" type="System.Boolean, mscorlib">
268+ <value>True</value>
269+ </data>
270+ <data name="label3.Location" type="System.Drawing.Point, System.Drawing">
271+ <value>3, 85</value>
272+ </data>
273+ <data name="label3.Size" type="System.Drawing.Size, System.Drawing">
274+ <value>105, 12</value>
275+ </data>
276+ <data name="label3.TabIndex" type="System.Int32, mscorlib">
277+ <value>5</value>
278+ </data>
279+ <data name="label3.Text" xml:space="preserve">
280+ <value>暗号化された文字列</value>
281+ </data>
282+ <data name="&gt;&gt;label3.Name" xml:space="preserve">
283+ <value>label3</value>
284+ </data>
285+ <data name="&gt;&gt;label3.Type" xml:space="preserve">
286+ <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
287+ </data>
288+ <data name="&gt;&gt;label3.Parent" xml:space="preserve">
289+ <value>tableLayoutPanel1</value>
290+ </data>
291+ <data name="&gt;&gt;label3.ZOrder" xml:space="preserve">
292+ <value>4</value>
293+ </data>
294+ <data name="buttonEncrypt.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
295+ <value>Top, Bottom, Left, Right</value>
296+ </data>
297+ <data name="buttonEncrypt.AutoSize" type="System.Boolean, mscorlib">
298+ <value>True</value>
299+ </data>
300+ <data name="buttonEncrypt.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
301+ <value>GrowAndShrink</value>
302+ </data>
303+ <data name="tableLayoutPanel1.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
304+ <value>Top, Bottom, Left, Right</value>
305+ </data>
306+ <data name="tableLayoutPanel1.ColumnCount" type="System.Int32, mscorlib">
307+ <value>2</value>
308+ </data>
309+ <data name="buttonClose.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
310+ <value>Top, Bottom, Right</value>
311+ </data>
312+ <data name="buttonClose.AutoSize" type="System.Boolean, mscorlib">
313+ <value>True</value>
314+ </data>
315+ <data name="buttonClose.AutoSizeMode" type="System.Windows.Forms.AutoSizeMode, System.Windows.Forms">
316+ <value>GrowAndShrink</value>
317+ </data>
318+ <data name="buttonClose.Location" type="System.Drawing.Point, System.Drawing">
319+ <value>250, 107</value>
320+ </data>
321+ <data name="buttonClose.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
322+ <value>3, 3, 3, 0</value>
323+ </data>
324+ <data name="buttonClose.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
325+ <value>10, 0, 10, 0</value>
326+ </data>
327+ <data name="buttonClose.Size" type="System.Drawing.Size, System.Drawing">
328+ <value>85, 23</value>
329+ </data>
330+ <data name="buttonClose.TabIndex" type="System.Int32, mscorlib">
331+ <value>7</value>
332+ </data>
333+ <data name="buttonClose.Text" xml:space="preserve">
334+ <value>閉じる (&amp;C)</value>
335+ </data>
336+ <data name="&gt;&gt;buttonClose.Name" xml:space="preserve">
337+ <value>buttonClose</value>
338+ </data>
339+ <data name="&gt;&gt;buttonClose.Type" xml:space="preserve">
340+ <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
341+ </data>
342+ <data name="&gt;&gt;buttonClose.Parent" xml:space="preserve">
343+ <value>tableLayoutPanel1</value>
344+ </data>
345+ <data name="&gt;&gt;buttonClose.ZOrder" xml:space="preserve">
346+ <value>1</value>
347+ </data>
348+ <data name="tableLayoutPanel1.Location" type="System.Drawing.Point, System.Drawing">
349+ <value>12, 9</value>
350+ </data>
351+ <data name="tableLayoutPanel1.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
352+ <value>3, 0, 3, 3</value>
353+ </data>
354+ <data name="tableLayoutPanel1.RowCount" type="System.Int32, mscorlib">
355+ <value>5</value>
356+ </data>
357+ <data name="tableLayoutPanel1.Size" type="System.Drawing.Size, System.Drawing">
358+ <value>338, 130</value>
359+ </data>
360+ <data name="tableLayoutPanel1.TabIndex" type="System.Int32, mscorlib">
361+ <value>0</value>
362+ </data>
363+ <data name="&gt;&gt;tableLayoutPanel1.Name" xml:space="preserve">
364+ <value>tableLayoutPanel1</value>
365+ </data>
366+ <data name="&gt;&gt;tableLayoutPanel1.Type" xml:space="preserve">
367+ <value>System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
368+ </data>
369+ <data name="&gt;&gt;tableLayoutPanel1.Parent" xml:space="preserve">
370+ <value>$this</value>
371+ </data>
372+ <data name="&gt;&gt;tableLayoutPanel1.ZOrder" xml:space="preserve">
373+ <value>0</value>
374+ </data>
375+ <data name="tableLayoutPanel1.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
376+ <value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="label2" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="buttonClose" Row="4" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="textBoxPlainText" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="textBoxEncryptedText" Row="3" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;Control Name="label3" Row="3" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="buttonEncrypt" Row="2" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;Control Name="label1" Row="1" RowSpan="1" Column="0" ColumnSpan="1" /&gt;&lt;Control Name="textBoxPassword" Row="1" RowSpan="1" Column="1" ColumnSpan="1" /&gt;&lt;/Controls&gt;&lt;Columns Styles="AutoSize,0,AutoSize,0" /&gt;&lt;Rows Styles="Percent,20,Percent,20,Percent,20,Percent,20,Percent,20" /&gt;&lt;/TableLayoutSettings&gt;</value>
377+ </data>
378+ <data name="buttonEncrypt.Location" type="System.Drawing.Point, System.Drawing">
379+ <value>3, 55</value>
380+ </data>
381+ <data name="buttonEncrypt.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
382+ <value>3, 3, 3, 0</value>
383+ </data>
384+ <data name="buttonEncrypt.Size" type="System.Drawing.Size, System.Drawing">
385+ <value>332, 23</value>
386+ </data>
387+ <data name="buttonEncrypt.TabIndex" type="System.Int32, mscorlib">
388+ <value>4</value>
389+ </data>
390+ <data name="buttonEncrypt.Text" xml:space="preserve">
391+ <value>暗号化</value>
392+ </data>
393+ <data name="&gt;&gt;buttonEncrypt.Name" xml:space="preserve">
394+ <value>buttonEncrypt</value>
395+ </data>
396+ <data name="&gt;&gt;buttonEncrypt.Type" xml:space="preserve">
397+ <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
398+ </data>
399+ <data name="&gt;&gt;buttonEncrypt.Parent" xml:space="preserve">
400+ <value>tableLayoutPanel1</value>
401+ </data>
402+ <data name="&gt;&gt;buttonEncrypt.ZOrder" xml:space="preserve">
403+ <value>5</value>
404+ </data>
405+ <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
406+ <value>True</value>
407+ </metadata>
408+ <data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
409+ <value>96, 96</value>
410+ </data>
411+ <data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
412+ <value>362, 151</value>
413+ </data>
414+ <data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
415+ <value>CenterParent</value>
416+ </data>
417+ <data name="$this.Text" xml:space="preserve">
418+ <value>APIキー暗号化</value>
419+ </data>
420+ <data name="&gt;&gt;$this.Name" xml:space="preserve">
421+ <value>EncryptApiKeyDialog</value>
422+ </data>
423+ <data name="&gt;&gt;$this.Type" xml:space="preserve">
424+ <value>OpenTween.OTBaseForm, OpenTween, Version=2.4.3.1, Culture=neutral, PublicKeyToken=null</value>
425+ </data>
426+</root>
\ No newline at end of file
--- a/OpenTween/OpenTween.csproj
+++ b/OpenTween/OpenTween.csproj
@@ -68,6 +68,7 @@
6868 <Compile Include="ApiInfoDialog.Designer.cs">
6969 <DependentUpon>ApiInfoDialog.cs</DependentUpon>
7070 </Compile>
71+ <Compile Include="ApiKey.cs" />
7172 <Compile Include="Api\ApiLimit.cs" />
7273 <Compile Include="Api\BitlyApi.cs" />
7374 <Compile Include="Api\DataModel\GeoJson.cs" />
@@ -133,6 +134,12 @@
133134 <Compile Include="Connection\OAuthUtility.cs" />
134135 <Compile Include="Connection\TwitterApiConnection.cs" />
135136 <Compile Include="DateTimeUtc.cs" />
137+ <Compile Include="EncryptApiKeyDialog.cs">
138+ <SubType>Form</SubType>
139+ </Compile>
140+ <Compile Include="EncryptApiKeyDialog.Designer.cs">
141+ <DependentUpon>EncryptApiKeyDialog.cs</DependentUpon>
142+ </Compile>
136143 <Compile Include="EventViewerDialog.cs">
137144 <SubType>Form</SubType>
138145 </Compile>
@@ -470,6 +477,9 @@
470477 <EmbeddedResource Include="AuthDialog.resx">
471478 <DependentUpon>AuthDialog.cs</DependentUpon>
472479 </EmbeddedResource>
480+ <EmbeddedResource Include="EncryptApiKeyDialog.resx">
481+ <DependentUpon>EncryptApiKeyDialog.cs</DependentUpon>
482+ </EmbeddedResource>
473483 <EmbeddedResource Include="EventViewerDialog.en.resx">
474484 <DependentUpon>EventViewerDialog.cs</DependentUpon>
475485 </EmbeddedResource>
--- a/OpenTween/Setting/Panel/CooperatePanel.Designer.cs
+++ b/OpenTween/Setting/Panel/CooperatePanel.Designer.cs
@@ -45,6 +45,7 @@
4545 this.CheckNicoms = new System.Windows.Forms.CheckBox();
4646 this.EnableImgAzyobuziNetCheckBox = new System.Windows.Forms.CheckBox();
4747 this.ImgAzyobuziNetDisabledInDMCheckBox = new System.Windows.Forms.CheckBox();
48+ this.EncryptApiKeyButton = new System.Windows.Forms.Button();
4849 this.MapThumbnailGroupBox.SuspendLayout();
4950 this.SuspendLayout();
5051 //
@@ -278,10 +279,18 @@
278279 this.ImgAzyobuziNetDisabledInDMCheckBox.Name = "ImgAzyobuziNetDisabledInDMCheckBox";
279280 this.ImgAzyobuziNetDisabledInDMCheckBox.UseVisualStyleBackColor = true;
280281 //
282+ // EncryptApiKeyButton
283+ //
284+ resources.ApplyResources(this.EncryptApiKeyButton, "EncryptApiKeyButton");
285+ this.EncryptApiKeyButton.Name = "EncryptApiKeyButton";
286+ this.EncryptApiKeyButton.UseVisualStyleBackColor = true;
287+ this.EncryptApiKeyButton.Click += new System.EventHandler(this.EncryptApiKeyButton_Click);
288+ //
281289 // CooperatePanel
282290 //
283291 resources.ApplyResources(this, "$this");
284292 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
293+ this.Controls.Add(this.EncryptApiKeyButton);
285294 this.Controls.Add(this.ImgAzyobuziNetDisabledInDMCheckBox);
286295 this.Controls.Add(this.EnableImgAzyobuziNetCheckBox);
287296 this.Controls.Add(this.MapThumbnailGroupBox);
@@ -316,5 +325,6 @@
316325 internal System.Windows.Forms.ComboBox MapThumbnailProviderComboBox;
317326 internal System.Windows.Forms.CheckBox EnableImgAzyobuziNetCheckBox;
318327 internal System.Windows.Forms.CheckBox ImgAzyobuziNetDisabledInDMCheckBox;
328+ private System.Windows.Forms.Button EncryptApiKeyButton;
319329 }
320330 }
--- a/OpenTween/Setting/Panel/CooperatePanel.cs
+++ b/OpenTween/Setting/Panel/CooperatePanel.cs
@@ -41,7 +41,10 @@ namespace OpenTween.Setting.Panel
4141 public partial class CooperatePanel : SettingPanelBase
4242 {
4343 public CooperatePanel()
44- => this.InitializeComponent();
44+ {
45+ this.InitializeComponent();
46+ this.EncryptApiKeyButton.Visible = MyCommon.DebugBuild;
47+ }
4548
4649 public void LoadConfig(SettingCommon settingCommon)
4750 {
@@ -79,5 +82,11 @@ namespace OpenTween.Setting.Panel
7982
8083 private void EnableImgAzyobuziNetCheckBox_CheckedChanged(object sender, EventArgs e)
8184 => this.ImgAzyobuziNetDisabledInDMCheckBox.Enabled = this.EnableImgAzyobuziNetCheckBox.Checked;
85+
86+ private void EncryptApiKeyButton_Click(object sender, EventArgs e)
87+ {
88+ using var dialog = new EncryptApiKeyDialog();
89+ dialog.ShowDialog(this.ParentForm);
90+ }
8291 }
8392 }
--- a/OpenTween/Setting/Panel/CooperatePanel.en.resx
+++ b/OpenTween/Setting/Panel/CooperatePanel.en.resx
@@ -154,13 +154,10 @@
154154 <data name="CheckNicoms.Text" xml:space="preserve">
155155 <value>Shorten nicovideo urls by nico.ms</value>
156156 </data>
157- <data name="Label60.Size" type="System.Drawing.Size, System.Drawing">
158- <value>107, 12</value>
157+ <data name="EncryptApiKeyButton.Size" type="System.Drawing.Size, System.Drawing">
158+ <value>154, 23</value>
159159 </data>
160- <data name="Label59.Size" type="System.Drawing.Size, System.Drawing">
161- <value>86, 12</value>
162- </data>
163- <data name="Label59.Text" xml:space="preserve">
164- <value>Your secret key</value>
160+ <data name="EncryptApiKeyButton.Text" xml:space="preserve">
161+ <value>API Key Encryption</value>
165162 </data>
166163 </root>
\ No newline at end of file
--- a/OpenTween/Setting/Panel/CooperatePanel.resx
+++ b/OpenTween/Setting/Panel/CooperatePanel.resx
@@ -117,21 +117,20 @@
117117 <resheader name="writer">
118118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119 </resheader>
120- <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
121- <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
122- <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
123120 <data name="MapThumbnailProviderComboBox.Items" xml:space="preserve">
124121 <value>OpenStreetMap</value>
125122 </data>
126123 <data name="MapThumbnailProviderComboBox.Items1" xml:space="preserve">
127124 <value>Google Maps</value>
128125 </data>
126+ <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
129127 <data name="MapThumbnailProviderComboBox.Location" type="System.Drawing.Point, System.Drawing">
130128 <value>102, 22</value>
131129 </data>
132130 <data name="MapThumbnailProviderComboBox.Size" type="System.Drawing.Size, System.Drawing">
133131 <value>121, 20</value>
134132 </data>
133+ <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
135134 <data name="MapThumbnailProviderComboBox.TabIndex" type="System.Int32, mscorlib">
136135 <value>1</value>
137136 </data>
@@ -150,6 +149,7 @@
150149 <data name="label48.AutoSize" type="System.Boolean, mscorlib">
151150 <value>True</value>
152151 </data>
152+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
153153 <data name="label48.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
154154 <value>NoControl</value>
155155 </data>
@@ -355,7 +355,7 @@
355355 <value>$this</value>
356356 </data>
357357 <data name="&gt;&gt;MapThumbnailGroupBox.ZOrder" xml:space="preserve">
358- <value>2</value>
358+ <value>3</value>
359359 </data>
360360 <data name="Label39.AutoSize" type="System.Boolean, mscorlib">
361361 <value>True</value>
@@ -385,7 +385,7 @@
385385 <value>$this</value>
386386 </data>
387387 <data name="&gt;&gt;Label39.ZOrder" xml:space="preserve">
388- <value>3</value>
388+ <value>4</value>
389389 </data>
390390 <data name="UserAppointUrlText.Location" type="System.Drawing.Point, System.Drawing">
391391 <value>218, 102</value>
@@ -406,7 +406,7 @@
406406 <value>$this</value>
407407 </data>
408408 <data name="&gt;&gt;UserAppointUrlText.ZOrder" xml:space="preserve">
409- <value>4</value>
409+ <value>5</value>
410410 </data>
411411 <data name="ComboBoxTranslateLanguage.Items" xml:space="preserve">
412412 <value>Afrikaans</value>
@@ -799,7 +799,7 @@
799799 <value>$this</value>
800800 </data>
801801 <data name="&gt;&gt;ComboBoxTranslateLanguage.ZOrder" xml:space="preserve">
802- <value>5</value>
802+ <value>6</value>
803803 </data>
804804 <data name="Label29.AutoSize" type="System.Boolean, mscorlib">
805805 <value>True</value>
@@ -829,7 +829,7 @@
829829 <value>$this</value>
830830 </data>
831831 <data name="&gt;&gt;Label29.ZOrder" xml:space="preserve">
832- <value>6</value>
832+ <value>7</value>
833833 </data>
834834 <data name="CheckNicoms.AutoSize" type="System.Boolean, mscorlib">
835835 <value>True</value>
@@ -859,7 +859,7 @@
859859 <value>$this</value>
860860 </data>
861861 <data name="&gt;&gt;CheckNicoms.ZOrder" xml:space="preserve">
862- <value>7</value>
862+ <value>8</value>
863863 </data>
864864 <data name="EnableImgAzyobuziNetCheckBox.AutoSize" type="System.Boolean, mscorlib">
865865 <value>True</value>
@@ -886,7 +886,7 @@
886886 <value>$this</value>
887887 </data>
888888 <data name="&gt;&gt;EnableImgAzyobuziNetCheckBox.ZOrder" xml:space="preserve">
889- <value>1</value>
889+ <value>2</value>
890890 </data>
891891 <data name="ImgAzyobuziNetDisabledInDMCheckBox.AutoSize" type="System.Boolean, mscorlib">
892892 <value>True</value>
@@ -916,6 +916,36 @@
916916 <value>$this</value>
917917 </data>
918918 <data name="&gt;&gt;ImgAzyobuziNetDisabledInDMCheckBox.ZOrder" xml:space="preserve">
919+ <value>1</value>
920+ </data>
921+ <data name="EncryptApiKeyButton.AutoSize" type="System.Boolean, mscorlib">
922+ <value>True</value>
923+ </data>
924+ <data name="EncryptApiKeyButton.Location" type="System.Drawing.Point, System.Drawing">
925+ <value>23, 293</value>
926+ </data>
927+ <data name="EncryptApiKeyButton.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
928+ <value>20, 0, 20, 0</value>
929+ </data>
930+ <data name="EncryptApiKeyButton.Size" type="System.Drawing.Size, System.Drawing">
931+ <value>135, 23</value>
932+ </data>
933+ <data name="EncryptApiKeyButton.TabIndex" type="System.Int32, mscorlib">
934+ <value>8</value>
935+ </data>
936+ <data name="EncryptApiKeyButton.Text" xml:space="preserve">
937+ <value>APIキー暗号化</value>
938+ </data>
939+ <data name="&gt;&gt;EncryptApiKeyButton.Name" xml:space="preserve">
940+ <value>EncryptApiKeyButton</value>
941+ </data>
942+ <data name="&gt;&gt;EncryptApiKeyButton.Type" xml:space="preserve">
943+ <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
944+ </data>
945+ <data name="&gt;&gt;EncryptApiKeyButton.Parent" xml:space="preserve">
946+ <value>$this</value>
947+ </data>
948+ <data name="&gt;&gt;EncryptApiKeyButton.ZOrder" xml:space="preserve">
919949 <value>0</value>
920950 </data>
921951 <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
@@ -928,6 +958,6 @@
928958 <value>CooperatePanel</value>
929959 </data>
930960 <data name="&gt;&gt;$this.Type" xml:space="preserve">
931- <value>OpenTween.Setting.Panel.SettingPanelBase, OpenTween, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
961+ <value>OpenTween.Setting.Panel.SettingPanelBase, OpenTween, Version=2.4.3.1, Culture=neutral, PublicKeyToken=null</value>
932962 </data>
933963 </root>
\ No newline at end of file
--- a/OpenTween/Thumbnail/Services/FoursquareCheckin.cs
+++ b/OpenTween/Thumbnail/Services/FoursquareCheckin.cs
@@ -52,14 +52,20 @@ namespace OpenTween.Thumbnail.Services
5252 => this.localHttpClient ?? Networking.Http;
5353
5454 private readonly HttpClient? localHttpClient;
55+ private readonly ApiKey clientId;
56+ private readonly ApiKey clientSecret;
5557
5658 public FoursquareCheckin()
57- : this(null)
59+ : this(null, ApplicationSettings.FoursquareClientId, ApplicationSettings.FoursquareClientSecret)
5860 {
5961 }
6062
61- public FoursquareCheckin(HttpClient? http)
62- => this.localHttpClient = http;
63+ public FoursquareCheckin(HttpClient? http, ApiKey clientId, ApiKey clientSecret)
64+ {
65+ this.localHttpClient = http;
66+ this.clientId = clientId;
67+ this.clientSecret = clientSecret;
68+ }
6369
6470 public override async Task<ThumbnailInfo?> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
6571 {
@@ -96,6 +102,10 @@ namespace OpenTween.Thumbnail.Services
96102 if (!match.Success)
97103 return null;
98104
105+ if (!(this.clientId, this.clientSecret).TryGetValue(out var keyPair))
106+ return null;
107+
108+ var (clientId, clientSecret) = keyPair;
99109 var checkinIdGroup = match.Groups["checkin_id"];
100110
101111 try
@@ -105,8 +115,8 @@ namespace OpenTween.Thumbnail.Services
105115
106116 var query = new Dictionary<string, string>
107117 {
108- ["client_id"] = ApplicationSettings.FoursquareClientId,
109- ["client_secret"] = ApplicationSettings.FoursquareClientSecret,
118+ ["client_id"] = clientId,
119+ ["client_secret"] = clientSecret,
110120 ["v"] = "20140419", // https://developer.foursquare.com/overview/versioning
111121
112122 ["shortId"] = checkinIdGroup.Value,
@@ -140,6 +150,10 @@ namespace OpenTween.Thumbnail.Services
140150 if (!match.Success)
141151 return null;
142152
153+ if (!(this.clientId, this.clientSecret).TryGetValue(out var keyPair))
154+ return null;
155+
156+ var (clientId, clientSecret) = keyPair;
143157 var checkinIdGroup = match.Groups["checkin_id"];
144158 var signatureGroup = match.Groups["signature"];
145159
@@ -150,8 +164,8 @@ namespace OpenTween.Thumbnail.Services
150164
151165 var query = new Dictionary<string, string>
152166 {
153- ["client_id"] = ApplicationSettings.FoursquareClientId,
154- ["client_secret"] = ApplicationSettings.FoursquareClientSecret,
167+ ["client_id"] = clientId,
168+ ["client_secret"] = clientSecret,
155169 ["v"] = "20140419", // https://developer.foursquare.com/overview/versioning
156170 };
157171
--- a/OpenTween/Thumbnail/Services/Tinami.cs
+++ b/OpenTween/Thumbnail/Services/Tinami.cs
@@ -45,15 +45,19 @@ namespace OpenTween.Thumbnail.Services
4545 protected HttpClient http
4646 => this.localHttpClient ?? Networking.Http;
4747
48+ private readonly ApiKey apiKey;
4849 private readonly HttpClient? localHttpClient;
4950
5051 public Tinami()
51- : this(null)
52+ : this(ApplicationSettings.TINAMIApiKey, null)
5253 {
5354 }
5455
55- public Tinami(HttpClient? http)
56- => this.localHttpClient = http;
56+ public Tinami(ApiKey apiKey, HttpClient? http)
57+ {
58+ this.apiKey = apiKey;
59+ this.localHttpClient = http;
60+ }
5761
5862 public override async Task<ThumbnailInfo?> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
5963 {
@@ -61,11 +65,14 @@ namespace OpenTween.Thumbnail.Services
6165 if (!match.Success)
6266 return null;
6367
68+ if (!this.apiKey.TryGetValue(out var apiKey))
69+ return null;
70+
6471 var contentId = match.Groups["ContentId"].Value;
6572
6673 try
6774 {
68- var xdoc = await this.FetchContentInfoApiAsync(contentId, token)
75+ var xdoc = await this.FetchContentInfoApiAsync(apiKey, contentId, token)
6976 .ConfigureAwait(false);
7077
7178 if (xdoc.XPathSelectElement("/rsp").Attribute("stat").Value != "ok")
@@ -89,11 +96,11 @@ namespace OpenTween.Thumbnail.Services
8996 return null;
9097 }
9198
92- protected virtual async Task<XDocument> FetchContentInfoApiAsync(string contentId, CancellationToken token)
99+ protected virtual async Task<XDocument> FetchContentInfoApiAsync(string apiKey, string contentId, CancellationToken token)
93100 {
94101 var query = new Dictionary<string, string>
95102 {
96- ["api_key"] = ApplicationSettings.TINAMIApiKey,
103+ ["api_key"] = apiKey,
97104 ["cont_id"] = contentId,
98105 };
99106
--- a/OpenTween/Thumbnail/Services/Tumblr.cs
+++ b/OpenTween/Thumbnail/Services/Tumblr.cs
@@ -46,15 +46,19 @@ namespace OpenTween.Thumbnail.Services
4646 protected HttpClient http
4747 => this.localHttpClient ?? Networking.Http;
4848
49+ private readonly ApiKey tumblrConsumerKey;
4950 private readonly HttpClient? localHttpClient;
5051
5152 public Tumblr()
52- : this(null)
53+ : this(ApplicationSettings.TumblrConsumerKey, null)
5354 {
5455 }
5556
56- public Tumblr(HttpClient? http)
57- => this.localHttpClient = http;
57+ public Tumblr(ApiKey apiKey, HttpClient? http)
58+ {
59+ this.tumblrConsumerKey = apiKey;
60+ this.localHttpClient = http;
61+ }
5862
5963 public override async Task<ThumbnailInfo?> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
6064 {
@@ -62,6 +66,9 @@ namespace OpenTween.Thumbnail.Services
6266 if (!match.Success)
6367 return null;
6468
69+ if (!this.tumblrConsumerKey.TryGetValue(out var apiKey))
70+ return null;
71+
6572 // 参照: http://www.tumblr.com/docs/en/api/v2#photo-posts
6673
6774 var host = match.Groups["host"].Value;
@@ -69,7 +76,7 @@ namespace OpenTween.Thumbnail.Services
6976
7077 var param = new Dictionary<string, string>
7178 {
72- ["api_key"] = ApplicationSettings.TumblrConsumerKey,
79+ ["api_key"] = apiKey,
7380 ["id"] = postId,
7481 };
7582
--- a/OpenTween/Tween.cs
+++ b/OpenTween/Tween.cs
@@ -140,7 +140,7 @@ namespace OpenTween
140140 private FormWindowState _formWindowState = FormWindowState.Normal; // フォームの状態保存用 通知領域からアイコンをクリックして復帰した際に使用する
141141
142142 //twitter解析部
143- private readonly TwitterApi twitterApi = new TwitterApi();
143+ private readonly TwitterApi twitterApi = new TwitterApi(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret);
144144 private Twitter tw = null!;
145145
146146 //Growl呼び出し部
--- a/OpenTween/TweetDetailsView.cs
+++ b/OpenTween/TweetDetailsView.cs
@@ -376,7 +376,7 @@ namespace OpenTween
376376
377377 this.PostBrowser.DocumentText = this.Owner.createDetailHtml(translatedText);
378378 }
379- catch (HttpRequestException e)
379+ catch (WebApiException e)
380380 {
381381 this.RaiseStatusChanged("Err:" + e.Message);
382382 }
--- a/OpenTween/Twitter.cs
+++ b/OpenTween/Twitter.cs
@@ -169,10 +169,6 @@ namespace OpenTween
169169
170170 private long previousStatusId = -1L;
171171
172- public Twitter() : this(new TwitterApi())
173- {
174- }
175-
176172 public Twitter(TwitterApi api)
177173 {
178174 this.Api = api;
Show on old repository browser