OSDN Git Service

Merge branch 'bitly-oauth2'
authorKimura Youichi <kim.upsilon@bucyou.net>
Sat, 29 Apr 2017 04:36:50 +0000 (13:36 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Sat, 29 Apr 2017 04:36:59 +0000 (13:36 +0900)
22 files changed:
OpenTween.Tests/Api/BitlyApiTest.cs [new file with mode: 0644]
OpenTween.Tests/OpenTween.Tests.csproj
OpenTween/Api/BitlyApi.cs [new file with mode: 0644]
OpenTween/AppendSettingDialog.Designer.cs
OpenTween/AppendSettingDialog.cs
OpenTween/ApplicationSettings.cs
OpenTween/LoginDialog.Designer.cs [new file with mode: 0644]
OpenTween/LoginDialog.cs [new file with mode: 0644]
OpenTween/LoginDialog.en.resx [new file with mode: 0644]
OpenTween/LoginDialog.resx [new file with mode: 0644]
OpenTween/OpenTween.csproj
OpenTween/Properties/Resources.Designer.cs
OpenTween/Properties/Resources.en.resx
OpenTween/Properties/Resources.resx
OpenTween/Resources/ChangeLog.txt
OpenTween/Setting/Panel/ShortUrlPanel.Designer.cs
OpenTween/Setting/Panel/ShortUrlPanel.cs
OpenTween/Setting/Panel/ShortUrlPanel.en.resx
OpenTween/Setting/Panel/ShortUrlPanel.resx
OpenTween/Setting/SettingCommon.cs
OpenTween/ShortUrl.cs
OpenTween/Tween.cs

diff --git a/OpenTween.Tests/Api/BitlyApiTest.cs b/OpenTween.Tests/Api/BitlyApiTest.cs
new file mode 100644 (file)
index 0000000..1703e15
--- /dev/null
@@ -0,0 +1,173 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using Xunit;
+
+namespace OpenTween.Api
+{
+    public class BitlyApiTest
+    {
+        [Fact]
+        public async Task ShortenAsync_OAuth2Test()
+        {
+            using (var mockHandler = new HttpMessageHandlerMock())
+            using (var http = new HttpClient(mockHandler))
+            {
+                var bitly = new BitlyApi(http);
+
+                mockHandler.Enqueue(x =>
+                {
+                    Assert.Equal(HttpMethod.Get, x.Method);
+                    Assert.Equal("https://api-ssl.bitly.com/v3/shorten",
+                        x.RequestUri.GetLeftPart(UriPartial.Path));
+
+                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
+
+                    Assert.Equal("http://www.example.com/", query["longUrl"]);
+                    Assert.Equal("bit.ly", query["domain"]);
+                    Assert.Equal("hogehoge", query["access_token"]);
+
+                    return new HttpResponseMessage(HttpStatusCode.OK)
+                    {
+                        Content = new StringContent("http://bit.ly/foo"),
+                    };
+                });
+
+                bitly.EndUserAccessToken = "hogehoge";
+
+                var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly")
+                    .ConfigureAwait(false);
+                Assert.Equal("http://bit.ly/foo", result.OriginalString);
+
+                Assert.Equal(0, mockHandler.QueueCount);
+            }
+        }
+
+        [Fact]
+        public async Task ShortenAsync_LegacyApiKeyTest()
+        {
+            using (var mockHandler = new HttpMessageHandlerMock())
+            using (var http = new HttpClient(mockHandler))
+            {
+                var bitly = new BitlyApi(http);
+
+                mockHandler.Enqueue(x =>
+                {
+                    Assert.Equal(HttpMethod.Get, x.Method);
+                    Assert.Equal("https://api-ssl.bitly.com/v3/shorten",
+                        x.RequestUri.GetLeftPart(UriPartial.Path));
+
+                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
+
+                    Assert.Equal("http://www.example.com/", query["longUrl"]);
+                    Assert.Equal("bit.ly", query["domain"]);
+                    Assert.Equal("username", query["login"]);
+                    Assert.Equal("hogehoge", query["apiKey"]);
+
+                    return new HttpResponseMessage(HttpStatusCode.OK)
+                    {
+                        Content = new StringContent("http://bit.ly/foo"),
+                    };
+                });
+
+                bitly.EndUserLoginName = "username";
+                bitly.EndUserApiKey = "hogehoge";
+
+                var result = await bitly.ShortenAsync(new Uri("http://www.example.com/"), "bit.ly")
+                    .ConfigureAwait(false);
+                Assert.Equal("http://bit.ly/foo", result.OriginalString);
+
+                Assert.Equal(0, mockHandler.QueueCount);
+            }
+        }
+
+        [Fact]
+        public async Task GetAccessTokenAsync_Test()
+        {
+            using (var mockHandler = new HttpMessageHandlerMock())
+            using (var http = new HttpClient(mockHandler))
+            {
+                var bitly = new BitlyApi(http);
+
+                mockHandler.Enqueue(async x =>
+                {
+                    Assert.Equal(HttpMethod.Post, x.Method);
+                    Assert.Equal("https://api-ssl.bitly.com/oauth/access_token",
+                        x.RequestUri.GetLeftPart(UriPartial.Path));
+
+                    Assert.Equal("Basic", x.Headers.Authorization.Scheme);
+                    Assert.Equal(ApplicationSettings.BitlyClientId + ":" + ApplicationSettings.BitlyClientSecret,
+                        Encoding.UTF8.GetString(Convert.FromBase64String(x.Headers.Authorization.Parameter)));
+
+                    var body = await x.Content.ReadAsStringAsync()
+                        .ConfigureAwait(false);
+                    var query = HttpUtility.ParseQueryString(body);
+
+                    Assert.Equal("password", query["grant_type"]);
+                    Assert.Equal("hogehoge", query["username"]);
+                    Assert.Equal("tetete", query["password"]);
+
+                    return new HttpResponseMessage(HttpStatusCode.OK)
+                    {
+                        Content = new StringContent("{\"access_token\": \"abcdefg\"}"),
+                    };
+                });
+
+                var result = await bitly.GetAccessTokenAsync("hogehoge", "tetete")
+                    .ConfigureAwait(false);
+                Assert.Equal("abcdefg", result);
+
+                Assert.Equal(0, mockHandler.QueueCount);
+            }
+        }
+
+        [Fact]
+        public async Task GetAccessTokenAsync_ErrorResponseTest()
+        {
+            using (var mockHandler = new HttpMessageHandlerMock())
+            using (var http = new HttpClient(mockHandler))
+            {
+                var bitly = new BitlyApi(http);
+
+                mockHandler.Enqueue(x =>
+                {
+                    return new HttpResponseMessage(HttpStatusCode.OK)
+                    {
+                        Content = new StringContent("{\"status_code\": \"500\", \"status_txt\": \"MISSING_ARG_USERNAME\"}"),
+                    };
+                });
+
+                await Assert.ThrowsAsync<WebApiException>(() => bitly.GetAccessTokenAsync("hogehoge", "tetete"))
+                    .ConfigureAwait(false);
+
+                Assert.Equal(0, mockHandler.QueueCount);
+            }
+        }
+    }
+}
index 16e28b0..e99b467 100644 (file)
@@ -73,6 +73,7 @@
   <ItemGroup>
     <Compile Include="AnyOrderComparer.cs" />
     <Compile Include="Api\ApiLimitTest.cs" />
+    <Compile Include="Api\BitlyApiTest.cs" />
     <Compile Include="Api\MicrosoftTranslatorApiTest.cs" />
     <Compile Include="Api\TwitterApiStatusTest.cs" />
     <Compile Include="Api\TwitterApiTest.cs" />
diff --git a/OpenTween/Api/BitlyApi.cs b/OpenTween/Api/BitlyApi.cs
new file mode 100644 (file)
index 0000000..693fbf4
--- /dev/null
@@ -0,0 +1,160 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using OpenTween.Connection;
+
+namespace OpenTween.Api
+{
+    public class BitlyApi
+    {
+        public static readonly Uri ApiBase = new Uri("https://api-ssl.bitly.com/");
+
+        public string EndUserAccessToken { get; set; }
+
+        public string EndUserLoginName { get; set; }
+        public string EndUserApiKey { get; set; }
+
+        private HttpClient http => this.localHttpClient ?? Networking.Http;
+        private readonly HttpClient localHttpClient;
+
+        public BitlyApi()
+            : this(null)
+        {
+        }
+
+        public BitlyApi(HttpClient http)
+        {
+            this.localHttpClient = http;
+        }
+
+        public async Task<Uri> ShortenAsync(Uri srcUri, string domain = null)
+        {
+            var query = new Dictionary<string, string>
+            {
+                ["format"] = "txt",
+                ["longUrl"] = srcUri.OriginalString,
+            };
+
+            if (!string.IsNullOrEmpty(domain))
+                query["domain"] = domain;
+
+            var uri = new Uri("/v3/shorten", UriKind.Relative);
+            var responseText = await this.GetAsync(uri, query).ConfigureAwait(false);
+
+            if (!Regex.IsMatch(responseText, @"^https?://"))
+                throw new WebApiException("Failed to create URL.", responseText);
+
+            return new Uri(responseText.TrimEnd());
+        }
+
+        public async Task<string> GetAsync(Uri endpoint, IEnumerable<KeyValuePair<string, string>> param)
+        {
+            var paramWithToken = param.Concat(this.CreateAccessTokenParams());
+
+            var requestUri = new Uri(new Uri(ApiBase, endpoint), "?" + MyCommon.BuildQueryString(paramWithToken));
+
+            using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
+            using (var response = await this.http.SendAsync(request).ConfigureAwait(false))
+            {
+                return await response.Content.ReadAsStringAsync()
+                    .ConfigureAwait(false);
+            }
+        }
+
+        public async Task<string> GetAccessTokenAsync(string username, string password)
+        {
+            var param = new Dictionary<string, string>
+            {
+                ["grant_type"] = "password",
+                ["username"] = username,
+                ["password"] = password,
+            };
+
+            var endpoint = new Uri(ApiBase, "/oauth/access_token");
+
+            using (var request = new HttpRequestMessage(HttpMethod.Post, endpoint))
+            using (var postContent = new FormUrlEncodedContent(param))
+            {
+                var authzParam = ApplicationSettings.BitlyClientId + ":" + ApplicationSettings.BitlyClientSecret;
+                request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(authzParam)));
+
+                request.Content = postContent;
+
+                using (var response = await this.http.SendAsync(request).ConfigureAwait(false))
+                {
+                    var responseBytes = await response.Content.ReadAsByteArrayAsync()
+                        .ConfigureAwait(false);
+
+                    return this.ParseOAuthCredential(responseBytes);
+                }
+            }
+        }
+
+        private string ParseOAuthCredential(byte[] responseBytes)
+        {
+            using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max))
+            {
+                var xElm = XElement.Load(jsonReader);
+
+                var statusCode = xElm.Element("status_code")?.Value ?? "200";
+                if (statusCode != "200")
+                {
+                    var statusText = xElm.Element("status_txt")?.Value;
+                    throw new WebApiException(statusText ?? $"status_code = {statusCode}");
+                }
+
+                var accessToken = xElm.Element("access_token")?.Value;
+                if (accessToken == null)
+                    throw new WebApiException("Property `access_token` required");
+
+                return accessToken;
+            }
+        }
+
+        private IEnumerable<KeyValuePair<string, string>> CreateAccessTokenParams()
+        {
+            if (string.IsNullOrEmpty(this.EndUserAccessToken))
+            {
+                return new[]
+                {
+                    new KeyValuePair<string, string>("login", this.EndUserLoginName),
+                    new KeyValuePair<string, string>("apiKey", this.EndUserApiKey),
+                };
+            }
+
+            return new[]
+            {
+                new KeyValuePair<string, string>("access_token", this.EndUserAccessToken),
+            };
+        }
+    }
+}
index 2444320..06a67eb 100644 (file)
             this.Save.Name = "Save";
             this.ToolTip1.SetToolTip(this.Save, resources.GetString("Save.ToolTip"));
             this.Save.UseVisualStyleBackColor = true;
