1 // OpenTween - Client of Twitter
2 // Copyright (c) 2016 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
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)
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
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.
23 using System.Collections.Generic;
27 using System.Threading;
28 using System.Threading.Tasks;
29 using OpenTween.Api.DataModel;
30 using OpenTween.Connection;
32 namespace OpenTween.Api
34 public sealed class TwitterApi : IDisposable
36 public long CurrentUserId { get; private set; }
37 public string CurrentScreenName { get; private set; }
39 public IApiConnection Connection => this.apiConnection;
41 internal IApiConnection apiConnection;
43 public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
45 var newInstance = new TwitterApiConnection(accessToken, accessSecret);
46 var oldInstance = Interlocked.Exchange(ref this.apiConnection, newInstance);
47 oldInstance?.Dispose();
49 this.CurrentUserId = userId;
50 this.CurrentScreenName = screenName;
53 public Task<TwitterStatus[]> StatusesHomeTimeline(int? count = null, long? maxId = null, long? sinceId = null)
55 var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative);
56 var param = new Dictionary<string, string>
58 ["include_entities"] = "true",
59 ["include_ext_alt_text"] = "true",
60 ["tweet_mode"] = "extended",
64 param["count"] = count.ToString();
66 param["max_id"] = maxId.ToString();
68 param["since_id"] = sinceId.ToString();
70 return this.apiConnection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/home_timeline");
73 public Task<TwitterStatus[]> StatusesMentionsTimeline(int? count = null, long? maxId = null, long? sinceId = null)
75 var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative);
76 var param = new Dictionary<string, string>
78 ["include_entities"] = "true",
79 ["include_ext_alt_text"] = "true",
80 ["tweet_mode"] = "extended",
84 param["count"] = count.ToString();
86 param["max_id"] = maxId.ToString();
88 param["since_id"] = sinceId.ToString();
90 return this.apiConnection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/mentions_timeline");
93 public Task<TwitterStatus[]> StatusesUserTimeline(string screenName, int? count = null, long? maxId = null, long? sinceId = null)
95 var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative);
96 var param = new Dictionary<string, string>
98 ["screen_name"] = screenName,
99 ["include_rts"] = "true",
100 ["include_entities"] = "true",
101 ["include_ext_alt_text"] = "true",
102 ["tweet_mode"] = "extended",
106 param["count"] = count.ToString();
108 param["max_id"] = maxId.ToString();
110 param["since_id"] = sinceId.ToString();
112 return this.apiConnection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/user_timeline");
115 public Task<TwitterStatus> StatusesShow(long statusId)
117 var endpoint = new Uri("statuses/show.json", UriKind.Relative);
118 var param = new Dictionary<string, string>
120 ["id"] = statusId.ToString(),
121 ["include_entities"] = "true",
122 ["include_ext_alt_text"] = "true",
123 ["tweet_mode"] = "extended",
126 return this.apiConnection.GetAsync<TwitterStatus>(endpoint, param, "/statuses/show/:id");
129 public Task<LazyJson<TwitterStatus>> StatusesUpdate(string status, long? replyToId, IReadOnlyList<long> mediaIds,
130 bool? autoPopulateReplyMetadata = null, IReadOnlyList<long> excludeReplyUserIds = null, string attachmentUrl = null)
132 var endpoint = new Uri("statuses/update.json", UriKind.Relative);
133 var param = new Dictionary<string, string>
136 ["include_entities"] = "true",
137 ["include_ext_alt_text"] = "true",
138 ["tweet_mode"] = "extended",
141 if (replyToId != null)
142 param["in_reply_to_status_id"] = replyToId.ToString();
143 if (mediaIds != null && mediaIds.Count > 0)
144 param.Add("media_ids", string.Join(",", mediaIds));
145 if (autoPopulateReplyMetadata != null)
146 param["auto_populate_reply_metadata"] = autoPopulateReplyMetadata.Value ? "true" : "false";
147 if (excludeReplyUserIds != null && excludeReplyUserIds.Count > 0)
148 param["exclude_reply_user_ids"] = string.Join(",", excludeReplyUserIds);
149 if (attachmentUrl != null)
150 param["attachment_url"] = attachmentUrl;
152 return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
155 public Task<LazyJson<TwitterStatus>> StatusesDestroy(long statusId)
157 var endpoint = new Uri("statuses/destroy.json", UriKind.Relative);
158 var param = new Dictionary<string, string>
160 ["id"] = statusId.ToString(),
163 return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
166 public Task<LazyJson<TwitterStatus>> StatusesRetweet(long statusId)
168 var endpoint = new Uri("statuses/retweet.json", UriKind.Relative);
169 var param = new Dictionary<string, string>
171 ["id"] = statusId.ToString(),
172 ["include_entities"] = "true",
173 ["include_ext_alt_text"] = "true",
174 ["tweet_mode"] = "extended",
177 return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
180 public Task<TwitterSearchResult> SearchTweets(string query, string lang = null, int? count = null, long? maxId = null, long? sinceId = null)
182 var endpoint = new Uri("search/tweets.json", UriKind.Relative);
183 var param = new Dictionary<string, string>
186 ["result_type"] = "recent",
187 ["include_entities"] = "true",
188 ["include_ext_alt_text"] = "true",
189 ["tweet_mode"] = "extended",
193 param["lang"] = lang;
195 param["count"] = count.ToString();
197 param["max_id"] = maxId.ToString();
199 param["since_id"] = sinceId.ToString();
201 return this.apiConnection.GetAsync<TwitterSearchResult>(endpoint, param, "/search/tweets");
204 public Task<TwitterLists> ListsOwnerships(string screenName, long? cursor = null, int? count = null)
206 var endpoint = new Uri("lists/ownerships.json", UriKind.Relative);
207 var param = new Dictionary<string, string>
209 ["screen_name"] = screenName,
213 param["cursor"] = cursor.ToString();
215 param["count"] = count.ToString();
217 return this.apiConnection.GetAsync<TwitterLists>(endpoint, param, "/lists/ownerships");
220 public Task<TwitterLists> ListsSubscriptions(string screenName, long? cursor = null, int? count = null)
222 var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative);
223 var param = new Dictionary<string, string>
225 ["screen_name"] = screenName,
229 param["cursor"] = cursor.ToString();
231 param["count"] = count.ToString();
233 return this.apiConnection.GetAsync<TwitterLists>(endpoint, param, "/lists/subscriptions");
236 public Task<TwitterLists> ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null)
238 var endpoint = new Uri("lists/memberships.json", UriKind.Relative);
239 var param = new Dictionary<string, string>
241 ["screen_name"] = screenName,
245 param["cursor"] = cursor.ToString();
247 param["count"] = count.ToString();
248 if (filterToOwnedLists != null)
249 param["filter_to_owned_lists"] = filterToOwnedLists.Value ? "true" : "false";
251 return this.apiConnection.GetAsync<TwitterLists>(endpoint, param, "/lists/memberships");
254 public Task<LazyJson<TwitterList>> ListsCreate(string name, string description = null, bool? @private = null)
256 var endpoint = new Uri("lists/create.json", UriKind.Relative);
257 var param = new Dictionary<string, string>
262 if (description != null)
263 param["description"] = description;
264 if (@private != null)
265 param["mode"] = @private.Value ? "private" : "public";
267 return this.apiConnection.PostLazyAsync<TwitterList>(endpoint, param);
270 public Task<LazyJson<TwitterList>> ListsUpdate(long listId, string name = null, string description = null, bool? @private = null)
272 var endpoint = new Uri("lists/update.json", UriKind.Relative);
273 var param = new Dictionary<string, string>
275 ["list_id"] = listId.ToString(),
279 param["name"] = name;
280 if (description != null)
281 param["description"] = description;
282 if (@private != null)
283 param["mode"] = @private.Value ? "private" : "public";
285 return this.apiConnection.PostLazyAsync<TwitterList>(endpoint, param);
288 public Task<LazyJson<TwitterList>> ListsDestroy(long listId)
290 var endpoint = new Uri("lists/destroy.json", UriKind.Relative);
291 var param = new Dictionary<string, string>
293 ["list_id"] = listId.ToString(),
296 return this.apiConnection.PostLazyAsync<TwitterList>(endpoint, param);
299 public Task<TwitterStatus[]> ListsStatuses(long listId, int? count = null, long? maxId = null, long? sinceId = null, bool? includeRTs = null)
301 var endpoint = new Uri("lists/statuses.json", UriKind.Relative);
302 var param = new Dictionary<string, string>
304 ["list_id"] = listId.ToString(),
305 ["include_entities"] = "true",
306 ["include_ext_alt_text"] = "true",
307 ["tweet_mode"] = "extended",
311 param["count"] = count.ToString();
313 param["max_id"] = maxId.ToString();
315 param["since_id"] = sinceId.ToString();
316 if (includeRTs != null)
317 param["include_rts"] = includeRTs.Value ? "true" : "false";
319 return this.apiConnection.GetAsync<TwitterStatus[]>(endpoint, param, "/lists/statuses");
322 public Task<TwitterUsers> ListsMembers(long listId, long? cursor = null)
324 var endpoint = new Uri("lists/members.json", UriKind.Relative);
325 var param = new Dictionary<string, string>
327 ["list_id"] = listId.ToString(),
328 ["include_entities"] = "true",
329 ["include_ext_alt_text"] = "true",
330 ["tweet_mode"] = "extended",
334 param["cursor"] = cursor.ToString();
336 return this.apiConnection.GetAsync<TwitterUsers>(endpoint, param, "/lists/members");
339 public Task<TwitterUser> ListsMembersShow(long listId, string screenName)
341 var endpoint = new Uri("lists/members/show.json", UriKind.Relative);
342 var param = new Dictionary<string, string>
344 ["list_id"] = listId.ToString(),
345 ["screen_name"] = screenName,
346 ["include_entities"] = "true",
347 ["include_ext_alt_text"] = "true",
348 ["tweet_mode"] = "extended",
351 return this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/lists/members/show");
354 public Task<LazyJson<TwitterUser>> ListsMembersCreate(long listId, string screenName)
356 var endpoint = new Uri("lists/members/create.json", UriKind.Relative);
357 var param = new Dictionary<string, string>
359 ["list_id"] = listId.ToString(),
360 ["screen_name"] = screenName,
361 ["include_entities"] = "true",
362 ["include_ext_alt_text"] = "true",
363 ["tweet_mode"] = "extended",
366 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
369 public Task<LazyJson<TwitterUser>> ListsMembersDestroy(long listId, string screenName)
371 var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative);
372 var param = new Dictionary<string, string>
374 ["list_id"] = listId.ToString(),
375 ["screen_name"] = screenName,
376 ["include_entities"] = "true",
377 ["include_ext_alt_text"] = "true",
378 ["tweet_mode"] = "extended",
381 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
384 public Task<TwitterDirectMessage[]> DirectMessagesRecv(int? count = null, long? maxId = null, long? sinceId = null)
386 var endpoint = new Uri("direct_messages.json", UriKind.Relative);
387 var param = new Dictionary<string, string>
389 ["full_text"] = "true",
390 ["include_entities"] = "true",
391 ["include_ext_alt_text"] = "true",
395 param["count"] = count.ToString();
397 param["max_id"] = maxId.ToString();
399 param["since_id"] = sinceId.ToString();
401 return this.apiConnection.GetAsync<TwitterDirectMessage[]>(endpoint, param, "/direct_messages");
404 public Task<TwitterDirectMessage[]> DirectMessagesSent(int? count = null, long? maxId = null, long? sinceId = null)
406 var endpoint = new Uri("direct_messages/sent.json", UriKind.Relative);
407 var param = new Dictionary<string, string>
409 ["full_text"] = "true",
410 ["include_entities"] = "true",
411 ["include_ext_alt_text"] = "true",
415 param["count"] = count.ToString();
417 param["max_id"] = maxId.ToString();
419 param["since_id"] = sinceId.ToString();
421 return this.apiConnection.GetAsync<TwitterDirectMessage[]>(endpoint, param, "/direct_messages/sent");
424 public Task<LazyJson<TwitterDirectMessage>> DirectMessagesNew(string status, string sendTo)
426 var endpoint = new Uri("direct_messages/new.json", UriKind.Relative);
427 var param = new Dictionary<string, string>
430 ["screen_name"] = sendTo,
433 return this.apiConnection.PostLazyAsync<TwitterDirectMessage>(endpoint, param);
436 public Task<LazyJson<TwitterDirectMessage>> DirectMessagesDestroy(long statusId)
438 var endpoint = new Uri("direct_messages/destroy.json", UriKind.Relative);
439 var param = new Dictionary<string, string>
441 ["id"] = statusId.ToString(),
444 return this.apiConnection.PostLazyAsync<TwitterDirectMessage>(endpoint, param);
447 public Task<TwitterUser> UsersShow(string screenName)
449 var endpoint = new Uri("users/show.json", UriKind.Relative);
450 var param = new Dictionary<string, string>
452 ["screen_name"] = screenName,
453 ["include_entities"] = "true",
454 ["include_ext_alt_text"] = "true",
455 ["tweet_mode"] = "extended",
458 return this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/users/show/:id");
461 public Task<LazyJson<TwitterUser>> UsersReportSpam(string screenName)
463 var endpoint = new Uri("users/report_spam.json", UriKind.Relative);
464 var param = new Dictionary<string, string>
466 ["screen_name"] = screenName,
467 ["tweet_mode"] = "extended",
470 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
473 public Task<TwitterStatus[]> FavoritesList(int? count = null, long? maxId = null, long? sinceId = null)
475 var endpoint = new Uri("favorites/list.json", UriKind.Relative);
476 var param = new Dictionary<string, string>
478 ["include_entities"] = "true",
479 ["include_ext_alt_text"] = "true",
480 ["tweet_mode"] = "extended",
484 param["count"] = count.ToString();
486 param["max_id"] = maxId.ToString();
488 param["since_id"] = sinceId.ToString();
490 return this.apiConnection.GetAsync<TwitterStatus[]>(endpoint, param, "/favorites/list");
493 public Task<LazyJson<TwitterStatus>> FavoritesCreate(long statusId)
495 var endpoint = new Uri("favorites/create.json", UriKind.Relative);
496 var param = new Dictionary<string, string>
498 ["id"] = statusId.ToString(),
499 ["tweet_mode"] = "extended",
502 return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
505 public Task<LazyJson<TwitterStatus>> FavoritesDestroy(long statusId)
507 var endpoint = new Uri("favorites/destroy.json", UriKind.Relative);
508 var param = new Dictionary<string, string>
510 ["id"] = statusId.ToString(),
511 ["tweet_mode"] = "extended",
514 return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
517 public Task<TwitterFriendship> FriendshipsShow(string sourceScreenName, string targetScreenName)
519 var endpoint = new Uri("friendships/show.json", UriKind.Relative);
520 var param = new Dictionary<string, string>
522 ["source_screen_name"] = sourceScreenName,
523 ["target_screen_name"] = targetScreenName,
526 return this.apiConnection.GetAsync<TwitterFriendship>(endpoint, param, "/friendships/show");
529 public Task<LazyJson<TwitterFriendship>> FriendshipsCreate(string screenName)
531 var endpoint = new Uri("friendships/create.json", UriKind.Relative);
532 var param = new Dictionary<string, string>
534 ["screen_name"] = screenName,
537 return this.apiConnection.PostLazyAsync<TwitterFriendship>(endpoint, param);
540 public Task<LazyJson<TwitterFriendship>> FriendshipsDestroy(string screenName)
542 var endpoint = new Uri("friendships/destroy.json", UriKind.Relative);
543 var param = new Dictionary<string, string>
545 ["screen_name"] = screenName,
548 return this.apiConnection.PostLazyAsync<TwitterFriendship>(endpoint, param);
551 public Task<long[]> NoRetweetIds(long? cursor = null)
553 var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative);
555 return this.apiConnection.GetAsync<long[]>(endpoint, null, "/friendships/no_retweets/ids");
558 public Task<TwitterIds> FollowersIds(long? cursor = null)
560 var endpoint = new Uri("followers/ids.json", UriKind.Relative);
561 var param = new Dictionary<string, string>();
564 param["cursor"] = cursor.ToString();
566 return this.apiConnection.GetAsync<TwitterIds>(endpoint, param, "/followers/ids");
569 public Task<TwitterIds> MutesUsersIds(long? cursor = null)
571 var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative);
572 var param = new Dictionary<string, string>();
575 param["cursor"] = cursor.ToString();
577 return this.apiConnection.GetAsync<TwitterIds>(endpoint, param, "/mutes/users/ids");
580 public Task<TwitterIds> BlocksIds(long? cursor = null)
582 var endpoint = new Uri("blocks/ids.json", UriKind.Relative);
583 var param = new Dictionary<string, string>();
586 param["cursor"] = cursor.ToString();
588 return this.apiConnection.GetAsync<TwitterIds>(endpoint, param, "/blocks/ids");
591 public Task<LazyJson<TwitterUser>> BlocksCreate(string screenName)
593 var endpoint = new Uri("blocks/create.json", UriKind.Relative);
594 var param = new Dictionary<string, string>
596 ["screen_name"] = screenName,
597 ["tweet_mode"] = "extended",
600 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
603 public Task<LazyJson<TwitterUser>> BlocksDestroy(string screenName)
605 var endpoint = new Uri("blocks/destroy.json", UriKind.Relative);
606 var param = new Dictionary<string, string>
608 ["screen_name"] = screenName,
609 ["tweet_mode"] = "extended",
612 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
615 public async Task<TwitterUser> AccountVerifyCredentials()
617 var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative);
618 var param = new Dictionary<string, string>
620 ["include_entities"] = "true",
621 ["include_ext_alt_text"] = "true",
622 ["tweet_mode"] = "extended",
625 var user = await this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/account/verify_credentials")
626 .ConfigureAwait(false);
628 this.CurrentUserId = user.Id;
629 this.CurrentScreenName = user.ScreenName;
634 public Task<LazyJson<TwitterUser>> AccountUpdateProfile(string name, string url, string location, string description)
636 var endpoint = new Uri("account/update_profile.json", UriKind.Relative);
637 var param = new Dictionary<string, string>
639 ["include_entities"] = "true",
640 ["include_ext_alt_text"] = "true",
641 ["tweet_mode"] = "extended",
645 param["name"] = name;
648 if (location != null)
649 param["location"] = location;
651 if (description != null)
653 // name, location, description に含まれる < > " の文字はTwitter側で除去されるが、
654 // twitter.com の挙動では description でのみ < 等の文字参照を使って表示することができる
655 var escapedDescription = description.Replace("<", "<").Replace(">", ">").Replace("\"", """);
656 param["description"] = escapedDescription;
659 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
662 public Task<LazyJson<TwitterUser>> AccountUpdateProfileImage(IMediaItem image)
664 var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative);
665 var param = new Dictionary<string, string>
667 ["include_entities"] = "true",
668 ["include_ext_alt_text"] = "true",
669 ["tweet_mode"] = "extended",
671 var paramMedia = new Dictionary<string, IMediaItem>
676 return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param, paramMedia);
679 public Task<TwitterRateLimits> ApplicationRateLimitStatus()
681 var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative);
683 return this.apiConnection.GetAsync<TwitterRateLimits>(endpoint, null, "/application/rate_limit_status");
686 public Task<TwitterConfiguration> Configuration()
688 var endpoint = new Uri("help/configuration.json", UriKind.Relative);
690 return this.apiConnection.GetAsync<TwitterConfiguration>(endpoint, null, "/help/configuration");
693 public Task<LazyJson<TwitterUploadMediaResult>> MediaUpload(IMediaItem media)
695 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
696 var paramMedia = new Dictionary<string, IMediaItem>
701 return this.apiConnection.PostLazyAsync<TwitterUploadMediaResult>(endpoint, null, paramMedia);
704 public Task MediaMetadataCreate(long mediaId, string altText)
706 var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json");
708 var escapedAltText = EscapeJsonString(altText);
709 var json = $@"{{""media_id"": ""{mediaId}"", ""alt_text"": {{""text"": ""{escapedAltText}""}}}}";
711 return this.apiConnection.PostJsonAsync(endpoint, json);
714 public Task<Stream> UserStreams(string replies = null, string track = null)
716 var endpoint = new Uri("https://userstream.twitter.com/1.1/user.json");
717 var param = new Dictionary<string, string>();
719 if (!string.IsNullOrEmpty(replies))
720 param["replies"] = replies;
721 if (!string.IsNullOrEmpty(track))
722 param["track"] = track;
724 return this.apiConnection.GetStreamingStreamAsync(endpoint, param);
727 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
729 return ((TwitterApiConnection)this.apiConnection).CreateOAuthEchoHandler(authServiceProvider, realm);
732 public void Dispose()
734 this.apiConnection?.Dispose();
737 /// <summary>JSON に出力する文字列を ECMA-404 に従ってエスケープする</summary>
738 public static string EscapeJsonString(string rawText)
740 var builder = new StringBuilder(rawText.Length + 20);
742 foreach (var c in rawText)
744 if (c <= 0x1F || char.IsSurrogate(c))
745 builder.AppendFormat(@"\u{0:X4}", (int)c);
746 else if (c == '\\' || c == '\"')
747 builder.Append('\\').Append(c);
752 return builder.ToString();