OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[opentween/open-tween.git] / OpenTween / Connection / Mobypicture.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 #nullable enable
23
24 using System;
25 using System.Collections.Generic;
26 using System.IO;
27 using System.Linq;
28 using System.Net;
29 using System.Net.Http;
30 using System.Text;
31 using System.Threading.Tasks;
32 using System.Windows.Forms;
33 using System.Xml;
34 using System.Xml.Linq;
35 using System.Xml.XPath;
36 using OpenTween.Api;
37 using OpenTween.Api.DataModel;
38
39 namespace OpenTween.Connection
40 {
41     public class Mobypicture : IMediaUploadService
42     {
43         private static readonly long MaxFileSize = 5L * 1024 * 1024; // 上限不明
44
45         // 参照: http://developers.mobypicture.com/documentation/1-0/postmedia/
46         private static readonly IEnumerable<string> AllowedExtensions = new[]
47         {
48             // Photo
49             ".jpg",
50             ".gif",
51             ".png",
52             ".bmp",
53
54             // Video
55             ".flv",
56             ".mpg",
57             ".mpeg",
58             ".mkv",
59             ".wmv",
60             ".mov",
61             ".3gp",
62             ".mp4",
63             ".avi",
64
65             // Audio
66             ".mp3",
67             ".wma",
68             ".aac",
69             ".aif",
70             ".au",
71             ".flac",
72             ".ra",
73             ".wav",
74             ".ogg",
75             ".3gp",
76         };
77
78         private readonly MobypictureApi mobypictureApi;
79
80         private TwitterConfiguration twitterConfig;
81
82         public Mobypicture(Twitter twitter, TwitterConfiguration twitterConfig)
83         {
84             this.twitterConfig = twitterConfig ?? throw new ArgumentNullException(nameof(twitterConfig));
85
86             this.mobypictureApi = new MobypictureApi(twitter.Api);
87         }
88
89         public int MaxMediaCount => 1;
90
91         public string SupportedFormatsStrForDialog
92         {
93             get
94             {
95                 var filterFormatExtensions = "";
96                 foreach (var pictureExtension in AllowedExtensions)
97                 {
98                     filterFormatExtensions += '*' + pictureExtension + ';';
99                 }
100                 return "Media Files(" + filterFormatExtensions + ")|" + filterFormatExtensions;
101             }
102         }
103
104         public bool CanUseAltText => false;
105
106         public bool CheckFileExtension(string fileExtension)
107             => AllowedExtensions.Contains(fileExtension, StringComparer.OrdinalIgnoreCase);
108
109         public bool CheckFileSize(string fileExtension, long fileSize)
110         {
111             var maxFileSize = this.GetMaxFileSize(fileExtension);
112             return maxFileSize == null || fileSize <= maxFileSize.Value;
113         }
114
115         public long? GetMaxFileSize(string fileExtension)
116             => MaxFileSize;
117
118         public async Task<PostStatusParams> UploadAsync(IMediaItem[] mediaItems, PostStatusParams postParams)
119         {
120             if (mediaItems == null)
121                 throw new ArgumentNullException(nameof(mediaItems));
122
123             if (mediaItems.Length != 1)
124                 throw new ArgumentOutOfRangeException(nameof(mediaItems));
125
126             var item = mediaItems[0];
127
128             if (item == null)
129                 throw new ArgumentException("Err:Media not specified.");
130
131             if (!item.Exists)
132                 throw new ArgumentException("Err:Media not found.");
133
134             var xml = await this.mobypictureApi.UploadFileAsync(item, postParams.Text)
135                 .ConfigureAwait(false);
136
137             var imageUrlElm = xml.XPathSelectElement("/rsp/media/mediaurl");
138             if (imageUrlElm == null)
139                 throw new WebApiException("Invalid API response", xml.ToString());
140
141             postParams.Text += " " + imageUrlElm.Value.Trim();
142
143             return postParams;
144         }
145
146         public int GetReservedTextLength(int mediaCount)
147             => this.twitterConfig.ShortUrlLength + 1;
148
149         public void UpdateTwitterConfiguration(TwitterConfiguration config)
150             => this.twitterConfig = config;
151
152         public class MobypictureApi
153         {
154             private readonly HttpClient http;
155
156             private static readonly Uri UploadEndpoint = new Uri("https://api.mobypicture.com/2.0/upload.xml");
157
158             private static readonly Uri OAuthRealm = new Uri("http://api.twitter.com/");
159             private static readonly Uri AuthServiceProvider = new Uri("https://api.twitter.com/1.1/account/verify_credentials.json");
160
161             public MobypictureApi(TwitterApi twitterApi)
162             {
163                 var handler = twitterApi.CreateOAuthEchoHandler(AuthServiceProvider, OAuthRealm);
164
165                 this.http = Networking.CreateHttpClient(handler);
166                 this.http.Timeout = Networking.UploadImageTimeout;
167             }
168
169             /// <summary>
170             /// 画像のアップロードを行います
171             /// </summary>
172             /// <exception cref="WebApiException"/>
173             /// <exception cref="XmlException"/>
174             public async Task<XDocument> UploadFileAsync(IMediaItem item, string message)
175             {
176                 // 参照: http://developers.mobypicture.com/documentation/2-0/upload/
177
178                 using var request = new HttpRequestMessage(HttpMethod.Post, UploadEndpoint);
179                 using var multipart = new MultipartFormDataContent();
180                 request.Content = multipart;
181
182                 using var apiKeyContent = new StringContent(ApplicationSettings.MobypictureKey);
183                 using var messageContent = new StringContent(message);
184                 using var mediaStream = item.OpenRead();
185                 using var mediaContent = new StreamContent(mediaStream);
186
187                 multipart.Add(apiKeyContent, "key");
188                 multipart.Add(messageContent, "message");
189                 multipart.Add(mediaContent, "media", item.Name);
190
191                 using var response = await this.http.SendAsync(request)
192                     .ConfigureAwait(false);
193
194                 var responseText = await response.Content.ReadAsStringAsync()
195                     .ConfigureAwait(false);
196
197                 if (!response.IsSuccessStatusCode)
198                     throw new WebApiException(response.StatusCode.ToString(), responseText);
199
200                 return XDocument.Parse(responseText);
201             }
202         }
203     }
204 }