-            this.Save.Click += new System.EventHandler(this.Save_Click);
             // 
             // Cancel
             // 
             this.Cancel.Name = "Cancel";
             this.ToolTip1.SetToolTip(this.Cancel, resources.GetString("Cancel.ToolTip"));
             this.Cancel.UseVisualStyleBackColor = true;
-            this.Cancel.Click += new System.EventHandler(this.Cancel_Click);
             // 
             // ColorDialog1
             // 
index 2523f82..4ed41fd 100644 (file)
@@ -51,8 +51,6 @@ namespace OpenTween
         internal Twitter tw;
         internal TwitterApi twitterApi;
 
-        private bool _ValidationError = false;
-
         public AppendSettingDialog()
         {
             this.InitializeComponent();
@@ -151,44 +149,6 @@ namespace OpenTween
             }
         }
 
-        private void Save_Click(object sender, EventArgs e)
-        {
-            if (MyCommon.IsNetworkAvailable() &&
-                (this.ShortUrlPanel.ComboBoxAutoShortUrlFirst.SelectedIndex == (int)MyCommon.UrlConverter.Bitly || this.ShortUrlPanel.ComboBoxAutoShortUrlFirst.SelectedIndex == (int)MyCommon.UrlConverter.Jmp))
-            {
-                // bit.ly 短縮機能実装のプライバシー問題の暫定対応
-                // bit.ly 使用時はログインIDとAPIキーの指定を必須とする
-                // 参照: http://sourceforge.jp/projects/opentween/lists/archive/dev/2012-January/000020.html
-                if (string.IsNullOrEmpty(this.ShortUrlPanel.TextBitlyId.Text) || string.IsNullOrEmpty(this.ShortUrlPanel.TextBitlyPw.Text))
-                {
-                    MessageBox.Show("bit.ly のログイン名とAPIキーの指定は必須項目です。", Application.ProductName);
-                    _ValidationError = true;
-                    TreeViewSetting.SelectedNode = TreeViewSetting.Nodes["ConnectionNode"].Nodes["ShortUrlNode"]; // 動作タブを選択
-                    TreeViewSetting.Select();
-                    this.ShortUrlPanel.TextBitlyId.Focus();
-                    return;
-                }
-
-                if (!BitlyValidation(this.ShortUrlPanel.TextBitlyId.Text, this.ShortUrlPanel.TextBitlyPw.Text).Result)
-                {
-                    MessageBox.Show(Properties.Resources.SettingSave_ClickText1);
-                    _ValidationError = true;
-                    TreeViewSetting.SelectedNode = TreeViewSetting.Nodes["ConnectionNode"].Nodes["ShortUrlNode"]; // 動作タブを選択
-                    TreeViewSetting.Select();
-                    this.ShortUrlPanel.TextBitlyId.Focus();
-                    return;
-                }
-                else
-                {
-                    _ValidationError = false;
-                }
-            }
-            else
-            {
-                _ValidationError = false;
-            }
-        }
-
         private void Setting_FormClosing(object sender, FormClosingEventArgs e)
         {
             if (MyCommon._endingFlag) return;
@@ -200,10 +160,6 @@ namespace OpenTween
                     e.Cancel = true;
                 }
             }
