From 52335c36c0311a8a9cd4b3bd292a7c7fc9dd62ad Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 14 Jun 2014 09:11:41 +0900 Subject: [PATCH] =?utf8?q?=E7=94=BB=E5=83=8F=E5=85=B1=E6=9C=89=E3=82=B5?= =?utf8?q?=E3=83=BC=E3=83=93=E3=82=B9=E3=81=AB=E4=BD=BF=E7=94=A8=E3=81=99?= =?utf8?q?=E3=82=8B=E3=82=A4=E3=83=B3=E3=82=BF=E3=83=95=E3=82=A7=E3=83=BC?= =?utf8?q?=E3=82=B9=E3=82=92=E8=A8=AD=E8=A8=88=E3=81=97=E7=9B=B4=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * IMultimediaShareService から IMediaUploadService に変更 * 複数枚のメディアをアップロード可能なサービスを考慮 * URLのために確保する文字数を取得出来るようにした --- OpenTween/Api/TwitterConfiguration.cs | 2 +- OpenTween/Connection/IMediaUploadService.cs | 84 ++++++ OpenTween/Connection/IMultimediaShareService.cs | 37 --- OpenTween/Connection/Imgur.cs | 183 +++++++------ OpenTween/Connection/TwipplePhoto.cs | 210 +++++++-------- OpenTween/Connection/TwitPic.cs | 330 +++++++++++------------- OpenTween/Connection/TwitterPhoto.cs | 205 ++++++--------- OpenTween/Connection/imgly.cs | 306 ++++++++++------------ OpenTween/Connection/yfrog.cs | 306 ++++++++++------------ OpenTween/MediaSelector.cs | 79 +++--- OpenTween/OpenTween.csproj | 2 +- OpenTween/Tween.cs | 26 +- 12 files changed, 844 insertions(+), 926 deletions(-) create mode 100644 OpenTween/Connection/IMediaUploadService.cs delete mode 100644 OpenTween/Connection/IMultimediaShareService.cs diff --git a/OpenTween/Api/TwitterConfiguration.cs b/OpenTween/Api/TwitterConfiguration.cs index f7d9e084..cdbb0407 100644 --- a/OpenTween/Api/TwitterConfiguration.cs +++ b/OpenTween/Api/TwitterConfiguration.cs @@ -41,7 +41,7 @@ namespace OpenTween.Api public int CharactersReservedPerMedia { get; set; } [DataMember(Name = "photo_size_limit")] - public int PhotoSizeLimit { get; set; } + public long PhotoSizeLimit { get; set; } [DataMember(Name = "photo_sizes")] public TwitterMediaSizes PhotoSizes { get; set; } diff --git a/OpenTween/Connection/IMediaUploadService.cs b/OpenTween/Connection/IMediaUploadService.cs new file mode 100644 index 00000000..b515d895 --- /dev/null +++ b/OpenTween/Connection/IMediaUploadService.cs @@ -0,0 +1,84 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2014 kim_upsilon (@kim_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 , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Api; + +namespace OpenTween.Connection +{ + /// + /// Twitterでの画像の共有に使用できるサービスを表すインタフェース + /// + public interface IMediaUploadService + { + /// + /// アップロード可能なメディアの最大枚数 + /// + int MaxMediaCount { get; } + + /// + /// アップロード可能なファイルの種類を表す文字列 (OpenFileDialog.Filter に使用) + /// + string SupportedFormatsStrForDialog { get; } + + /// + /// ファイルの拡張子からアップロード可能なフォーマットであるかを判定します + /// + /// アップロードするファイルの拡張子 (ピリオドを含む) + bool CheckFileExtension(string fileExtension); + + /// + /// ファイルサイズがアップロード可能な範囲内であるかを判定します + /// + /// アップロードするファイルの拡張子 (ピリオドを含む) + /// アップロードするファイルのサイズ (バイト単位) + bool CheckFileSize(string fileExtension, long fileSize); + + /// + /// アップロード可能なファイルサイズの上限を返します + /// + /// アップロードするファイルの拡張子 (ピリオドを含む) + /// ファイルサイズの上限 (バイト単位, nullの場合は上限なし) + long? GetMaxFileSize(string fileExtension); + + /// + /// メディアのアップロードとツイートの投稿を行います + /// + /// + Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths); + + /// + /// 画像URLのために確保する必要のある文字数を返します + /// + /// アップロードするメディアの個数 + int GetReservedTextLength(int mediaCount); + + /// + /// IMediaUploadService で使用する /help/configuration.json の値を更新します + /// + void UpdateTwitterConfiguration(TwitterConfiguration config); + } +} diff --git a/OpenTween/Connection/IMultimediaShareService.cs b/OpenTween/Connection/IMultimediaShareService.cs deleted file mode 100644 index fe8b039f..00000000 --- a/OpenTween/Connection/IMultimediaShareService.cs +++ /dev/null @@ -1,37 +0,0 @@ -// OpenTween - Client of Twitter -// Copyright (c) 2011 kiri_feather (@kiri_feather) -// (c) 2011 Egtra (@egtra) -// 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 , or write to -// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, -// Boston, MA 02110-1301, USA. - -namespace OpenTween -{ - public interface IMultimediaShareService - { - string Upload(ref string filePath, - ref string message, - long? reply_to); - bool CheckValidExtension(string ext) ; - string GetFileOpenDialogFilter(); - MyCommon.UploadFileType GetFileType(string ext); - bool IsSupportedFileType(MyCommon.UploadFileType type); - bool CheckValidFilesize(string ext, long fileSize); - bool Configuration(string key, object value); - } -} diff --git a/OpenTween/Connection/Imgur.cs b/OpenTween/Connection/Imgur.cs index 9b018346..a01c82ae 100644 --- a/OpenTween/Connection/Imgur.cs +++ b/OpenTween/Connection/Imgur.cs @@ -21,16 +21,18 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; -using System.Net; -using System.IO; +using System.Threading.Tasks; using System.Xml.Linq; -using System.Xml; +using OpenTween.Api; namespace OpenTween.Connection { - public class Imgur : HttpConnectionOAuth, IMultimediaShareService + public class Imgur : IMediaUploadService { private readonly static long MaxFileSize = 10L * 1024 * 1024; private readonly static Uri UploadEndpoint = new Uri("https://api.imgur.com/3/image.xml"); @@ -48,132 +50,123 @@ namespace OpenTween.Connection ".xcf", }; - private readonly Twitter _twitter; + private readonly Twitter twitter; + private TwitterConfiguration twitterConfig; - public Imgur(Twitter tw) + public Imgur(Twitter tw, TwitterConfiguration twitterConfig) { - this._twitter = tw; - - Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, - tw.AccessToken, tw.AccessTokenSecret, - "", ""); + this.twitter = tw; + this.twitterConfig = twitterConfig; } - protected override void AppendOAuthInfo(HttpWebRequest webRequest, Dictionary query, string token, string tokenSecret) + public int MaxMediaCount { - webRequest.Headers[HttpRequestHeader.Authorization] = - string.Format("Client-ID {0}", ApplicationSettings.ImgurClientID); + get { return 1; } } - public string Upload(ref string filePath, ref string message, long? reply_to) + public string SupportedFormatsStrForDialog { - if (!File.Exists(filePath)) - return "Err:File isn't exists."; - - var mediaFile = new FileInfo(filePath); - var content = ""; - HttpStatusCode result; - try - { - result = this.UploadFile(mediaFile, message, ref content); - } - catch (Exception ex) + get { - return "Err:" + ex.Message; - } + var formats = new StringBuilder(); - if (result != HttpStatusCode.OK) - { - return "Err:" + result; - } + foreach (var extension in SupportedExtensions) + formats.AppendFormat("*{0};", extension); - var imageUrl = ""; - try - { - var xdoc = XDocument.Parse(content); - var image = xdoc.Element("data"); - if (image.Attribute("success").Value != "1") - { - return "APIErr:" + image.Attribute("status").Value; - } - imageUrl = image.Element("link").Value; - } - catch (XmlException ex) - { - return "XmlErr:" + ex.Message; + return "Image Files(" + formats + ")|" + formats; } + } - filePath = ""; - if (message == null) - message = ""; + public bool CheckFileExtension(string fileExtension) + { + return SupportedExtensions.Contains(fileExtension, StringComparer.OrdinalIgnoreCase); + } - // Post to twitter - if (message.Length + AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia + 1 > 140) - { - message = message.Substring(0, 140 - AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia - 1) + " " + imageUrl; - } - else - { - message += " " + imageUrl; - } - return _twitter.PostStatus(message, reply_to); + public bool CheckFileSize(string fileExtension, long fileSize) + { + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; + } + + public long? GetMaxFileSize(string fileExtension) + { + return MaxFileSize; } - private HttpStatusCode UploadFile(FileInfo mediaFile, string message, ref string content) + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) { - if (!CheckValidExtension(mediaFile.Extension)) - throw new ArgumentException("Service don't support this filetype", "mediaFile"); - if (!CheckValidFilesize(mediaFile.Extension, mediaFile.Length)) - throw new ArgumentException("File is too large", "mediaFile"); + if (filePaths.Length != 1) + throw new ArgumentOutOfRangeException("filePaths"); + + var file = new FileInfo(filePaths[0]); + + if (!file.Exists) + throw new ArgumentException("File isn't exists.", "filePaths[0]"); - var param = new Dictionary + XDocument xml; + try { - {"title", message}, - }; - var binary = new List> + xml = await this.UploadFileAsync(file, text) + .ConfigureAwait(false); + } + catch (HttpRequestException ex) { - new KeyValuePair("image", mediaFile) - }; - this.InstanceTimeout = 60000; + throw new WebApiException("Err:" + ex.Message, ex); + } - return this.GetContent(PostMethod, UploadEndpoint, param, binary, ref content, null, null); - } + var imageElm = xml.Element("data"); - public bool CheckValidExtension(string ext) - { - return SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - } + if (imageElm.Attribute("success").Value != "1") + throw new WebApiException("Err:" + imageElm.Attribute("status").Value); - public string GetFileOpenDialogFilter() - { - var formats = new StringBuilder(); + var imageUrl = imageElm.Element("link").Value; - foreach (var extension in SupportedExtensions) - formats.AppendFormat("*{0};", extension); + var textWithImageUrl = text + " " + imageUrl.Trim(); - return "Image Files(" + formats + ")|" + formats; + await Task.Run(() => this.twitter.PostStatus(textWithImageUrl, inReplyToStatusId)) + .ConfigureAwait(false); } - public MyCommon.UploadFileType GetFileType(string ext) + public int GetReservedTextLength(int mediaCount) { - return this.CheckValidExtension(ext) - ? MyCommon.UploadFileType.Picture - : MyCommon.UploadFileType.Invalid; + return this.twitterConfig.ShortUrlLength; } - public bool IsSupportedFileType(MyCommon.UploadFileType type) + public void UpdateTwitterConfiguration(TwitterConfiguration config) { - return type == MyCommon.UploadFileType.Picture; + this.twitterConfig = config; } - public bool CheckValidFilesize(string ext, long fileSize) + public async Task UploadFileAsync(FileInfo file, string title) { - return CheckValidExtension(ext) && fileSize <= MaxFileSize; - } + using (var content = new MultipartFormDataContent()) + using (var fileStream = file.OpenRead()) + using (var fileContent = new StreamContent(fileStream)) + using (var titleContent = new StringContent(title)) + { + content.Add(fileContent, "image", file.Name); + content.Add(titleContent, "title"); - public bool Configuration(string key, object value) - { - throw new NotImplementedException(); + using (var http = MyCommon.CreateHttpClient()) + using (var request = new HttpRequestMessage(HttpMethod.Post, UploadEndpoint)) + { + http.Timeout = TimeSpan.FromMinutes(1); + + request.Headers.Authorization = + new AuthenticationHeaderValue("Client-ID", ApplicationSettings.ImgurClientID); + request.Content = content; + + using (var response = await http.SendAsync(request).ConfigureAwait(false)) + { + response.EnsureSuccessStatusCode(); + + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + return XDocument.Load(stream); + } + } + } + } } } } diff --git a/OpenTween/Connection/TwipplePhoto.cs b/OpenTween/Connection/TwipplePhoto.cs index c330b3b7..e399867c 100644 --- a/OpenTween/Connection/TwipplePhoto.cs +++ b/OpenTween/Connection/TwipplePhoto.cs @@ -1,5 +1,6 @@ // OpenTween - Client of Twitter // Copyright (c) 2013 ANIKITI (@anikiti07) +// (c) 2014 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. @@ -24,157 +25,158 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Threading.Tasks; +using System.Windows.Forms; using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using OpenTween.Api; namespace OpenTween.Connection { - public sealed class TwipplePhoto : HttpConnectionOAuthEcho, - IMultimediaShareService + public sealed class TwipplePhoto : IMediaUploadService { - private const long MaxFileSize = 4 * 1024 * 1024; - - private readonly Twitter _twitter; - private readonly Uri _twipplePhotoUploadUri = new Uri("http://p.twipple.jp/api/upload2"); - private readonly IEnumerable _supportedPictureExtensions = new[] + private static readonly long MaxFileSize = 4L * 1024 * 1024; + private static readonly IEnumerable SupportedPictureExtensions = new[] { ".gif", ".jpg", - ".png" + ".png", }; + private readonly Twitter twitter; + private readonly TwippleApi twippleApi; + + private TwitterConfiguration twitterConfig; + #region Constructors - public TwipplePhoto(Twitter twitter) - : base(new Uri("http://api.twitter.com/"), new Uri("https://api.twitter.com/1.1/account/verify_credentials.json")) + public TwipplePhoto(Twitter twitter, TwitterConfiguration twitterConfig) { if (twitter == null) throw new ArgumentNullException("twitter"); + if (twitterConfig == null) + throw new ArgumentNullException("config"); + + this.twitter = twitter; + this.twitterConfig = twitterConfig; - _twitter = twitter; - Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, - _twitter.AccessToken, _twitter.AccessTokenSecret, - "", ""); + this.twippleApi = new TwippleApi(twitter.AccessToken, twitter.AccessTokenSecret); } #endregion - #region IMultimediaShareService Members - - #region Upload Methods - - public string Upload(ref string filePath, ref string message, long? reply_to) + public int MaxMediaCount { - if (!File.Exists(filePath)) - return "Err:File isn't exists."; - - var mediaFile = new FileInfo(filePath); - var content = ""; - HttpStatusCode result; - try - { - result = UploadFile(mediaFile, ref content); - } - catch (Exception ex) - { - return "Err:" + ex.Message; - } + get { return 1; } + } - var imageUrl = ""; - if (result == HttpStatusCode.OK) + public string SupportedFormatsStrForDialog + { + get { - try + var filterFormatExtensions = ""; + foreach (var pictureExtension in SupportedPictureExtensions) { - var xdoc = new XmlDocument(); - xdoc.LoadXml(content); - var urlNode = xdoc.SelectSingleNode("/rsp/mediaurl"); - if (urlNode != null) - { - imageUrl = urlNode.InnerText; - } + filterFormatExtensions += '*'; + filterFormatExtensions += pictureExtension; + filterFormatExtensions += ';'; } - catch (XmlException ex) - { - return "XmlErr:" + ex.Message; - } - } - else - { - return "Err:" + result; + return "Image Files(" + filterFormatExtensions + ")|" + filterFormatExtensions; } - - filePath = ""; - if (message == null) - message = ""; - - // Post to twitter - if (message.Length + AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia + 1 > 140) - { - message = message.Substring(0, 140 - AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia - 1) + " " + imageUrl; - } - else - { - message += " " + imageUrl; - } - return _twitter.PostStatus(message, reply_to); } - private HttpStatusCode UploadFile(FileInfo mediaFile, ref string content) + public bool CheckFileExtension(string fileExtension) { - if (!CheckValidExtension(mediaFile.Extension)) - throw new ArgumentException("Service don't support this filetype", "mediaFile"); - if (!CheckValidFilesize(mediaFile.Extension, mediaFile.Length)) - throw new ArgumentException("File is too large", "mediaFile"); - - var binaly = new List> - { - new KeyValuePair("media", mediaFile) - }; - InstanceTimeout = 60000; - - return GetContent(PostMethod, _twipplePhotoUploadUri, null, binaly, ref content, null, null); + return SupportedPictureExtensions.Contains(fileExtension, StringComparer.OrdinalIgnoreCase); } - #endregion - - public bool CheckValidExtension(string ext) + public bool CheckFileSize(string fileExtension, long fileSize) { - return _supportedPictureExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; } - public string GetFileOpenDialogFilter() + public long? GetMaxFileSize(string fileExtension) { - string filterFormatExtensions = ""; - foreach (var pictureExtension in _supportedPictureExtensions) - { - filterFormatExtensions += '*'; - filterFormatExtensions += pictureExtension; - filterFormatExtensions += ';'; - } - return "Image Files(" + filterFormatExtensions + ")|" + filterFormatExtensions; + return MaxFileSize; } - public MyCommon.UploadFileType GetFileType(string extension) + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) { - return CheckValidExtension(extension) - ? MyCommon.UploadFileType.Picture - : MyCommon.UploadFileType.Invalid; + if (filePaths.Length != 1) + throw new ArgumentOutOfRangeException("filePaths"); + + var file = new FileInfo(filePaths[0]); + + if (!file.Exists) + throw new ArgumentException("Err:File isn't exists.", "filePaths[0]"); + + var xml = await this.twippleApi.UploadFileAsync(file) + .ConfigureAwait(false); + + var imageUrlElm = xml.XPathSelectElement("/rsp/mediaurl"); + if (imageUrlElm == null) + throw new WebApiException("Invalid API response", xml.ToString()); + + var textWithImageUrl = text + " " + imageUrlElm.Value.Trim(); + + await Task.Run(() => this.twitter.PostStatus(textWithImageUrl, inReplyToStatusId)) + .ConfigureAwait(false); } - public bool IsSupportedFileType(MyCommon.UploadFileType type) + public int GetReservedTextLength(int mediaCount) { - return type == MyCommon.UploadFileType.Picture; + return this.twitterConfig.ShortUrlLength; } - public bool CheckValidFilesize(string extension, long fileSize) + public void UpdateTwitterConfiguration(TwitterConfiguration config) { - return CheckValidExtension(extension) && fileSize <= MaxFileSize; + this.twitterConfig = config; } - public bool Configuration(string key, object value) + public class TwippleApi : HttpConnectionOAuthEcho { - throw new NotImplementedException(); - } + private static readonly Uri UploadEndpoint = new Uri("http://p.twipple.jp/api/upload2"); - #endregion + public TwippleApi(string twitterAccessToken, string twitterAccessTokenSecret) + : base(new Uri("http://api.twitter.com/"), new Uri("https://api.twitter.com/1.1/account/verify_credentials.json")) + { + this.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, + twitterAccessToken, twitterAccessTokenSecret, "", ""); + + this.InstanceTimeout = 60000; + } + + /// + /// 画像のアップロードを行います + /// + /// + /// + public async Task UploadFileAsync(FileInfo file) + { + // 参照: http://p.twipple.jp/wiki/API_Upload2/ja + + var param = new Dictionary + { + {"upload_from", Application.ProductName}, + }; + var paramFiles = new List> + { + new KeyValuePair("media", file), + }; + var response = ""; + + var uploadTask = Task.Run(() => this.GetContent(HttpConnection.PostMethod, + UploadEndpoint, param, paramFiles, ref response, null, null)); + + var ret = await uploadTask.ConfigureAwait(false); + + if (ret != HttpStatusCode.OK) + throw new WebApiException("Err:" + ret, response); + + return XDocument.Parse(response); + } + } } } \ No newline at end of file diff --git a/OpenTween/Connection/TwitPic.cs b/OpenTween/Connection/TwitPic.cs index f8a0b6a6..f5a05422 100644 --- a/OpenTween/Connection/TwitPic.cs +++ b/OpenTween/Connection/TwitPic.cs @@ -5,195 +5,173 @@ // (c) 2010-2011 anis774 (@anis774) // (c) 2010-2011 fantasticswallow (@f_swallow) // (c) 2011 spinor (@tplantd) +// (c) 2014 kim_upsilon (@kim_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. -// +// for more details. +// // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using HttpConnectionOAuthEcho = OpenTween.HttpConnectionOAuthEcho; -using IMultimediaShareService = OpenTween.IMultimediaShareService; -using FileInfo = System.IO.FileInfo; -using NotSupportedException = System.NotSupportedException; -using HttpStatusCode = System.Net.HttpStatusCode; -using Exception = System.Exception; -using XmlDocument = System.Xml.XmlDocument; -using XmlException = System.Xml.XmlException; -using ArgumentException = System.ArgumentException; -using System.Collections.Generic; // for Dictionary, List, KeyValuePair -using UploadFileType = OpenTween.MyCommon.UploadFileType; -using Uri = System.Uri; -using Array = System.Array; - -namespace OpenTween +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using OpenTween.Api; + +namespace OpenTween.Connection { - public class TwitPic : HttpConnectionOAuthEcho, IMultimediaShareService - { - private string[] pictureExt = new string[] { ".jpg", ".jpeg", ".gif", ".png" }; - - private string[] multimediaExt = new string[] { ".avi", ".wmv", ".flv", ".m4v", ".mov", ".mp4", ".rm", ".mpeg", ".mpg", ".3gp", ".3g2" }; - - private const long MaxFileSize = 10 * 1024 * 1024; // Image only - // Multimedia filesize limit unknown. But length limit is 1:30. - - private Twitter tw; - - public string Upload( ref string filePath, ref string message, long? reply_to ) - { - if ( string.IsNullOrEmpty( filePath ) ) - return "Err:File isn't specified."; - if ( string.IsNullOrEmpty( message ) ) - message = ""; - - FileInfo mediaFile; - try - { - mediaFile = new FileInfo( filePath ); - } - catch ( NotSupportedException ex ) - { - return "Err:" + ex.Message; - } - if ( mediaFile == null || !mediaFile.Exists ) - return "Err:File isn't exists."; - - string content = ""; - HttpStatusCode ret; - // TwitPicへの投稿 - try - { - ret = this.UploadFile( mediaFile, message, ref content ); - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - string url = ""; - if ( ret == HttpStatusCode.OK ) - { - XmlDocument xd = new XmlDocument(); - try - { - xd.LoadXml( content ); - // URLの取得 - url = xd.SelectSingleNode( "/image/url" ).InnerText; - } - catch ( XmlException ex ) - { - return "Err:" + ex.Message; - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - } - else - return "Err:" + ret.ToString(); - - // アップロードまでは成功 - filePath = ""; - if ( string.IsNullOrEmpty( message ) ) - message = ""; - if ( string.IsNullOrEmpty( url ) ) - url = ""; - // Twitterへの投稿 - // 投稿メッセージの再構成 - if ( message.Length + AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia + 1 > 140 ) - message = message.Substring( 0, 140 - AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia - 1 ) + " " + url; - else - message += " " + url; - - return tw.PostStatus( message, reply_to ); - } - - private HttpStatusCode UploadFile( FileInfo mediaFile, string message, ref string content ) - { - // Message必須 - if ( string.IsNullOrEmpty( message ) ) - message = ""; - // Check filetype and size(Max 5MB) - if ( !this.CheckValidExtension( mediaFile.Extension ) ) - throw new ArgumentException( "Service don't support this filetype." ); - if ( !this.CheckValidFilesize( mediaFile.Extension, mediaFile.Length ) ) - throw new ArgumentException( "File is too large." ); - - Dictionary< string, string > param = new Dictionary< string, string >(); - param.Add( "key", ApplicationSettings.TwitpicApiKey ); - param.Add( "message", message ); - List< KeyValuePair< string, FileInfo > > binary = new List< KeyValuePair< string, FileInfo > >(); - binary.Add( new KeyValuePair< string, FileInfo >( "media", mediaFile ) ); - if ( this.GetFileType( mediaFile.Extension ) == UploadFileType.Picture ) - this.InstanceTimeout = 60000; // タイムアウト60秒 - else - this.InstanceTimeout = 120000; - - return this.GetContent( HttpConnection.PostMethod, new Uri( "http://api.twitpic.com/2/upload.xml" ), param, binary, ref content, null, null ); - } - - public bool CheckValidExtension( string ext ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return true; - if ( Array.IndexOf( this.multimediaExt, ext.ToLower() ) > -1 ) - return true; - - return false; - } - - public string GetFileOpenDialogFilter() - { - return "Image Files(*" + string.Join( ";*", this.pictureExt ) + ")|*" + string.Join( ";*", this.pictureExt ) - + "|Videos(*" + string.Join( ";*", this.multimediaExt ) + ")|*" + string.Join( ";*", this.multimediaExt ); - } - - public UploadFileType GetFileType( string ext ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return UploadFileType.Picture; - if ( Array.IndexOf( this.multimediaExt, ext.ToLower() ) > -1 ) - return UploadFileType.MultiMedia; - - return UploadFileType.Invalid; - } - - public bool IsSupportedFileType( UploadFileType type ) - { - return !type.Equals( UploadFileType.Invalid ); - } - - public bool CheckValidFilesize( string ext, long fileSize ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return fileSize <= TwitPic.MaxFileSize; - if ( Array.IndexOf( this.multimediaExt, ext.ToLower() ) > -1 ) - return true; // Multimedia : no check - - return false; - } - - public bool Configuration( string key, object value ) - { - return true; - } - - public TwitPic( Twitter twitter ) - : base( new Uri( "http://api.twitter.com/" ), new Uri( "https://api.twitter.com/1.1/account/verify_credentials.json" ) ) - { - this.tw = twitter; - this.Initialize( ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, tw.AccessToken, tw.AccessTokenSecret, "", "" ); - } - } + public class TwitPic : IMediaUploadService + { + private readonly string[] pictureExt = new[] { ".jpg", ".jpeg", ".gif", ".png" }; + + private readonly string[] multimediaExt = new[] { ".avi", ".wmv", ".flv", ".m4v", ".mov", ".mp4", ".rm", ".mpeg", ".mpg", ".3gp", ".3g2" }; + + private readonly long MaxFileSize = 10L * 1024 * 1024; // Image only + // Multimedia filesize limit unknown. But length limit is 1:30. + + private readonly Twitter tw; + private readonly TwitpicApi twitpicApi; + + private TwitterConfiguration twitterConfig; + + public TwitPic(Twitter twitter, TwitterConfiguration twitterConfig) + { + this.tw = twitter; + this.twitterConfig = twitterConfig; + + this.twitpicApi = new TwitpicApi(twitter.AccessToken, twitter.AccessTokenSecret); + } + + public int MaxMediaCount + { + get { return 1; } + } + + public string SupportedFormatsStrForDialog + { + get + { + return "Image Files(*" + string.Join(";*", this.pictureExt) + ")|*" + string.Join(";*", this.pictureExt) + + "|Videos(*" + string.Join(";*", this.multimediaExt) + ")|*" + string.Join(";*", this.multimediaExt); + } + } + + public bool CheckFileExtension(string fileExtension) + { + fileExtension = fileExtension.ToLower(); + + return this.pictureExt.Contains(fileExtension) || + this.multimediaExt.Contains(fileExtension); + } + + public bool CheckFileSize(string fileExtension, long fileSize) + { + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; + } + + public long? GetMaxFileSize(string fileExtension) + { + if (this.multimediaExt.Contains(fileExtension)) + return null; // Multimedia : no check + + return MaxFileSize; + } + + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) + { + if (filePaths.Length != 1) + throw new ArgumentOutOfRangeException("filePaths"); + + var file = new FileInfo(filePaths[0]); + + if (!file.Exists) + throw new ArgumentException("Err:File isn't exists.", "filePaths[0]"); + + var xml = await this.twitpicApi.UploadFileAsync(file, text) + .ConfigureAwait(false); + + var imageUrlElm = xml.XPathSelectElement("/image/url"); + if (imageUrlElm == null) + throw new WebApiException("Invalid API response", xml.ToString()); + + var textWithImageUrl = text + " " + imageUrlElm.Value.Trim(); + + await Task.Run(() => this.tw.PostStatus(textWithImageUrl, inReplyToStatusId)) + .ConfigureAwait(false); + } + + public int GetReservedTextLength(int mediaCount) + { + return this.twitterConfig.ShortUrlLength; + } + + public void UpdateTwitterConfiguration(TwitterConfiguration config) + { + this.twitterConfig = config; + } + + public class TwitpicApi : HttpConnectionOAuthEcho + { + private static readonly Uri UploadEndpoint = new Uri("http://api.twitpic.com/2/upload.xml"); + + public TwitpicApi(string twitterAccessToken, string twitterAccessTokenSecret) + : base(new Uri("http://api.twitter.com/"), new Uri("https://api.twitter.com/1.1/account/verify_credentials.json")) + { + this.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, + twitterAccessToken, twitterAccessTokenSecret, "", ""); + + this.InstanceTimeout = 120000; + } + + /// + /// 画像のアップロードを行います + /// + /// + /// + public async Task UploadFileAsync(FileInfo file, string message) + { + // 参照: http://dev.twitpic.com/docs/2/upload/ + + var param = new Dictionary + { + {"key", ApplicationSettings.TwitpicApiKey}, + {"message", message}, + }; + var paramFiles = new List> + { + new KeyValuePair("media", file), + }; + var response = ""; + + var uploadTask = Task.Run(() => this.GetContent(HttpConnection.PostMethod, + UploadEndpoint, param, paramFiles, ref response, null, null)); + + var ret = await uploadTask.ConfigureAwait(false); + + if (ret != HttpStatusCode.OK) + throw new WebApiException("Err:" + ret, response); + + return XDocument.Parse(response); + } + } + } } diff --git a/OpenTween/Connection/TwitterPhoto.cs b/OpenTween/Connection/TwitterPhoto.cs index 67e4bcfd..1790bd7c 100644 --- a/OpenTween/Connection/TwitterPhoto.cs +++ b/OpenTween/Connection/TwitterPhoto.cs @@ -5,139 +5,81 @@ // (c) 2010-2011 anis774 (@anis774) // (c) 2010-2011 fantasticswallow (@f_swallow) // (c) 2011 spinor (@tplantd) +// (c) 2014 kim_upsilon (@kim_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. -// +// for more details. +// // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using IMultimediaShareService = OpenTween.IMultimediaShareService; -using Array = System.Array; -using Convert = System.Convert; -using Exception = System.Exception; -using UploadFileType = OpenTween.MyCommon.UploadFileType; -using MyCommon = OpenTween.MyCommon; -using FileInfo = System.IO.FileInfo; -using NotSupportedException = System.NotSupportedException; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using OpenTween.Api; -namespace OpenTween +namespace OpenTween.Connection { - public class TwitterPhoto : IMultimediaShareService - { - private string[] pictureExt = new string[] { ".jpg", ".jpeg", ".gif", ".png" }; - - private const long MaxfilesizeDefault = 3145728; - - // help/configurationにより取得されコンストラクタへ渡される - private long _MaxFileSize = 3145728; - - private Twitter tw; - - public bool CheckValidExtension( string ext ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return true; - - return false; - } - - public bool CheckValidFilesize( string ext, long fileSize ) - { - if ( this.CheckValidExtension( ext ) ) - return fileSize <= this._MaxFileSize; - - return false; - } - - public bool Configuration( string key, object value ) - { - if ( key == "MaxUploadFilesize" ) - { - long val; - try - { - val = Convert.ToInt64( value ); - if ( val > 0 ) - this._MaxFileSize = val; - else - this._MaxFileSize = TwitterPhoto.MaxfilesizeDefault; - } - catch ( Exception ) - { - this._MaxFileSize = TwitterPhoto.MaxfilesizeDefault; - return false; // error - } - return true; // 正常に設定終了 - } - return true; // 設定項目がない場合はとりあえずエラー扱いにしない - } - - public string GetFileOpenDialogFilter() - { - return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; - } - - public UploadFileType GetFileType( string ext ) - { - if ( this.CheckValidExtension( ext ) ) - return UploadFileType.Picture; - - return UploadFileType.Invalid; - } - - public bool IsSupportedFileType( UploadFileType type ) - { - return type.Equals( UploadFileType.Picture ); - } - - public string Upload( ref string filePath, ref string message, long? reply_to ) - { - if ( string.IsNullOrEmpty( filePath ) ) - return "Err:File isn't specified."; - - if ( string.IsNullOrEmpty( message ) ) - message = ""; - - FileInfo mediaFile; - try - { - mediaFile = new FileInfo( filePath ); - } - catch ( NotSupportedException ex ) - { - return "Err:" + ex.Message; - } - - if ( !mediaFile.Exists ) - return "Err:File isn't exists."; - - if ( MyCommon.IsAnimatedGif( filePath ) ) - return "Err:Don't support animatedGIF."; - - return tw.PostStatusWithMedia( message, reply_to, mediaFile ); - } - - public string Upload(ref string[] filePaths, ref string message, long? reply_to) + public class TwitterPhoto : IMediaUploadService + { + private readonly string[] pictureExt = new[] { ".jpg", ".jpeg", ".gif", ".png" }; + + private readonly Twitter tw; + private TwitterConfiguration twitterConfig; + + public TwitterPhoto(Twitter twitter, TwitterConfiguration twitterConfig) { - if (filePaths == null || filePaths.Length == 0 || string.IsNullOrEmpty(filePaths[0])) - return "Err:File isn't specified."; + this.tw = twitter; + this.twitterConfig = twitterConfig; + } - if (string.IsNullOrEmpty(message)) - message = ""; + public int MaxMediaCount + { + get { return 4; } + } + + public string SupportedFormatsStrForDialog + { + get + { + return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; + } + } + + public bool CheckFileExtension(string fileExtension) + { + return this.pictureExt.Contains(fileExtension.ToLower()); + } + + public bool CheckFileSize(string fileExtension, long fileSize) + { + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; + } + + public long? GetMaxFileSize(string fileExtension) + { + return this.twitterConfig.PhotoSizeLimit; + } + + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) + { + if (filePaths == null || filePaths.Length == 0 || string.IsNullOrEmpty(filePaths[0])) + throw new ArgumentException("Err:File isn't specified.", "filePaths"); var mediaFiles = new List(); @@ -145,31 +87,30 @@ namespace OpenTween { if (string.IsNullOrEmpty(filePath)) continue; - FileInfo mediaFile; - try - { - mediaFile = new FileInfo(filePath); - } - catch (NotSupportedException ex) - { - return "Err:" + ex.Message; - } + var mediaFile = new FileInfo(filePath); if (!mediaFile.Exists) - return "Err:File isn't exists."; + throw new ArgumentException("Err:File isn't exists.", "filePaths"); if (MyCommon.IsAnimatedGif(filePath)) - return "Err:Don't support animatedGIF."; + throw new ArgumentException("Err:Don't support animatedGIF.", "filePaths"); mediaFiles.Add(mediaFile); } - return tw.PostStatusWithMultipleMedia(message, reply_to, mediaFiles); + await Task.Run(() => this.tw.PostStatusWithMultipleMedia(text, inReplyToStatusId, mediaFiles)) + .ConfigureAwait(false); + } + + public int GetReservedTextLength(int mediaCount) + { + // 枚数に関わらず文字数は一定 + return this.twitterConfig.ShortUrlLength; + } + + public void UpdateTwitterConfiguration(TwitterConfiguration config) + { + this.twitterConfig = config; } - - public TwitterPhoto(Twitter twitter) - { - this.tw = twitter; - } - } + } } diff --git a/OpenTween/Connection/imgly.cs b/OpenTween/Connection/imgly.cs index 54500199..d25175e2 100644 --- a/OpenTween/Connection/imgly.cs +++ b/OpenTween/Connection/imgly.cs @@ -5,183 +5,161 @@ // (c) 2010-2011 anis774 (@anis774) // (c) 2010-2011 fantasticswallow (@f_swallow) // (c) 2011 spinor (@tplantd) +// (c) 2014 kim_upsilon (@kim_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. -// +// for more details. +// // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using HttpConnectionOAuthEcho = OpenTween.HttpConnectionOAuthEcho; -using IMultimediaShareService = OpenTween.IMultimediaShareService; -using FileInfo = System.IO.FileInfo; -using NotSupportedException = System.NotSupportedException; -using HttpStatusCode = System.Net.HttpStatusCode; -using Exception = System.Exception; -using XmlDocument = System.Xml.XmlDocument; -using XmlException = System.Xml.XmlException; -using ArgumentException = System.ArgumentException; -using System.Collections.Generic; // for Dictionary, List, KeyValuePair -using Uri = System.Uri; -using Array = System.Array; -using UploadFileType = OpenTween.MyCommon.UploadFileType; - -namespace OpenTween +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using OpenTween.Api; + +namespace OpenTween.Connection { - public class imgly : HttpConnectionOAuthEcho, IMultimediaShareService - { - private string[] pictureExt = new string[] { ".jpg", ".jpeg", ".gif", ".png" }; - - private const long MaxFileSize = 4 * 1024 * 1024; - - private Twitter tw; - - public string Upload( ref string filePath, ref string message, long? reply_to ) - { - if ( string.IsNullOrEmpty( filePath ) ) - return "Err:File isn't specified."; - if ( string.IsNullOrEmpty( message ) ) - message = ""; - - FileInfo mediaFile; - try - { - mediaFile = new FileInfo( filePath ); - } - catch ( NotSupportedException ex ) - { - return "Err:" + ex.Message; - } - if ( mediaFile == null || !mediaFile.Exists ) - return "Err:File isn't exists."; - - string content = ""; - HttpStatusCode ret; - // img.lyへの投稿 - try - { - ret = this.UploadFile( mediaFile, message, ref content ); - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - - string url = ""; - if ( ret == HttpStatusCode.OK ) - { - XmlDocument xd = new XmlDocument(); - try - { - xd.LoadXml( content ); - // URLの取得 - url = xd.SelectSingleNode( "/image/url" ).InnerText; - } - catch ( XmlException ex ) - { - return "Err:" + ex.Message; - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - } - else - { - return "Err:" + ret.ToString(); - } - // アップロードまでは成功 - filePath = ""; - if ( string.IsNullOrEmpty( url ) ) - url = ""; - // Twitterへの投稿 - // 投稿メッセージの再構成 - if ( string.IsNullOrEmpty( message ) ) - message = ""; - if ( message.Length + AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia + 1 > 140 ) - message = message.Substring( 0, 140 - AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia - 1 ) + " " + url; - else - message += " " + url; - - return tw.PostStatus( message, reply_to ); - } - - private HttpStatusCode UploadFile( FileInfo mediaFile, string message, ref string content ) - { - // Message必須 - if ( string.IsNullOrEmpty( message ) ) - message = ""; - // Check filetype and size(Max 4MB) - if ( !this.CheckValidExtension( mediaFile.Extension ) ) - throw new ArgumentException( "Service don't support this filetype." ); - if ( !this.CheckValidFilesize( mediaFile.Extension, mediaFile.Length ) ) - throw new ArgumentException( "File is too large." ); - - Dictionary< string, string > param = new Dictionary< string, string >(); - param.Add( "message", message ); - List< KeyValuePair< string, FileInfo > > binary = new List< KeyValuePair< string, FileInfo > >(); - binary.Add( new KeyValuePair< string, FileInfo >( "media", mediaFile ) ); - this.InstanceTimeout = 60000; // タイムアウト60秒 - - return this.GetContent( HttpConnection.PostMethod, new Uri( "http://img.ly/api/2/upload.xml" ), param, binary, ref content, null, null ); - } - - public bool CheckValidExtension( string ext ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return true; - - return false; - } - - public string GetFileOpenDialogFilter() - { - return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; - } - - public UploadFileType GetFileType( string ext ) - { - if ( this.CheckValidExtension( ext ) ) - return UploadFileType.Picture; - - return UploadFileType.Invalid; - } - - public bool IsSupportedFileType( UploadFileType type ) - { - return type.Equals( UploadFileType.Picture ); - } - - public bool CheckValidFilesize( string ext, long fileSize ) - { - if ( this.CheckValidExtension( ext ) ) - return fileSize <= imgly.MaxFileSize; - - return false; - } - - public bool Configuration( string key, object value ) - { - return true; - } - - public imgly( Twitter twitter ) - : base( new Uri( "http://api.twitter.com/" ), new Uri( "https://api.twitter.com/1.1/account/verify_credentials.json" ) ) - { - this.tw = twitter; - this.Initialize( ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, tw.AccessToken, tw.AccessTokenSecret, "", "" ); - } - } + public class imgly : IMediaUploadService + { + private readonly string[] pictureExt = new[] { ".jpg", ".jpeg", ".gif", ".png" }; + private readonly long MaxFileSize = 4L * 1024 * 1024; + + private readonly Twitter tw; + private readonly ImglyApi imglyApi; + + private TwitterConfiguration twitterConfig; + + public imgly(Twitter twitter, TwitterConfiguration twitterConfig) + { + this.tw = twitter; + this.twitterConfig = twitterConfig; + + this.imglyApi = new ImglyApi(twitter.AccessToken, twitter.AccessTokenSecret); + } + + public int MaxMediaCount + { + get { return 1; } + } + + public string SupportedFormatsStrForDialog + { + get + { + return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; + } + } + + public bool CheckFileExtension(string fileExtension) + { + return this.pictureExt.Contains(fileExtension.ToLower()); + } + + public bool CheckFileSize(string fileExtension, long fileSize) + { + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; + } + + public long? GetMaxFileSize(string fileExtension) + { + return MaxFileSize; + } + + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) + { + if (filePaths.Length != 1) + throw new ArgumentOutOfRangeException("filePaths"); + + var file = new FileInfo(filePaths[0]); + + if (!file.Exists) + throw new ArgumentException("Err:File isn't exists.", "filePaths[0]"); + + var xml = await this.imglyApi.UploadFileAsync(file, text) + .ConfigureAwait(false); + + var imageUrlElm = xml.XPathSelectElement("/image/url"); + if (imageUrlElm == null) + throw new WebApiException("Invalid API response", xml.ToString()); + + var textWithImageUrl = text + " " + imageUrlElm.Value.Trim(); + + await Task.Run(() => this.tw.PostStatus(textWithImageUrl, inReplyToStatusId)) + .ConfigureAwait(false); + } + + public int GetReservedTextLength(int mediaCount) + { + return this.twitterConfig.ShortUrlLength; + } + + public void UpdateTwitterConfiguration(TwitterConfiguration config) + { + this.twitterConfig = config; + } + + public class ImglyApi : HttpConnectionOAuthEcho + { + private static readonly Uri UploadEndpoint = new Uri("http://img.ly/api/2/upload.xml"); + + public ImglyApi(string twitterAccessToken, string twitterAccessTokenSecret) + : base(new Uri("http://api.twitter.com/"), new Uri("https://api.twitter.com/1.1/account/verify_credentials.json")) + { + this.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, + twitterAccessToken, twitterAccessTokenSecret, "", ""); + + this.InstanceTimeout = 60000; + } + + /// + /// 画像のアップロードを行います + /// + /// + /// + public async Task UploadFileAsync(FileInfo file, string message) + { + // 参照: http://img.ly/api + + var param = new Dictionary + { + {"message", message}, + }; + var paramFiles = new List> + { + new KeyValuePair("media", file), + }; + var response = ""; + + var uploadTask = Task.Run(() => this.GetContent(HttpConnection.PostMethod, + UploadEndpoint, param, paramFiles, ref response, null, null)); + + var ret = await uploadTask.ConfigureAwait(false); + + if (ret != HttpStatusCode.OK) + throw new WebApiException("Err:" + ret, response); + + return XDocument.Parse(response); + } + } + } } diff --git a/OpenTween/Connection/yfrog.cs b/OpenTween/Connection/yfrog.cs index ae18dca2..0c3947e5 100644 --- a/OpenTween/Connection/yfrog.cs +++ b/OpenTween/Connection/yfrog.cs @@ -5,183 +5,161 @@ // (c) 2010-2011 anis774 (@anis774) // (c) 2010-2011 fantasticswallow (@f_swallow) // (c) 2011 spinor (@tplantd) +// (c) 2014 kim_upsilon (@kim_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. -// +// for more details. +// // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using HttpConnectionOAuthEcho = OpenTween.HttpConnectionOAuthEcho; -using IMultimediaShareService = OpenTween.IMultimediaShareService; -using FileInfo = System.IO.FileInfo; -using NotSupportedException = System.NotSupportedException; -using HttpStatusCode = System.Net.HttpStatusCode; -using Exception = System.Exception; -using XmlDocument = System.Xml.XmlDocument; -using XmlException = System.Xml.XmlException; -using ArgumentException = System.ArgumentException; -using System.Collections.Generic; // for Dictionary, List, KeyValuePair -using Uri = System.Uri; -using Array = System.Array; -using UploadFileType = OpenTween.MyCommon.UploadFileType; - -namespace OpenTween +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using OpenTween.Api; + +namespace OpenTween.Connection { - public class yfrog : HttpConnectionOAuthEcho, IMultimediaShareService - { - private string[] pictureExt = new string[] { ".jpg", ".jpeg", ".gif", ".png" }; - - private const long MaxFileSize = 5 * 1024 * 1024; - - private Twitter tw; - - public string Upload( ref string filePath, ref string message, long? reply_to ) - { - if ( string.IsNullOrEmpty( filePath ) ) - return "Err:File isn't exists."; - if ( string.IsNullOrEmpty( message ) ) - message = ""; - - // FileInfo作成 - FileInfo mediaFile; - try - { - mediaFile = new FileInfo( filePath ); - } - catch ( NotSupportedException ex ) - { - return "Err:" + ex.Message; - } - if ( mediaFile == null || !mediaFile.Exists ) - return "Err:File isn't exists."; - - string content = ""; - HttpStatusCode ret; - // yfrogへの投稿 - try - { - ret = this.UploadFile( mediaFile, message, ref content ); - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - string url = ""; - if ( ret == HttpStatusCode.OK ) - { - XmlDocument xd = new XmlDocument(); - try - { - xd.LoadXml( content ); - // URLの取得 - url = xd.SelectSingleNode( "/rsp/mediaurl" ).InnerText; - } - catch ( XmlException ex ) - { - return "Err:" + ex.Message; - } - catch ( Exception ex ) - { - return "Err:" + ex.Message; - } - } - else - return "Err:" + ret.ToString(); - - if ( string.IsNullOrEmpty( url ) ) - url = ""; - // アップロードまでは成功 - filePath = ""; - // Twitterへの投稿 - // 投稿メッセージの再構成 - if ( string.IsNullOrEmpty( message ) ) - message = ""; - if ( message.Length + AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia + 1 > 140 ) - message = message.Substring(0, 140 - AppendSettingDialog.Instance.TwitterConfiguration.CharactersReservedPerMedia - 1) + " " + url; - else - message += " " + url; - - return this.tw.PostStatus( message, reply_to ); - } - - private HttpStatusCode UploadFile( FileInfo mediaFile, string message, ref string content ) - { - // Message必須 - if ( string.IsNullOrEmpty( message ) ) - message = ""; - // Check filetype and size(Max 5MB) - if ( !this.CheckValidExtension( mediaFile.Extension ) ) - throw new ArgumentException( "Service don't support this filetype." ); - if ( !this.CheckValidFilesize( mediaFile.Extension, mediaFile.Length ) ) - throw new ArgumentException( "File is too large." ); - - Dictionary< string, string > param = new Dictionary< string, string >(); - param.Add( "key", ApplicationSettings.YfrogApiKey ); - param.Add( "message", message ); - List< KeyValuePair< string, FileInfo > > binary = new List< KeyValuePair< string, FileInfo > >(); - binary.Add( new KeyValuePair< string, FileInfo >( "media", mediaFile ) ); - this.InstanceTimeout = 60000; // タイムアウト60秒 - - return this.GetContent( HttpConnection.PostMethod, new Uri( "http://yfrog.com/api/xauth_upload" ), param, binary, ref content, null, null ); - } - - public bool CheckValidExtension( string ext ) - { - if ( Array.IndexOf( this.pictureExt, ext.ToLower() ) > -1 ) - return true; - - return false; - } - - public string GetFileOpenDialogFilter() - { - return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; - } - - public UploadFileType GetFileType( string ext ) - { - if ( this.CheckValidExtension( ext ) ) - return UploadFileType.Picture; - - return UploadFileType.Invalid; - } - - public bool IsSupportedFileType( UploadFileType type ) - { - return type.Equals( UploadFileType.Picture ); - } - - public bool CheckValidFilesize( string ext, long fileSize ) - { - if ( this.CheckValidExtension( ext ) ) - return fileSize <= yfrog.MaxFileSize; - - return false; - } - - public yfrog( Twitter twitter ) - : base( new Uri( "http://api.twitter.com/" ), new Uri( "https://api.twitter.com/1.1/account/verify_credentials.xml" ) ) - { - this.tw = twitter; - this.Initialize( ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, this.tw.AccessToken, this.tw.AccessTokenSecret, "", "" ); - } - - public bool Configuration( string key, object value ) - { - return true; - } - } + public class yfrog : IMediaUploadService + { + private readonly string[] pictureExt = new[] { ".jpg", ".jpeg", ".gif", ".png" }; + private readonly long MaxFileSize = 5L * 1024 * 1024; + + private readonly Twitter tw; + private readonly YfrogApi yfrogApi; + + private TwitterConfiguration twitterConfig; + + public yfrog(Twitter twitter, TwitterConfiguration twitterConfig) + { + this.tw = twitter; + this.twitterConfig = twitterConfig; + + this.yfrogApi = new YfrogApi(twitter.AccessToken, twitter.AccessTokenSecret); + } + + public int MaxMediaCount + { + get { return 1; } + } + + public string SupportedFormatsStrForDialog + { + get + { + return "Image Files(*.gif;*.jpg;*.jpeg;*.png)|*.gif;*.jpg;*.jpeg;*.png"; + } + } + + public bool CheckFileExtension(string fileExtension) + { + return this.pictureExt.Contains(fileExtension.ToLower()); + } + + public bool CheckFileSize(string fileExtension, long fileSize) + { + var maxFileSize = this.GetMaxFileSize(fileExtension); + return maxFileSize == null || fileSize <= maxFileSize.Value; + } + + public long? GetMaxFileSize(string fileExtension) + { + return MaxFileSize; + } + + public async Task PostStatusAsync(string text, long? inReplyToStatusId, string[] filePaths) + { + if (filePaths.Length != 1) + throw new ArgumentOutOfRangeException("filePaths"); + + var file = new FileInfo(filePaths[0]); + + if (!file.Exists) + throw new ArgumentException("Err:File isn't exists.", "filePaths[0]"); + + var xml = await this.yfrogApi.UploadFileAsync(file, text) + .ConfigureAwait(false); + + var imageUrlElm = xml.XPathSelectElement("/rsp/mediaurl"); + if (imageUrlElm == null) + throw new WebApiException("Invalid API response", xml.ToString()); + + var textWithImageUrl = text + " " + imageUrlElm.Value.Trim(); + + await Task.Run(() => this.tw.PostStatus(textWithImageUrl, inReplyToStatusId)) + .ConfigureAwait(false); + } + + public int GetReservedTextLength(int mediaCount) + { + return this.twitterConfig.ShortUrlLength; + } + + public void UpdateTwitterConfiguration(TwitterConfiguration config) + { + this.twitterConfig = config; + } + + public class YfrogApi : HttpConnectionOAuthEcho + { + private static readonly Uri UploadEndpoint = new Uri("https://yfrog.com/api/xauth_upload"); + + public YfrogApi(string twitterAccessToken, string twitterAccessTokenSecret) + : base(new Uri("http://api.twitter.com/"), new Uri("https://api.twitter.com/1.1/account/verify_credentials.xml")) + { + this.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret, + twitterAccessToken, twitterAccessTokenSecret, "", ""); + + this.InstanceTimeout = 60000; + } + + /// + /// 画像のアップロードを行います + /// + /// + /// + public async Task UploadFileAsync(FileInfo file, string message) + { + // 参照: http://twitter.yfrog.com/page/api#a1 + + var param = new Dictionary + { + {"key", ApplicationSettings.YfrogApiKey}, + }; + var paramFiles = new List> + { + new KeyValuePair("media", file), + }; + var response = ""; + + var uploadTask = Task.Run(() => this.GetContent(HttpConnection.PostMethod, + UploadEndpoint, param, paramFiles, ref response, null, null)); + + var ret = await uploadTask.ConfigureAwait(false); + + if (ret != HttpStatusCode.OK) + throw new WebApiException("Err:" + ret, response); + + return XDocument.Parse(response); + } + } + } } diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelector.cs index ca591445..47ee93cb 100644 --- a/OpenTween/MediaSelector.cs +++ b/OpenTween/MediaSelector.cs @@ -29,6 +29,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using OpenTween.Api; using OpenTween.Connection; namespace OpenTween @@ -68,15 +69,23 @@ namespace OpenTween } /// - /// 指定された投稿先名から、作成済みの IMultimediaShareService インスタンスを取得する。 + /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。 /// - public IMultimediaShareService GetService(string serviceName) + public IMediaUploadService GetService(string serviceName) { - IMultimediaShareService service; + IMediaUploadService service; this.pictureService.TryGetValue(serviceName, out service); return service; } + /// + /// 利用可能な全ての IMediaUploadService インスタンスを取得する。 + /// + public ICollection GetServices() + { + return this.pictureService.Values; + } + private class SelectedMedia { public string Path { get; set; } @@ -106,20 +115,20 @@ namespace OpenTween } } - private Dictionary pictureService; + private Dictionary pictureService; - private void CreateServices(Twitter tw) + private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig) { if (this.pictureService != null) this.pictureService.Clear(); this.pictureService = null; - this.pictureService = new Dictionary { - {"TwitPic", new TwitPic(tw)}, - {"img.ly", new imgly(tw)}, - {"yfrog", new yfrog(tw)}, - {"Twitter", new TwitterPhoto(tw)}, - {"ついっぷるフォト", new TwipplePhoto(tw)}, - {"Imgur", new Imgur(tw)}, + this.pictureService = new Dictionary { + {"TwitPic", new TwitPic(tw, twitterConfig)}, + {"img.ly", new imgly(tw, twitterConfig)}, + {"yfrog", new yfrog(tw, twitterConfig)}, + {"Twitter", new TwitterPhoto(tw, twitterConfig)}, + {"ついっぷるフォト", new TwipplePhoto(tw, twitterConfig)}, + {"Imgur", new Imgur(tw, twitterConfig)}, }; } @@ -133,9 +142,9 @@ namespace OpenTween /// /// 投稿先サービスなどを初期化する。 /// - public void Initialize(Twitter tw, string svc, int? index = null) + public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null) { - CreateServices(tw); + CreateServices(tw, twitterConfig); SetImageServiceCombo(); SetImagePageCombo(); @@ -146,9 +155,9 @@ namespace OpenTween /// /// 投稿先サービスを再作成する。 /// - public void Reset(Twitter tw) + public void Reset(Twitter tw, TwitterConfiguration twitterConfig) { - CreateServices(tw); + CreateServices(tw, twitterConfig); SetImageServiceCombo(); } @@ -163,7 +172,7 @@ namespace OpenTween var serviceName = this.ServiceName; if (!string.IsNullOrEmpty(serviceName) && - this.pictureService[serviceName].CheckValidFilesize(ext, fl.Length)) + this.pictureService[serviceName].CheckFileSize(ext, fl.Length)) { return true; } @@ -171,7 +180,7 @@ namespace OpenTween foreach (string svc in ImageServiceCombo.Items) { if (!string.IsNullOrEmpty(svc) && - this.pictureService[svc].CheckValidFilesize(ext, fl.Length)) + this.pictureService[svc].CheckFileSize(ext, fl.Length)) { return true; } @@ -277,7 +286,7 @@ namespace OpenTween private void FilePickButton_Click(object sender, EventArgs e) { if (FilePickDialog == null || string.IsNullOrEmpty(this.ServiceName)) return; - FilePickDialog.Filter = this.pictureService[this.ServiceName].GetFileOpenDialogFilter(); + FilePickDialog.Filter = this.pictureService[this.ServiceName].SupportedFormatsStrForDialog; FilePickDialog.Title = Properties.Resources.PickPictureDialog1; FilePickDialog.FileName = ""; @@ -328,7 +337,7 @@ namespace OpenTween string ext = fl.Extension; var imageService = this.pictureService[serviceName]; - if (!imageService.CheckValidExtension(ext)) + if (!imageService.CheckFileExtension(ext)) { //画像以外の形式 ClearSelectedImagePage(); @@ -343,7 +352,7 @@ namespace OpenTween return; } - if (!imageService.CheckValidFilesize(ext, fl.Length)) + if (!imageService.CheckFileSize(ext, fl.Length)) { // ファイルサイズが大きすぎる ClearSelectedImagePage(); @@ -358,21 +367,17 @@ namespace OpenTween return; } - switch (imageService.GetFileType(ext)) + try { - case MyCommon.UploadFileType.Picture: - using (var fs = File.OpenRead(fileName)) - { - ImageSelectedPicture.Image = MemoryImage.CopyFromStream(fs); - } - SetSelectedImagePage(fileName, MyCommon.UploadFileType.Picture); - break; - case MyCommon.UploadFileType.MultiMedia: - SetSelectedImagePage(fileName, MyCommon.UploadFileType.MultiMedia); - break; - default: - ClearSelectedImagePage(); - break; + using (var fs = File.OpenRead(fileName)) + { + ImageSelectedPicture.Image = MemoryImage.CopyFromStream(fs); + } + SetSelectedImagePage(fileName, MyCommon.UploadFileType.Picture); + } + catch (InvalidImageException) + { + SetSelectedImagePage(fileName, MyCommon.UploadFileType.MultiMedia); } } catch (FileNotFoundException) @@ -391,7 +396,7 @@ namespace OpenTween { var text = string.Join(", ", ImageServiceCombo.Items.Cast() - .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckValidFilesize(ext, fileSize))); + .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize))); if (string.IsNullOrEmpty(text)) return Properties.Resources.PostPictureWarn6; @@ -513,7 +518,7 @@ namespace OpenTween FileInfo fi = new FileInfo(ImagefilePathText.Text.Trim()); string ext = fi.Extension; var imageService = this.pictureService[serviceName]; - if (!imageService.CheckValidFilesize(ext, fi.Length)) + if (!imageService.CheckFileSize(ext, fi.Length)) { ClearImageSelectedPicture(); ClearSelectedImagePage(); diff --git a/OpenTween/OpenTween.csproj b/OpenTween/OpenTween.csproj index 24d34105..fa3f2263 100644 --- a/OpenTween/OpenTween.csproj +++ b/OpenTween/OpenTween.csproj @@ -111,9 +111,9 @@ + - Form diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index d71d35e2..eb7585a0 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -1015,7 +1015,7 @@ namespace OpenTween AllrepliesToolStripMenuItem.Checked = tw.AllAtReply; //画像投稿サービス - ImageSelector.Initialize(tw, _cfgCommon.UseImageServiceName, _cfgCommon.UseImageService); + ImageSelector.Initialize(tw, SettingDialog.TwitterConfiguration, _cfgCommon.UseImageServiceName, _cfgCommon.UseImageService); //ウィンドウ設定 this.ClientSize = _cfgLocal.FormSize; @@ -2518,19 +2518,14 @@ namespace OpenTween else { var service = ImageSelector.GetService(args.status.imageService); - if (args.status.imagePath.Length > 1 && - args.status.imageService.Equals("Twitter")) + try { - //複数画像投稿 - ret = ((TwitterPhoto)service).Upload(ref args.status.imagePath, - ref args.status.status, - args.status.inReplyToId); + service.PostStatusAsync(args.status.status, args.status.inReplyToId, args.status.imagePath) + .Wait(); } - else + catch (AggregateException ex) { - ret = service.Upload(ref args.status.imagePath[0], - ref args.status.status, - args.status.inReplyToId); + ret = ex.InnerException.Message; } } bw.ReportProgress(300); @@ -3068,9 +3063,10 @@ namespace OpenTween //_waitFollower = false if (SettingDialog.TwitterConfiguration.PhotoSizeLimit != 0) { - var service = ImageSelector.GetService("Twitter"); - if (service != null) - service.Configuration("MaxUploadFilesize", SettingDialog.TwitterConfiguration.PhotoSizeLimit); + foreach (var service in this.ImageSelector.GetServices()) + { + service.UpdateTwitterConfiguration(this.SettingDialog.TwitterConfiguration); + } } this.PurgeListViewItemCache(); if (_curList != null) _curList.Refresh(); @@ -3928,7 +3924,7 @@ namespace OpenTween SettingDialog.ProxyUser, SettingDialog.ProxyPassword); - ImageSelector.Reset(tw); + ImageSelector.Reset(tw, SettingDialog.TwitterConfiguration); try { -- 2.11.0