OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[opentween/open-tween.git] / OpenTween / Thumbnail / ThumbnailGenerator.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 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.Linq;
27 using System.Net.Http;
28 using System.Text;
29 using System.Text.RegularExpressions;
30 using System.Threading;
31 using System.Threading.Tasks;
32 using OpenTween.Models;
33 using OpenTween.Thumbnail.Services;
34
35 namespace OpenTween.Thumbnail
36 {
37     class ThumbnailGenerator
38     {
39         public static List<IThumbnailService> Services { get; protected set; }
40
41         internal static ImgAzyobuziNet ImgAzyobuziNetInstance { get; private set; }
42
43         static ThumbnailGenerator()
44             => ThumbnailGenerator.Services = new List<IThumbnailService>();
45
46         public static void InitializeGenerator()
47         {
48             ImgAzyobuziNetInstance = new ImgAzyobuziNet(autoupdate: true);
49
50             ThumbnailGenerator.Services = new List<IThumbnailService>
51             {
52                 // ton.twitter.com
53                 new TonTwitterCom(),
54
55                 // twitter.com/tweet_video
56                 new TwitterComVideo(),
57
58                 // pic.twitter.com
59                 new PbsTwimgCom(),
60
61                 // youtube
62                 new Youtube(),
63
64                 // ニコニコ動画
65                 new Nicovideo(),
66
67                 // vimeo
68                 new Vimeo(),
69
70                 // DirectLink
71                 new SimpleThumbnailService(@"^https?://.*(\.jpg|\.jpeg|\.gif|\.png|\.bmp)$", "${0}"),
72
73                 // img.azyobuzi.net
74                 ImgAzyobuziNetInstance,
75
76                 // ImgUr
77                 new SimpleThumbnailService(
78                     @"^https?://(?:i\.)?imgur\.com/(\w+)(?:\..{3})?$",
79                     "https://img.imgur.com/${1}l.jpg",
80                     "https://img.imgur.com/${1}.jpg"),
81
82                 // Twitpic
83                 new MetaThumbnailService(@"^https?://(www\.)?twitpic\.com/.+$"),
84
85                 // MobyPicture
86                 new MetaThumbnailService(@"^https?://(?:www\.)?mobypicture.com/user/\w+/view/\d+$"),
87
88                 // 携帯百景
89                 new SimpleThumbnailService(
90                     @"^https?://movapic\.com/pic/(\w+)$",
91                     "http://image.movapic.com/pic/s_${1}.jpeg",
92                     "http://image.movapic.com/pic/m_${1}.jpeg"),
93
94                 // はてなフォトライフ
95                 new SimpleThumbnailService(
96                     @"^https?://f\.hatena\.ne\.jp/(([a-z])[a-z0-9_-]{1,30}[a-z0-9])/((\d{8})\d+)$",
97                     "http://img.f.hatena.ne.jp/images/fotolife/${2}/${1}/${4}/${3}_120.jpg",
98                     "http://img.f.hatena.ne.jp/images/fotolife/${2}/${1}/${4}/${3}.jpg"),
99
100                 // ニコニコ静画
101                 new SimpleThumbnailService(
102                     @"^https?://(?:seiga\.nicovideo\.jp/seiga/|nico\.ms/)im(?<id>\d+)",
103                     "http://lohas.nicoseiga.jp/thumb/${id}q?",
104                     "http://lohas.nicoseiga.jp/thumb/${id}l?"),
105
106                 // pixiv
107                 new Pixiv(),
108
109                 // flickr
110                 new MetaThumbnailService(@"^https?://www\.flickr\.com/.+$"),
111
112                 // フォト蔵
113                 new SimpleThumbnailService(
114                     @"^https?://photozou\.jp/photo/show/(?<userId>[0-9]+)/(?<photoId>[0-9]+)",
115                     "https://photozou.jp/p/thumb/${photoId}",
116                     "https://photozou.jp/p/img/${photoId}"),
117
118                 // Piapro
119                 new MetaThumbnailService(@"^https?://piapro\.jp/(?:content/[0-9a-z]+|t/[0-9a-zA-Z_\-]+)$"),
120
121                 // Tumblr
122                 new Tumblr(),
123
124                 // mypix/shamoji
125                 new SimpleThumbnailService(@"^https?://www\.(mypix\.jp|shamoji\.info)/app\.php/picture/(?<contentId>[0-9a-z]+)", "${0}/thumb.jpg"),
126
127                 // ow.ly
128                 new SimpleThumbnailService(@"^https?://ow\.ly/i/(\w+)$", "http://static.ow.ly/photos/thumb/${1}.jpg"),
129
130                 // cloudfiles
131                 new SimpleThumbnailService(@"^https?://c[0-9]+\.cdn[0-9]+\.cloudfiles\.rackspacecloud\.com/[a-z_0-9]+", "${0}"),
132
133                 // Instagram
134                 new SimpleThumbnailService(
135                     @"^https?://(?:instagram.com|instagr\.am|i\.instagram\.com|www\.instagram\.com)/p/.+/",
136                     "${0}media/?size=m",
137                     "${0}media/?size=l"),
138
139                 // Foursquare
140                 new FoursquareCheckin(),
141
142                 // TINAMI
143                 new Tinami(),
144
145                 // tuna.be
146                 new SimpleThumbnailService(@"^https?://tuna\.be/t/(?<entryId>[a-zA-Z0-9\.\-_]+)$", "https://tuna.be/show/thumb/${entryId}"),
147
148                 // GIFMAGAZINE
149                 new SimpleThumbnailService(@"^https?://gifmagazine\.net/post_images/(\d+)", "https://img.gifmagazine.net/gifmagazine/images/${1}/original.gif"),
150
151                 // SoundCloud
152                 new MetaThumbnailService(@"^https?://soundcloud.com/[\w-]+/[\w-]+$"),
153
154                 // Gyazo (参考: http://qiita.com/uiureo/items/9ea55b07dff28a322a9e)
155                 new SimpleThumbnailService(@"^https?://gyazo\.com/([a-zA-Z0-9]+)/?$", "https://gyazo.com/${1}/raw"),
156             };
157         }
158
159         public static async Task<IEnumerable<ThumbnailInfo>> GetThumbnailsAsync(PostClass post, CancellationToken token)
160         {
161             var thumbnails = new List<ThumbnailInfo>();
162
163             var expandedUrls = Enumerable.Concat(
164                 post.GetExpandedUrls(), post.Media.Select(x => x.Url));
165
166             foreach (var expandedUrl in expandedUrls)
167             {
168                 var thumbInfo = await ThumbnailGenerator.GetThumbnailInfoAsync(expandedUrl, post, token)
169                     .ConfigureAwait(false);
170
171                 if (thumbInfo != null)
172                     thumbnails.Add(thumbInfo);
173
174                 token.ThrowIfCancellationRequested();
175             }
176
177             if (post.PostGeo != null)
178             {
179                 var map = MapThumb.GetDefaultInstance();
180                 var thumb = await map.GetThumbnailInfoAsync(post.PostGeo.Value)
181                     .ConfigureAwait(false);
182                 thumbnails.Add(thumb);
183             }
184
185             return thumbnails;
186         }
187
188         public static async Task<ThumbnailInfo?> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
189         {
190             foreach (var generator in ThumbnailGenerator.Services)
191             {
192                 var result = await generator.GetThumbnailInfoAsync(url, post, token)
193                     .ConfigureAwait(false);
194
195                 if (result != null)
196                     return result;
197
198                 token.ThrowIfCancellationRequested();
199             }
200
201             return null;
202         }
203     }
204 }