-            if (_ValidationError)
-            {
-                e.Cancel = true;
-            }
             if (e.Cancel == false && TreeViewSetting.SelectedNode != null)
             {
                 var curPanel = (SettingPanelBase)TreeViewSetting.SelectedNode.Tag;
@@ -358,45 +314,6 @@ namespace OpenTween
             this.GetPeriodPanel.LabelUserStreamActive.Visible = tw.UserStreamActive;
         }
 
-        private async Task<bool> BitlyValidation(string id, string apikey)
-        {
-            if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(apikey))
-            {
-                return false;
-            }
-
-            try
-            {
-                var requestUri = new Uri("http://api.bit.ly/v3/validate");
-                var param = new Dictionary<string, string>
-                {
-                    ["login"] = ApplicationSettings.BitlyLoginId,
-                    ["apiKey"] = ApplicationSettings.BitlyApiKey,
-                    ["x_login"] = id,
-                    ["x_apiKey"] = apikey,
-                    ["format"] = "txt",
-                };
-
-                using (var postContent = new FormUrlEncodedContent(param))
-                using (var response = await Networking.Http.PostAsync(requestUri, postContent).ConfigureAwait(false))
-                {
-                    var responseText = await response.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
-
-                    return responseText.TrimEnd() == "1";
-                }
-            }
-            catch (OperationCanceledException) { }
-            catch (HttpRequestException) { }
-
-            return false;
-        }
-
-        private void Cancel_Click(object sender, EventArgs e)
-        {
-            _ValidationError = false;
-        }
-
         private void OpenUrl(string url)
         {
             string myPath = url;
index 00a8377..414a4e7 100644 (file)
@@ -119,17 +119,17 @@ namespace OpenTween
 
         //=====================================================================
         // bit.ly
-        // https://bitly.com/a/account から取得できます。
+        // https://bitly.com/a/oauth_apps から取得できます。
 
         /// <summary>
-        /// bit.ly ログイン名
+        /// bit.ly Client ID
         /// </summary>
-        public const string BitlyLoginId = "opentween";
+        public const string BitlyClientId = "ddab8ec50f4459c315cbde9d923cf490923b6d2e";
 
         /// <summary>
-        /// bit.ly APIキー
+        /// bit.ly Client Secret
         /// </summary>
-        public const string BitlyApiKey = "R_76319a25e2420b8d2c42e812fe177d8b";
+        public const string BitlyClientSecret = "485c9d03dd264f8eeb4fc65d38e2762c4420cee7";
 
         //=====================================================================
         // TINAMI
diff --git a/OpenTween/LoginDialog.Designer.cs b/OpenTween/LoginDialog.Designer.cs
new file mode 100644 (file)
index 0000000..65cf871
--- /dev/null
@@ -0,0 +1,107 @@
+namespace OpenTween
+{
+    partial class LoginDialog
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginDialog));
+            this.label1 = new System.Windows.Forms.Label();
+            this.textboxLoginName = new System.Windows.Forms.TextBox();
+            this.label2 = new System.Windows.Forms.Label();
+            this.textboxPassword = new System.Windows.Forms.TextBox();
+            this.buttonLogin = new System.Windows.Forms.Button();
+            this.buttonCancel = new System.Windows.Forms.Button();
+            this.SuspendLayout();
+            // 
+            // label1
+            // 
+            resources.ApplyResources(this.label1, "label1");
+            this.label1.Name = "label1";
+            // 
+            // textboxLoginName
+            // 
+            resources.ApplyResources(this.textboxLoginName, "textboxLoginName");
+            this.textboxLoginName.Name = "textboxLoginName";
+            // 
+            // label2
+            // 
+            resources.ApplyResources(this.label2, "label2");
+            this.label2.Name = "label2";
+            // 
+            // textboxPassword
+            // 
+            resources.ApplyResources(this.textboxPassword, "textboxPassword");
+            this.textboxPassword.Name = "textboxPassword";
+            this.textboxPassword.UseSystemPasswordChar = true;
+            // 
+            // buttonLogin
+            // 
+            resources.ApplyResources(this.buttonLogin, "buttonLogin");
+            this.buttonLogin.Name = "buttonLogin";
+            this.buttonLogin.UseVisualStyleBackColor = true;
+            this.buttonLogin.Click += new System.EventHandler(this.buttonLogin_Click);
+            // 
+            // buttonCancel
+            // 
+            resources.ApplyResources(this.buttonCancel, "buttonCancel");
+            this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.buttonCancel.Name = "buttonCancel";
+            this.buttonCancel.UseVisualStyleBackColor = true;
+            // 
+            // LoginDialog
+            // 
+            this.AcceptButton = this.buttonLogin;
+            resources.ApplyResources(this, "$this");
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.CancelButton = this.buttonCancel;
+            this.Controls.Add(this.buttonCancel);
+            this.Controls.Add(this.buttonLogin);
+            this.Controls.Add(this.textboxPassword);
+            this.Controls.Add(this.label2);
+            this.Controls.Add(this.textboxLoginName);
+            this.Controls.Add(this.label1);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "LoginDialog";
+            this.ShowIcon = false;
+            this.ShowInTaskbar = false;
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.TextBox textboxLoginName;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.TextBox textboxPassword;
+        private System.Windows.Forms.Button buttonLogin;
+        private System.Windows.Forms.Button buttonCancel;
+    }
+}
\ No newline at end of file
diff --git a/OpenTween/LoginDialog.cs b/OpenTween/LoginDialog.cs
new file mode 100644 (file)
index 0000000..1645a30
--- /dev/null
@@ -0,0 +1,68 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace OpenTween
+{
+    public partial class LoginDialog : OTBaseForm
+    {
+        public string LoginName => this.textboxLoginName.Text;
+        public string Password => this.textboxPassword.Text;
+
+        public Func<Task<bool>> LoginCallback { get; set; } = null;
+        public bool LoginSuccessed { get; set; } = false;
+
+        public LoginDialog()
+            => this.InitializeComponent();
+
+        private async void buttonLogin_Click(object sender, EventArgs e)
+        {
+            if (this.LoginCallback == null)
+                return;
+
+            try
+            {
+                using (ControlTransaction.Disabled(this))
+                {
+                    // AcceptButton によって自動でフォームが閉じられるのを抑制する
+                    this.AcceptButton = null;
+
+                    this.LoginSuccessed = await this.LoginCallback();
+                    if (this.LoginSuccessed)
+                        this.DialogResult = DialogResult.OK;
+                }
+            }
+            finally
+            {
+                this.AcceptButton = this.buttonLogin;
+            }
+        }
+    }
+}
diff --git a/OpenTween/LoginDialog.en.resx b/OpenTween/LoginDialog.en.resx
new file mode 100644 (file)
index 0000000..61d4b70
--- /dev/null
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="label1.Size" type="System.Drawing.Size, System.Drawing">
+    <value>56, 12</value>
+  </data>
+  <data name="label1.Text" xml:space="preserve">
+    <value>&amp;Username</value>
+  </data>
+  <data name="label2.Size" type="System.Drawing.Size, System.Drawing">
+    <value>54, 12</value>
+  </data>
+  <data name="label2.Text" xml:space="preserve">
+    <value>&amp;Password</value>
+  </data>
+  <data name="buttonLogin.Text" xml:space="preserve">
+    <value>&amp;Login</value>
+  </data>
+  <data name="buttonCancel.Text" xml:space="preserve">
+    <value>&amp;Cancel</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/OpenTween/LoginDialog.resx b/OpenTween/LoginDialog.resx
new file mode 100644 (file)
index 0000000..696d377
--- /dev/null
@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="label1.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="label1.Location" type="System.Drawing.Point, System.Drawing">
+    <value>12, 15</value>
+  </data>
+  <data name="label1.Size" type="System.Drawing.Size, System.Drawing">
+    <value>73, 12</value>
+  </data>
+  <data name="label1.TabIndex" type="System.Int32, mscorlib">
+    <value>0</value>
+  </data>
+  <data name="label1.Text" xml:space="preserve">
+    <value>ユーザー名(&amp;U)</value>
+  </data>
+  <data name="&gt;&gt;label1.Name" xml:space="preserve">
+    <value>label1</value>
+  </data>
+  <data name="&gt;&gt;label1.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;label1.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;label1.ZOrder" xml:space="preserve">
+    <value>5</value>
+  </data>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="textboxLoginName.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Left, Right</value>
+  </data>
+  <data name="textboxLoginName.Location" type="System.Drawing.Point, System.Drawing">
+    <value>94, 12</value>
+  </data>
+  <data name="textboxLoginName.Size" type="System.Drawing.Size, System.Drawing">
+    <value>193, 19</value>
+  </data>
+  <data name="textboxLoginName.TabIndex" type="System.Int32, mscorlib">
+    <value>1</value>
+  </data>
+  <data name="&gt;&gt;textboxLoginName.Name" xml:space="preserve">
+    <value>textboxLoginName</value>
+  </data>
+  <data name="&gt;&gt;textboxLoginName.Type" xml:space="preserve">
+    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;textboxLoginName.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;textboxLoginName.ZOrder" xml:space="preserve">
+    <value>4</value>
+  </data>
+  <data name="label2.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <data name="label2.Location" type="System.Drawing.Point, System.Drawing">
+    <value>12, 40</value>
+  </data>
+  <data name="label2.Size" type="System.Drawing.Size, System.Drawing">
+    <value>67, 12</value>
+  </data>
+  <data name="label2.TabIndex" type="System.Int32, mscorlib">
+    <value>2</value>
+  </data>
+  <data name="label2.Text" xml:space="preserve">
+    <value>パスワード(&amp;P)</value>
+  </data>
+  <data name="&gt;&gt;label2.Name" xml:space="preserve">
+    <value>label2</value>
+  </data>
+  <data name="&gt;&gt;label2.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;label2.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;label2.ZOrder" xml:space="preserve">
+    <value>3</value>
+  </data>
+  <data name="textboxPassword.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Left, Right</value>
+  </data>
+  <data name="textboxPassword.Location" type="System.Drawing.Point, System.Drawing">
+    <value>94, 37</value>
+  </data>
+  <data name="textboxPassword.Size" type="System.Drawing.Size, System.Drawing">
+    <value>193, 19</value>
+  </data>
+  <data name="textboxPassword.TabIndex" type="System.Int32, mscorlib">
+    <value>3</value>
+  </data>
+  <data name="&gt;&gt;textboxPassword.Name" xml:space="preserve">
+    <value>textboxPassword</value>
+  </data>
+  <data name="&gt;&gt;textboxPassword.Type" xml:space="preserve">
+    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;textboxPassword.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;textboxPassword.ZOrder" xml:space="preserve">
+    <value>2</value>
+  </data>
+  <data name="buttonLogin.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Bottom, Right</value>
+  </data>
+  <data name="buttonLogin.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <data name="buttonLogin.Location" type="System.Drawing.Point, System.Drawing">
+    <value>146, 64</value>
+  </data>
+  <data name="buttonLogin.Size" type="System.Drawing.Size, System.Drawing">
+    <value>75, 25</value>
+  </data>
+  <data name="buttonLogin.TabIndex" type="System.Int32, mscorlib">
+    <value>4</value>
+  </data>
+  <data name="buttonLogin.Text" xml:space="preserve">
+    <value>ログイン(&amp;L)</value>
+  </data>
+  <data name="&gt;&gt;buttonLogin.Name" xml:space="preserve">
+    <value>buttonLogin</value>
+  </data>
+  <data name="&gt;&gt;buttonLogin.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;buttonLogin.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;buttonLogin.ZOrder" xml:space="preserve">
+    <value>1</value>
+  </data>
+  <data name="buttonCancel.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Bottom, Right</value>
+  </data>
+  <data name="buttonCancel.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <data name="buttonCancel.Location" type="System.Drawing.Point, System.Drawing">
+    <value>227, 64</value>
+  </data>
+  <data name="buttonCancel.Size" type="System.Drawing.Size, System.Drawing">
+    <value>78, 25</value>
+  </data>
+  <data name="buttonCancel.TabIndex" type="System.Int32, mscorlib">
+    <value>5</value>
+  </data>
+  <data name="buttonCancel.Text" xml:space="preserve">
+    <value>キャンセル(&amp;C)</value>
+  </data>
+  <data name="&gt;&gt;buttonCancel.Name" xml:space="preserve">
+    <value>buttonCancel</value>
+  </data>
+  <data name="&gt;&gt;buttonCancel.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;buttonCancel.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;buttonCancel.ZOrder" xml:space="preserve">
+    <value>0</value>
+  </data>
+  <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
+    <value>6, 12</value>
+  </data>
+  <data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
+    <value>317, 101</value>
+  </data>
+  <data name="$this.StartPosition" type="System.Windows.Forms.FormStartPosition, System.Windows.Forms">
+    <value>CenterParent</value>
+  </data>
+  <data name="$this.Text" xml:space="preserve">
+    <value>LoginDialog</value>
+  </data>
+  <data name="&gt;&gt;$this.Name" xml:space="preserve">
+    <value>LoginDialog</value>
+  </data>
+  <data name="&gt;&gt;$this.Type" xml:space="preserve">
+    <value>OpenTween.OTBaseForm, OpenTween, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null</value>
+  </data>
+</root>
\ No newline at end of file
index b971f20..4b63e06 100644 (file)
@@ -69,6 +69,7 @@
       <DependentUpon>ApiInfoDialog.cs</DependentUpon>
     </Compile>
     <Compile Include="Api\ApiLimit.cs" />
+    <Compile Include="Api\BitlyApi.cs" />
     <Compile Include="Api\DataModel\GeoJson.cs" />
     <Compile Include="Api\DataModel\TwitterConfiguration.cs" />
     <Compile Include="Api\DataModel\TwitterEntity.cs" />
       <DependentUpon>FilterDialog.cs</DependentUpon>
     </Compile>
     <Compile Include="IndexedSortedSet.cs" />
+    <Compile Include="LoginDialog.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LoginDialog.Designer.cs">
+      <DependentUpon>LoginDialog.cs</DependentUpon>
+    </Compile>
     <Compile Include="MediaItem.cs" />
     <Compile Include="Models\ComparerMode.cs" />
     <Compile Include="Models\DirectMessagesTabModel.cs" />
       <DependentUpon>AppendSettingDialog.cs</DependentUpon>
       <SubType>Designer</SubType>
     </EmbeddedResource>
+    <EmbeddedResource Include="LoginDialog.en.resx">
+      <DependentUpon>LoginDialog.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="LoginDialog.resx">
+      <DependentUpon>LoginDialog.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="MediaSelector.en.resx">
       <DependentUpon>MediaSelector.cs</DependentUpon>
     </EmbeddedResource>
index 9b2f03b..d0415e5 100644 (file)
@@ -335,6 +335,15 @@ namespace OpenTween.Properties {
         }
         
         /// <summary>
+        ///   認証に失敗しました ({0}) に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string BitlyAuthorize_ErrorText {
+            get {
+                return ResourceManager.GetString("BitlyAuthorize_ErrorText", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   ブラウザの起動に失敗しました。エラーコード: {0} に類似しているローカライズされた文字列を検索します。
         /// </summary>
         internal static string BrowserStartFailed {
@@ -2541,15 +2550,6 @@ namespace OpenTween.Properties {
         }
         
         /// <summary>
-        ///   IDとAPIキーの組み合わせが違います。IDと同時に設定するのはパスワードではなくAPIキーです。ご確認ください。 に類似しているローカライズされた文字列を検索します。
-        /// </summary>
-        internal static string SettingSave_ClickText1 {
-            get {
-                return ResourceManager.GetString("SettingSave_ClickText1", resourceCulture);
-            }
-        }
-        
-        /// <summary>
         ///   フォロー状況取得中・・・ に類似しているローカライズされた文字列を検索します。
         /// </summary>
         internal static string ShowFriendshipText1 {
@@ -3189,6 +3189,15 @@ namespace OpenTween.Properties {
         }
         
         /// <summary>
+        ///   Bitlyを使用するには設定画面で認証情報を入力する必要があります に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string UrlConvert_BitlyAuthRequired {
+            get {
+                return ResourceManager.GetString("UrlConvert_BitlyAuthRequired", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   適用 に類似しているローカライズされた文字列を検索します。
         /// </summary>
         internal static string UserInfoButtonEdit_ClickText1 {
index 0caaa46..0891445 100644 (file)
@@ -1009,9 +1009,6 @@ Do you want to upload the {1} selected media file(s)?</value>
   <data name="SaveLogMenuItem_ClickText5" xml:space="preserve">
     <value>Save events?{0}  [Yes]    :Save events in this tab{0}  [No]  :Save all events{0}[Cancel]:Cancel{0}(Save as Tab separated text(TSV))</value>
   </data>
-  <data name="SettingSave_ClickText1" xml:space="preserve">
-    <value>Failed to authorization. Please confirm api key.</value>
-  </data>
   <data name="TranslateDefaultLanguage" xml:space="preserve">
     <value>en</value>
   </data>
@@ -1155,4 +1152,10 @@ Existing setting files are copied to {1}.
  * If you run as administrator, operations to post images with D&amp;D will be restricted.
  * After running, the setting files of {0} will be able to modify only from administrator.</value>
   </data>
+  <data name="BitlyAuthorize_ErrorText" xml:space="preserve">
+    <value>Failed to authorize. ({0})</value>
+  </data>
+  <data name="UrlConvert_BitlyAuthRequired" xml:space="preserve">
+    <value>To use Bitly, you must perform the Bitly authentication in settings dialog.</value>
+  </data>
 </root>
\ No newline at end of file
index 612c2cf..7450162 100644 (file)
   <data name="ChangeIconToolStripMenuItem_ClickText5" xml:space="preserve">
     <value>アイコンを変更しました。 次回発言より反映されます。</value>
   </data>
-  <data name="SettingSave_ClickText1" xml:space="preserve">
-    <value>IDとAPIキーの組み合わせが違います。IDと同時に設定するのはパスワードではなくAPIキーです。ご確認ください。</value>
-  </data>
   <data name="UserInfoButtonEdit_ClickText1" xml:space="preserve">
     <value>適用</value>
   </data>
  * 管理者権限で実行するとD&amp;Dで画像を投稿する操作が制限されます。
  * {0}の設定ファイルが一般ユーザー権限で編集できなくなります。</value>
   </data>
+  <data name="BitlyAuthorize_ErrorText" xml:space="preserve">
+    <value>認証に失敗しました ({0})</value>
+  </data>
+  <data name="UrlConvert_BitlyAuthRequired" xml:space="preserve">
+    <value>Bitlyを使用するには設定画面で認証情報を入力する必要があります</value>
+  </data>
 </root>
\ No newline at end of file
index 538b606..f6321d2 100644 (file)
@@ -1,7 +1,8 @@
 更新履歴
 
 ==== Ver 1.3.8-dev(2017/xx/xx)
- * FIX: bit.ly の正しいAPIキーを入力しても検証時にエラーが表示される不具合を修正
+ * NEW: bit.ly の認証方式が変更されました
+  - 短縮URLに bit.ly を使用する場合は、設定画面の「短縮URL」から bit.ly の「認可」ボタンを押して認証情報を入力して下さい
 
 ==== Ver 1.3.7(2017/03/20)
  * NEW: PNG画像のアップロード時にJPEGへの変換による劣化を回避する機能を追加しました (pic.twitter.com のみ)
index bd21647..bf20d8a 100644 (file)
             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ShortUrlPanel));
             this.ShortenTcoCheck = new System.Windows.Forms.CheckBox();
             this.CheckTinyURL = new System.Windows.Forms.CheckBox();
-            this.TextBitlyPw = new System.Windows.Forms.TextBox();
+            this.TextBitlyAccessToken = new System.Windows.Forms.TextBox();
             this.CheckAutoConvertUrl = new System.Windows.Forms.CheckBox();
             this.Label71 = new System.Windows.Forms.Label();
             this.ComboBoxAutoShortUrlFirst = new System.Windows.Forms.ComboBox();
-            this.Label76 = new System.Windows.Forms.Label();
             this.Label77 = new System.Windows.Forms.Label();
-            this.TextBitlyId = new System.Windows.Forms.TextBox();
+            this.ButtonBitlyAuthorize = new System.Windows.Forms.Button();
             this.SuspendLayout();
             // 
             // ShortenTcoCheck
             this.CheckTinyURL.Name = "CheckTinyURL";
             this.CheckTinyURL.UseVisualStyleBackColor = true;
             // 
-            // TextBitlyPw
+            // TextBitlyAccessToken
             // 
-            resources.ApplyResources(this.TextBitlyPw, "TextBitlyPw");
-            this.TextBitlyPw.Name = "TextBitlyPw";
+            resources.ApplyResources(this.TextBitlyAccessToken, "TextBitlyAccessToken");
+            this.TextBitlyAccessToken.Name = "TextBitlyAccessToken";
             // 
             // CheckAutoConvertUrl
             // 
             this.ComboBoxAutoShortUrlFirst.Name = "ComboBoxAutoShortUrlFirst";
             this.ComboBoxAutoShortUrlFirst.SelectedIndexChanged += new System.EventHandler(this.ComboBoxAutoShortUrlFirst_SelectedIndexChanged);
             // 
-            // Label76
-            // 
-            resources.ApplyResources(this.Label76, "Label76");
-            this.Label76.Name = "Label76";
-            // 
             // Label77
             // 
             resources.ApplyResources(this.Label77, "Label77");
             this.Label77.Name = "Label77";
             // 
-            // TextBitlyId
+            // ButtonBitlyAuthorize
             // 
-            resources.ApplyResources(this.TextBitlyId, "TextBitlyId");
-            this.TextBitlyId.Name = "TextBitlyId";
+            resources.ApplyResources(this.ButtonBitlyAuthorize, "ButtonBitlyAuthorize");
+            this.ButtonBitlyAuthorize.Name = "ButtonBitlyAuthorize";
+            this.ButtonBitlyAuthorize.UseVisualStyleBackColor = true;
+            this.ButtonBitlyAuthorize.Click += new System.EventHandler(this.ButtonBitlyAuthorize_Click);
             // 
             // ShortUrlPanel
             // 
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
             this.Controls.Add(this.ShortenTcoCheck);
             this.Controls.Add(this.CheckTinyURL);
-            this.Controls.Add(this.TextBitlyPw);
+            this.Controls.Add(this.TextBitlyAccessToken);
             this.Controls.Add(this.CheckAutoConvertUrl);
             this.Controls.Add(this.Label71);
             this.Controls.Add(this.ComboBoxAutoShortUrlFirst);
-            this.Controls.Add(this.Label76);
+            this.Controls.Add(this.ButtonBitlyAuthorize);
             this.Controls.Add(this.Label77);
-            this.Controls.Add(this.TextBitlyId);
             this.Name = "ShortUrlPanel";
             this.ResumeLayout(false);
             this.PerformLayout();
 
         internal System.Windows.Forms.CheckBox ShortenTcoCheck;
         internal System.Windows.Forms.CheckBox CheckTinyURL;
-        internal System.Windows.Forms.TextBox TextBitlyPw;
         internal System.Windows.Forms.CheckBox CheckAutoConvertUrl;
         internal System.Windows.Forms.Label Label71;
         internal System.Windows.Forms.ComboBox ComboBoxAutoShortUrlFirst;
-        internal System.Windows.Forms.Label Label76;
         internal System.Windows.Forms.Label Label77;
-        internal System.Windows.Forms.TextBox TextBitlyId;
+        private System.Windows.Forms.Button ButtonBitlyAuthorize;
+        private System.Windows.Forms.TextBox TextBitlyAccessToken;
     }
 }
index 564bdfd..cf96dd0 100644 (file)
@@ -32,6 +32,8 @@ using System.Data;
 using System.Linq;
 using System.Text;
 using System.Windows.Forms;
+using OpenTween.Api;
+using System.Threading.Tasks;
 
 namespace OpenTween.Setting.Panel
 {
@@ -53,10 +55,8 @@ namespace OpenTween.Setting.Panel
             this.ShortenTcoCheck.Enabled = this.CheckAutoConvertUrl.Checked;
 
             this.ComboBoxAutoShortUrlFirst.SelectedIndex = (int)settingCommon.AutoShortUrlFirst;
-            this.TextBitlyId.Text = settingCommon.BilyUser;
-            this.TextBitlyPw.Text = settingCommon.BitlyPwd;
-            this.TextBitlyId.Modified = false;
-            this.TextBitlyPw.Modified = false;
+            this.TextBitlyAccessToken.Text = settingCommon.BitlyAccessToken;
+            this.TextBitlyAccessToken.Modified = false;
         }
 
         public void SaveConfig(SettingCommon settingCommon)
@@ -65,8 +65,7 @@ namespace OpenTween.Setting.Panel
             settingCommon.UrlConvertAuto = this.CheckAutoConvertUrl.Checked;
             //settingCommon.ShortenTco = this.ShortenTcoCheck.Checked;
             settingCommon.AutoShortUrlFirst = (MyCommon.UrlConverter)this.ComboBoxAutoShortUrlFirst.SelectedIndex;
-            settingCommon.BilyUser = this.TextBitlyId.Text;
-            settingCommon.BitlyPwd = this.TextBitlyPw.Text;
+            settingCommon.BitlyAccessToken = this.TextBitlyAccessToken.Text;
         }
 
         private void ComboBoxAutoShortUrlFirst_SelectedIndexChanged(object sender, EventArgs e)
@@ -74,17 +73,15 @@ namespace OpenTween.Setting.Panel
             if (ComboBoxAutoShortUrlFirst.SelectedIndex == (int)MyCommon.UrlConverter.Bitly ||
                ComboBoxAutoShortUrlFirst.SelectedIndex == (int)MyCommon.UrlConverter.Jmp)
             {
-                Label76.Enabled = true;
                 Label77.Enabled = true;
-                TextBitlyId.Enabled = true;
-                TextBitlyPw.Enabled = true;
+                TextBitlyAccessToken.Enabled = true;
+                ButtonBitlyAuthorize.Enabled = true;
             }
             else
             {
-                Label76.Enabled = false;
                 Label77.Enabled = false;
-                TextBitlyId.Enabled = false;
-                TextBitlyPw.Enabled = false;
+                TextBitlyAccessToken.Enabled = false;
+                ButtonBitlyAuthorize.Enabled = false;
             }
         }
 
@@ -92,5 +89,36 @@ namespace OpenTween.Setting.Panel
         {
             ShortenTcoCheck.Enabled = CheckAutoConvertUrl.Checked;
         }
+
+        private void ButtonBitlyAuthorize_Click(object sender, EventArgs e)
+        {
+            using (var dialog = new LoginDialog())
+            {
+                const string DialogText = "Bitly Login";
+                dialog.Text = DialogText;
+
+                string accessToken = null;
+                dialog.LoginCallback = async () =>
+                {
+                    try
+                    {
+                        var bitly = new BitlyApi();
+                        accessToken = await bitly.GetAccessTokenAsync(dialog.LoginName, dialog.Password);
+                        return true;
+                    }
+                    catch (WebApiException ex)
+                    {
+                        var text = string.Format(Properties.Resources.BitlyAuthorize_ErrorText, ex.Message);
+                        MessageBox.Show(dialog, text, DialogText, MessageBoxButtons.OK, MessageBoxIcon.Error);
+                        return false;
+                    }
+                };
+
+                if (dialog.ShowDialog(this.ParentForm) == DialogResult.OK)
+                {
+                    this.TextBitlyAccessToken.Text = accessToken;
+                }
+            }
+        }
     }
 }
index 4dfc4cd..2ecd57d 100644 (file)
   <data name="Label71.Text" xml:space="preserve">
     <value>Primary URLshorten service</value>
   </data>
+  <data name="Label77.Size" type="System.Drawing.Size, System.Drawing">
+    <value>108, 12</value>
+  </data>
+  <data name="Label77.Text" xml:space="preserve">
+    <value>Bit.ly Access Token</value>
+  </data>
+  <data name="ButtonBitlyAuthorize.Text" xml:space="preserve">
+    <value>Authorize</value>
+  </data>
 </root>
\ No newline at end of file
index a6b97cb..41f6b3f 100644 (file)
     <value>95, 16</value>
   </data>
   <data name="ShortenTcoCheck.TabIndex" type="System.Int32, mscorlib">
-    <value>18</value>
+    <value>2</value>
   </data>
   <data name="ShortenTcoCheck.Text" xml:space="preserve">
     <value>t.coで短縮する</value>
     <value>122, 16</value>
   </data>
   <data name="CheckTinyURL.TabIndex" type="System.Int32, mscorlib">
-    <value>10</value>
+    <value>0</value>
   </data>
   <data name="CheckTinyURL.Text" xml:space="preserve">
     <value>短縮URLを解決する</value>
   <data name="&gt;&gt;CheckTinyURL.ZOrder" xml:space="preserve">
     <value>1</value>
   </data>
-  <data name="TextBitlyPw.Location" type="System.Drawing.Point, System.Drawing">
-    <value>419, 127</value>
+  <data name="TextBitlyAccessToken.Location" type="System.Drawing.Point, System.Drawing">
+    <value>249, 131</value>
   </data>
-  <data name="TextBitlyPw.Size" type="System.Drawing.Size, System.Drawing">
-    <value>70, 19</value>
+  <data name="TextBitlyAccessToken.Size" type="System.Drawing.Size, System.Drawing">
+    <value>173, 19</value>
   </data>
-  <data name="TextBitlyPw.TabIndex" type="System.Int32, mscorlib">
-    <value>17</value>
+  <data name="TextBitlyAccessToken.TabIndex" type="System.Int32, mscorlib">
+    <value>6</value>
   </data>
-  <data name="&gt;&gt;TextBitlyPw.Name" xml:space="preserve">
-    <value>TextBitlyPw</value>
+  <data name="&gt;&gt;TextBitlyAccessToken.Name" xml:space="preserve">
+    <value>TextBitlyAccessToken</value>
   </data>
-  <data name="&gt;&gt;TextBitlyPw.Type" xml:space="preserve">
+  <data name="&gt;&gt;TextBitlyAccessToken.Type" xml:space="preserve">
     <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;TextBitlyPw.Parent" xml:space="preserve">
+  <data name="&gt;&gt;TextBitlyAccessToken.Parent" xml:space="preserve">
     <value>$this</value>
   </data>
-  <data name="&gt;&gt;TextBitlyPw.ZOrder" xml:space="preserve">
+  <data name="&gt;&gt;TextBitlyAccessToken.ZOrder" xml:space="preserve">
     <value>2</value>
   </data>
   <data name="CheckAutoConvertUrl.AutoSize" type="System.Boolean, mscorlib">
     <value>242, 16</value>
   </data>
   <data name="CheckAutoConvertUrl.TabIndex" type="System.Int32, mscorlib">
-    <value>11</value>
+    <value>1</value>
   </data>
   <data name="CheckAutoConvertUrl.Text" xml:space="preserve">
     <value>入力欄のURLを投稿する際に自動で短縮する</value>
     <value>154, 12</value>
   </data>
   <data name="Label71.TabIndex" type="System.Int32, mscorlib">
-    <value>12</value>
+    <value>3</value>
   </data>
   <data name="Label71.Text" xml:space="preserve">
     <value>URL自動短縮で優先的に使用</value>
     <value>246, 20</value>
   </data>
   <data name="ComboBoxAutoShortUrlFirst.TabIndex" type="System.Int32, mscorlib">
-    <value>13</value>
+    <value>4</value>
   </data>
   <data name="&gt;&gt;ComboBoxAutoShortUrlFirst.Name" xml:space="preserve">
     <value>ComboBoxAutoShortUrlFirst</value>
   <data name="&gt;&gt;ComboBoxAutoShortUrlFirst.ZOrder" xml:space="preserve">
     <value>5</value>
   </data>
-  <data name="Label76.AutoSize" type="System.Boolean, mscorlib">
-    <value>True</value>
-  </data>
-  <data name="Label76.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
-    <value>NoControl</value>
-  </data>
-  <data name="Label76.Location" type="System.Drawing.Point, System.Drawing">
-    <value>246, 130</value>
-  </data>
-  <data name="Label76.Size" type="System.Drawing.Size, System.Drawing">
-    <value>16, 12</value>
-  </data>
-  <data name="Label76.TabIndex" type="System.Int32, mscorlib">
-    <value>14</value>
-  </data>
-  <data name="Label76.Text" xml:space="preserve">
-    <value>ID</value>
-  </data>
-  <data name="&gt;&gt;Label76.Name" xml:space="preserve">
-    <value>Label76</value>
-  </data>
-  <data name="&gt;&gt;Label76.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;Label76.Parent" xml:space="preserve">
-    <value>$this</value>
-  </data>
-  <data name="&gt;&gt;Label76.ZOrder" xml:space="preserve">
-    <value>6</value>
-  </data>
   <data name="Label77.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
   </data>
     <value>NoControl</value>
   </data>
   <data name="Label77.Location" type="System.Drawing.Point, System.Drawing">
-    <value>364, 130</value>
+    <value>23, 134</value>
   </data>
   <data name="Label77.Size" type="System.Drawing.Size, System.Drawing">
-    <value>42, 12</value>
+    <value>106, 12</value>
   </data>
   <data name="Label77.TabIndex" type="System.Int32, mscorlib">
-    <value>16</value>
+    <value>5</value>
   </data>
   <data name="Label77.Text" xml:space="preserve">
-    <value>APIKey</value>
+    <value>Bit.ly アクセストークン</value>
   </data>
   <data name="&gt;&gt;Label77.Name" xml:space="preserve">
     <value>Label77</value>
   <data name="&gt;&gt;Label77.ZOrder" xml:space="preserve">
     <value>7</value>
   </data>
-  <data name="TextBitlyId.Location" type="System.Drawing.Point, System.Drawing">
-    <value>273, 127</value>
+  <data name="ButtonBitlyAuthorize.Location" type="System.Drawing.Point, System.Drawing">
+    <value>429, 129</value>
+  </data>
+  <data name="ButtonBitlyAuthorize.Size" type="System.Drawing.Size, System.Drawing">
+    <value>67, 23</value>
   </data>
-  <data name="TextBitlyId.Size" type="System.Drawing.Size, System.Drawing">
-    <value>71, 19</value>
+  <data name="ButtonBitlyAuthorize.TabIndex" type="System.Int32, mscorlib">
+    <value>7</value>
   </data>
-  <data name="TextBitlyId.TabIndex" type="System.Int32, mscorlib">
-    <value>15</value>
+  <data name="ButtonBitlyAuthorize.Text" xml:space="preserve">
+    <value>認可</value>
   </data>
-  <data name="&gt;&gt;TextBitlyId.Name" xml:space="preserve">
-    <value>TextBitlyId</value>
+  <data name="&gt;&gt;ButtonBitlyAuthorize.Name" xml:space="preserve">
+    <value>ButtonBitlyAuthorize</value>
   </data>
-  <data name="&gt;&gt;TextBitlyId.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="&gt;&gt;ButtonBitlyAuthorize.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;TextBitlyId.Parent" xml:space="preserve">
+  <data name="&gt;&gt;ButtonBitlyAuthorize.Parent" xml:space="preserve">
     <value>$this</value>
   </data>
-  <data name="&gt;&gt;TextBitlyId.ZOrder" xml:space="preserve">
-    <value>8</value>
+  <data name="&gt;&gt;ButtonBitlyAuthorize.ZOrder" xml:space="preserve">
+    <value>6</value>
   </data>
   <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
index 8abdaf7..33fe841 100644 (file)
@@ -196,6 +196,10 @@ namespace OpenTween
         public bool GetFav = true;
         public string BilyUser = "";
         public string BitlyPwd = "";
+
+        /// <summary>Bitly API アクセストークン</summary>
+        public string BitlyAccessToken { get; set; } = "";
+
         public bool ShowGrid = false;
         public bool UseAtIdSupplement = true;
         public bool UseHashSupplement = true;
index a08527e..dd774fe 100644 (file)
@@ -35,6 +35,7 @@ using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Web;
+using OpenTween.Api;
 using OpenTween.Connection;
 
 namespace OpenTween
@@ -64,6 +65,7 @@ namespace OpenTween
         /// </summary>
         public int PurgeCount { get; set; }
 
+        public string BitlyAccessToken { get; set; }
         public string BitlyId { get; set; }
         public string BitlyKey { get; set; }
 
@@ -443,34 +445,19 @@ namespace OpenTween
             if ("http://bit.ly/xxxx".Length > srcUri.OriginalString.Length)
                 return srcUri;
 
-            // bit.ly 短縮機能実装のプライバシー問題の暫定対応
-            // ログインIDとAPIキーが指定されていない場合は短縮せずにPOSTする
-            // 参照: http://sourceforge.jp/projects/opentween/lists/archive/dev/2012-January/000020.html
-            if (string.IsNullOrEmpty(this.BitlyId) || string.IsNullOrEmpty(this.BitlyKey))
+            // OAuth2 アクセストークンまたは API キー (旧方式) のいずれも設定されていなければ短縮しない
+            if (string.IsNullOrEmpty(this.BitlyAccessToken) && (string.IsNullOrEmpty(this.BitlyId) || string.IsNullOrEmpty(this.BitlyKey)))
                 return srcUri;
 
-            var query = new Dictionary<string, string>
+            var bitly = new BitlyApi
             {
-                ["login"] = this.BitlyId,
-                ["apiKey"] = this.BitlyKey,
-                ["format"] = "txt",
-                ["domain"] = domain,
-                ["longUrl"] = srcUri.OriginalString,
+                EndUserAccessToken = this.BitlyAccessToken,
+                EndUserLoginName = this.BitlyId,
+                EndUserApiKey = this.BitlyKey,
             };
 
-            var uri = new Uri("https://api-ssl.bitly.com/v3/shorten?" + MyCommon.BuildQueryString(query));
-            using (var response = await this.http.GetAsync(uri).ConfigureAwait(false))
-            {
-                response.EnsureSuccessStatusCode();
-
-                var result = await response.Content.ReadAsStringAsync()
-                    .ConfigureAwait(false);
-
-                if (!Regex.IsMatch(result, @"^https?://"))
-                    throw new WebApiException("Failed to create URL.", result);
-
-                return new Uri(result.TrimEnd());
-            }
+            return await bitly.ShortenAsync(srcUri, domain)
+                .ConfigureAwait(false);
         }
 
         private async Task<Uri> ShortenByUxnuAsync(Uri srcUri)
index 245444f..864ed57 100644 (file)
@@ -811,6 +811,7 @@ namespace OpenTween
             tw.AllAtReply = SettingManager.Common.AllAtReply;
             AllrepliesToolStripMenuItem.Checked = tw.AllAtReply;
             ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
+            ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
             ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
             ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
 
@@ -3731,6 +3732,7 @@ namespace OpenTween
                     tw.RestrictFavCheck = SettingManager.Common.RestrictFavCheck;
                     tw.ReadOwnPost = SettingManager.Common.ReadOwnPost;
                     ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
+                    ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
                     ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
                     ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
                     TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
@@ -9199,6 +9201,17 @@ namespace OpenTween
 
         private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
         {
+            if (Converter_Type == MyCommon.UrlConverter.Bitly || Converter_Type == MyCommon.UrlConverter.Jmp)
+            {
+                // OAuth2 アクセストークンまたは API キー (旧方式) のいずれも設定されていなければ短縮しない
+                if (string.IsNullOrEmpty(SettingManager.Common.BitlyAccessToken) &&
+                    (string.IsNullOrEmpty(SettingManager.Common.BilyUser) || string.IsNullOrEmpty(SettingManager.Common.BitlyPwd)))
+                {
+                    MessageBox.Show(this, Properties.Resources.UrlConvert_BitlyAuthRequired, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                    return false;
+                }
+            }
+
             //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;