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.
25 using System.Collections.Generic;
28 using System.Net.Http;
30 using System.Threading;
31 using System.Threading.Tasks;
32 using OpenTween.Api.DataModel;
33 using OpenTween.Connection;
34 using OpenTween.Models;
36 namespace OpenTween.Api
38 public sealed class TwitterApi : IDisposable
40 public long CurrentUserId { get; private set; }
42 public string CurrentScreenName { get; private set; } = "";
44 public IApiConnectionLegacy Connection => this.ApiConnection ?? throw new InvalidOperationException();
46 internal IApiConnectionLegacy? ApiConnection;
48 public TwitterAppToken AppToken { get; private set; } = TwitterAppToken.GetDefault();
54 public TwitterApi(ApiKey consumerKey, ApiKey consumerSecret)
58 AuthType = APIAuthType.OAuth1,
59 OAuth1CustomConsumerKey = consumerKey,
60 OAuth1CustomConsumerSecret = consumerSecret,
64 public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
65 => this.Initialize(new TwitterCredentialOAuth1(this.AppToken, accessToken, accessSecret), userId, screenName);
67 public void Initialize(ITwitterCredential credential, long userId, string screenName)
69 this.AppToken = credential.AppToken;
71 var newInstance = new TwitterApiConnection(credential);
72 var oldInstance = Interlocked.Exchange(ref this.ApiConnection, newInstance);
73 oldInstance?.Dispose();
75 this.CurrentUserId = userId;
76 this.CurrentScreenName = screenName;
79 public Task<TwitterStatus[]> StatusesHomeTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null)
81 var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative);
82 var param = new Dictionary<string, string>
84 ["include_entities"] = "true",
85 ["include_ext_alt_text"] = "true",
86 ["tweet_mode"] = "extended",
90 param["count"] = count.ToString();
92 param["max_id"] = maxId.Id;
94 param["since_id"] = sinceId.Id;
96 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/home_timeline");
99 public Task<TwitterStatus[]> StatusesMentionsTimeline(int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null)
101 var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative);
102 var param = new Dictionary<string, string>
104 ["include_entities"] = "true",
105 ["include_ext_alt_text"] = "true",
106 ["tweet_mode"] = "extended",
110 param["count"] = count.ToString();
112 param["max_id"] = maxId.Id;
114 param["since_id"] = sinceId.Id;
116 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/mentions_timeline");
119 public Task<TwitterStatus[]> StatusesUserTimeline(string screenName, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null)
121 var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative);
122 var param = new Dictionary<string, string>
124 ["screen_name"] = screenName,
125 ["include_rts"] = "true",
126 ["include_entities"] = "true",
127 ["include_ext_alt_text"] = "true",
128 ["tweet_mode"] = "extended",
132 param["count"] = count.ToString();
134 param["max_id"] = maxId.Id;
136 param["since_id"] = sinceId.Id;
138 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/user_timeline");
141 public Task<TwitterStatus> StatusesShow(TwitterStatusId statusId)
143 var endpoint = new Uri("statuses/show.json", UriKind.Relative);
144 var param = new Dictionary<string, string>
146 ["id"] = statusId.Id,
147 ["include_entities"] = "true",
148 ["include_ext_alt_text"] = "true",
149 ["tweet_mode"] = "extended",
152 return this.Connection.GetAsync<TwitterStatus>(endpoint, param, "/statuses/show/:id");
155 public Task<TwitterStatus[]> StatusesLookup(IReadOnlyList<string> statusIds)
157 var endpoint = new Uri("statuses/lookup.json", UriKind.Relative);
158 var param = new Dictionary<string, string>
160 ["id"] = string.Join(",", statusIds),
161 ["include_entities"] = "true",
162 ["include_ext_alt_text"] = "true",
163 ["tweet_mode"] = "extended",
166 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/lookup");
169 public Task<LazyJson<TwitterStatus>> StatusesUpdate(
171 TwitterStatusId? replyToId,
172 IReadOnlyList<long>? mediaIds,
173 bool? autoPopulateReplyMetadata = null,
174 IReadOnlyList<long>? excludeReplyUserIds = null,
175 string? attachmentUrl = null)
177 var endpoint = new Uri("statuses/update.json", UriKind.Relative);
178 var param = new Dictionary<string, string>
181 ["include_entities"] = "true",
182 ["include_ext_alt_text"] = "true",
183 ["tweet_mode"] = "extended",
186 if (replyToId != null)
187 param["in_reply_to_status_id"] = replyToId.Id;
188 if (mediaIds != null && mediaIds.Count > 0)
189 param.Add("media_ids", string.Join(",", mediaIds));
190 if (autoPopulateReplyMetadata != null)
191 param["auto_populate_reply_metadata"] = autoPopulateReplyMetadata.Value ? "true" : "false";
192 if (excludeReplyUserIds != null && excludeReplyUserIds.Count > 0)
193 param["exclude_reply_user_ids"] = string.Join(",", excludeReplyUserIds);
194 if (attachmentUrl != null)
195 param["attachment_url"] = attachmentUrl;
197 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
200 public Task<LazyJson<TwitterStatus>> StatusesDestroy(TwitterStatusId statusId)
202 var endpoint = new Uri("statuses/destroy.json", UriKind.Relative);
203 var param = new Dictionary<string, string>
205 ["id"] = statusId.Id,
208 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
211 public Task<LazyJson<TwitterStatus>> StatusesRetweet(TwitterStatusId statusId)
213 var endpoint = new Uri("statuses/retweet.json", UriKind.Relative);
214 var param = new Dictionary<string, string>
216 ["id"] = statusId.Id,
217 ["include_entities"] = "true",
218 ["include_ext_alt_text"] = "true",
219 ["tweet_mode"] = "extended",
222 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
225 public Task<TwitterSearchResult> SearchTweets(string query, string? lang = null, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null)
227 var endpoint = new Uri("search/tweets.json", UriKind.Relative);
228 var param = new Dictionary<string, string>
231 ["result_type"] = "recent",
232 ["include_entities"] = "true",
233 ["include_ext_alt_text"] = "true",
234 ["tweet_mode"] = "extended",
238 param["lang"] = lang;
240 param["count"] = count.ToString();
242 param["max_id"] = maxId.Id;
244 param["since_id"] = sinceId.Id;
246 return this.Connection.GetAsync<TwitterSearchResult>(endpoint, param, "/search/tweets");
249 public Task<TwitterLists> ListsOwnerships(string screenName, long? cursor = null, int? count = null)
251 var endpoint = new Uri("lists/ownerships.json", UriKind.Relative);
252 var param = new Dictionary<string, string>
254 ["screen_name"] = screenName,
258 param["cursor"] = cursor.ToString();
260 param["count"] = count.ToString();
262 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/ownerships");
265 public Task<TwitterLists> ListsSubscriptions(string screenName, long? cursor = null, int? count = null)
267 var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative);
268 var param = new Dictionary<string, string>
270 ["screen_name"] = screenName,
274 param["cursor"] = cursor.ToString();
276 param["count"] = count.ToString();
278 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/subscriptions");
281 public Task<TwitterLists> ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null)
283 var endpoint = new Uri("lists/memberships.json", UriKind.Relative);
284 var param = new Dictionary<string, string>
286 ["screen_name"] = screenName,
290 param["cursor"] = cursor.ToString();
292 param["count"] = count.ToString();
293 if (filterToOwnedLists != null)
294 param["filter_to_owned_lists"] = filterToOwnedLists.Value ? "true" : "false";
296 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/memberships");
299 public Task<LazyJson<TwitterList>> ListsCreate(string name, string? description = null, bool? @private = null)
301 var endpoint = new Uri("lists/create.json", UriKind.Relative);
302 var param = new Dictionary<string, string>
307 if (description != null)
308 param["description"] = description;
309 if (@private != null)
310 param["mode"] = @private.Value ? "private" : "public";
312 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
315 public Task<LazyJson<TwitterList>> ListsUpdate(long listId, string? name = null, string? description = null, bool? @private = null)
317 var endpoint = new Uri("lists/update.json", UriKind.Relative);
318 var param = new Dictionary<string, string>
320 ["list_id"] = listId.ToString(),
324 param["name"] = name;
325 if (description != null)
326 param["description"] = description;
327 if (@private != null)
328 param["mode"] = @private.Value ? "private" : "public";
330 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
333 public Task<LazyJson<TwitterList>> ListsDestroy(long listId)
335 var endpoint = new Uri("lists/destroy.json", UriKind.Relative);
336 var param = new Dictionary<string, string>
338 ["list_id"] = listId.ToString(),
341 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
344 public Task<TwitterStatus[]> ListsStatuses(long listId, int? count = null, TwitterStatusId? maxId = null, TwitterStatusId? sinceId = null, bool? includeRTs = null)
346 var endpoint = new Uri("lists/statuses.json", UriKind.Relative);
347 var param = new Dictionary<string, string>
349 ["list_id"] = listId.ToString(),
350 ["include_entities"] = "true",
351 ["include_ext_alt_text"] = "true",
352 ["tweet_mode"] = "extended",
356 param["count"] = count.ToString();
358 param["max_id"] = maxId.Id;
360 param["since_id"] = sinceId.Id;
361 if (includeRTs != null)
362 param["include_rts"] = includeRTs.Value ? "true" : "false";
364 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/lists/statuses");
367 public Task<TwitterUsers> ListsMembers(long listId, long? cursor = null)
369 var endpoint = new Uri("lists/members.json", UriKind.Relative);
370 var param = new Dictionary<string, string>
372 ["list_id"] = listId.ToString(),
373 ["include_entities"] = "true",
374 ["include_ext_alt_text"] = "true",
375 ["tweet_mode"] = "extended",
379 param["cursor"] = cursor.ToString();
381 return this.Connection.GetAsync<TwitterUsers>(endpoint, param, "/lists/members");
384 public Task<TwitterUser> ListsMembersShow(long listId, string screenName)
386 var endpoint = new Uri("lists/members/show.json", UriKind.Relative);
387 var param = new Dictionary<string, string>
389 ["list_id"] = listId.ToString(),
390 ["screen_name"] = screenName,
391 ["include_entities"] = "true",
392 ["include_ext_alt_text"] = "true",
393 ["tweet_mode"] = "extended",
396 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/lists/members/show");
399 public Task<LazyJson<TwitterUser>> ListsMembersCreate(long listId, string screenName)
401 var endpoint = new Uri("lists/members/create.json", UriKind.Relative);
402 var param = new Dictionary<string, string>
404 ["list_id"] = listId.ToString(),
405 ["screen_name"] = screenName,
406 ["include_entities"] = "true",
407 ["include_ext_alt_text"] = "true",
408 ["tweet_mode"] = "extended",
411 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
414 public Task<LazyJson<TwitterUser>> ListsMembersDestroy(long listId, string screenName)
416 var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative);
417 var param = new Dictionary<string, string>
419 ["list_id"] = listId.ToString(),
420 ["screen_name"] = screenName,
421 ["include_entities"] = "true",
422 ["include_ext_alt_text"] = "true",
423 ["tweet_mode"] = "extended",
426 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
429 public Task<TwitterMessageEventList> DirectMessagesEventsList(int? count = null, string? cursor = null)
431 var endpoint = new Uri("direct_messages/events/list.json", UriKind.Relative);
432 var param = new Dictionary<string, string>();
435 param["count"] = count.ToString();
437 param["cursor"] = cursor;
439 return this.Connection.GetAsync<TwitterMessageEventList>(endpoint, param, "/direct_messages/events/list");
442 public Task<LazyJson<TwitterMessageEventSingle>> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null)
444 var endpoint = new Uri("direct_messages/events/new.json", UriKind.Relative);
449 attachment = ",\r\n" + $$"""
453 "id": "{{JsonUtils.EscapeJsonString(mediaId.ToString())}}"
462 "type": "message_create",
465 "recipient_id": "{{JsonUtils.EscapeJsonString(recipientId.ToString())}}"
468 "text": "{{JsonUtils.EscapeJsonString(text)}}"{{attachment}}
475 return this.Connection.PostJsonAsync<TwitterMessageEventSingle>(endpoint, json);
478 public Task DirectMessagesEventsDestroy(TwitterDirectMessageId eventId)
480 var endpoint = new Uri("direct_messages/events/destroy.json", UriKind.Relative);
481 var param = new Dictionary<string, string>
486 // なぜか application/x-www-form-urlencoded でパラメーターを送ると Bad Request になる謎仕様
487 endpoint = new Uri(endpoint.OriginalString + "?" + MyCommon.BuildQueryString(param), UriKind.Relative);
489 return this.Connection.DeleteAsync(endpoint);
492 public Task<TwitterUser> UsersShow(string screenName)
494 var endpoint = new Uri("users/show.json", UriKind.Relative);
495 var param = new Dictionary<string, string>
497 ["screen_name"] = screenName,
498 ["include_entities"] = "true",
499 ["include_ext_alt_text"] = "true",
500 ["tweet_mode"] = "extended",
503 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/users/show/:id");
506 public Task<TwitterUser[]> UsersLookup(IReadOnlyList<string> userIds)
508 var endpoint = new Uri("users/lookup.json", UriKind.Relative);
509 var param = new Dictionary<string, string>
511 ["user_id"] = string.Join(",", userIds),
512 ["include_entities"] = "true",
513 ["include_ext_alt_text"] = "true",
514 ["tweet_mode"] = "extended",
517 return this.Connection.GetAsync<TwitterUser[]>(endpoint, param, "/users/lookup");
520 public Task<LazyJson<TwitterUser>> UsersReportSpam(string screenName)
522 var endpoint = new Uri("users/report_spam.json", UriKind.Relative);
523 var param = new Dictionary<string, string>
525 ["screen_name"] = screenName,
526 ["tweet_mode"] = "extended",
529 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
532 public Task<TwitterStatus[]> FavoritesList(int? count = null, long? maxId = null, long? sinceId = null)
534 var endpoint = new Uri("favorites/list.json", UriKind.Relative);
535 var param = new Dictionary<string, string>
537 ["include_entities"] = "true",
538 ["include_ext_alt_text"] = "true",
539 ["tweet_mode"] = "extended",
543 param["count"] = count.ToString();
545 param["max_id"] = maxId.ToString();
547 param["since_id"] = sinceId.ToString();
549 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/favorites/list");
552 public Task<LazyJson<TwitterStatus>> FavoritesCreate(TwitterStatusId statusId)
554 var endpoint = new Uri("favorites/create.json", UriKind.Relative);
555 var param = new Dictionary<string, string>
557 ["id"] = statusId.Id,
558 ["tweet_mode"] = "extended",
561 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
564 public Task<LazyJson<TwitterStatus>> FavoritesDestroy(TwitterStatusId statusId)
566 var endpoint = new Uri("favorites/destroy.json", UriKind.Relative);
567 var param = new Dictionary<string, string>
569 ["id"] = statusId.Id,
570 ["tweet_mode"] = "extended",
573 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
576 public Task<TwitterFriendship> FriendshipsShow(string sourceScreenName, string targetScreenName)
578 var endpoint = new Uri("friendships/show.json", UriKind.Relative);
579 var param = new Dictionary<string, string>
581 ["source_screen_name"] = sourceScreenName,
582 ["target_screen_name"] = targetScreenName,
585 return this.Connection.GetAsync<TwitterFriendship>(endpoint, param, "/friendships/show");
588 public Task<LazyJson<TwitterFriendship>> FriendshipsCreate(string screenName)
590 var endpoint = new Uri("friendships/create.json", UriKind.Relative);
591 var param = new Dictionary<string, string>
593 ["screen_name"] = screenName,
596 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
599 public Task<LazyJson<TwitterFriendship>> FriendshipsDestroy(string screenName)
601 var endpoint = new Uri("friendships/destroy.json", UriKind.Relative);
602 var param = new Dictionary<string, string>
604 ["screen_name"] = screenName,
607 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
610 public Task<long[]> NoRetweetIds()
612 var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative);
614 return this.Connection.GetAsync<long[]>(endpoint, null, "/friendships/no_retweets/ids");
617 public Task<TwitterIds> FollowersIds(long? cursor = null)
619 var endpoint = new Uri("followers/ids.json", UriKind.Relative);
620 var param = new Dictionary<string, string>();
623 param["cursor"] = cursor.ToString();
625 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/followers/ids");
628 public Task<TwitterIds> MutesUsersIds(long? cursor = null)
630 var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative);
631 var param = new Dictionary<string, string>();
634 param["cursor"] = cursor.ToString();
636 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/mutes/users/ids");
639 public Task<TwitterIds> BlocksIds(long? cursor = null)
641 var endpoint = new Uri("blocks/ids.json", UriKind.Relative);
642 var param = new Dictionary<string, string>();
645 param["cursor"] = cursor.ToString();
647 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/blocks/ids");
650 public Task<LazyJson<TwitterUser>> BlocksCreate(string screenName)
652 var endpoint = new Uri("blocks/create.json", UriKind.Relative);
653 var param = new Dictionary<string, string>
655 ["screen_name"] = screenName,
656 ["tweet_mode"] = "extended",
659 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
662 public Task<LazyJson<TwitterUser>> BlocksDestroy(string screenName)
664 var endpoint = new Uri("blocks/destroy.json", UriKind.Relative);
665 var param = new Dictionary<string, string>
667 ["screen_name"] = screenName,
668 ["tweet_mode"] = "extended",
671 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
674 public async Task<TwitterUser> AccountVerifyCredentials()
676 var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative);
677 var param = new Dictionary<string, string>
679 ["include_entities"] = "true",
680 ["include_ext_alt_text"] = "true",
681 ["tweet_mode"] = "extended",
684 var user = await this.Connection.GetAsync<TwitterUser>(endpoint, param, "/account/verify_credentials")
685 .ConfigureAwait(false);
687 this.CurrentUserId = user.Id;
688 this.CurrentScreenName = user.ScreenName;
693 public Task<LazyJson<TwitterUser>> AccountUpdateProfile(string name, string url, string? location, string? description)
695 var endpoint = new Uri("account/update_profile.json", UriKind.Relative);
696 var param = new Dictionary<string, string>
698 ["include_entities"] = "true",
699 ["include_ext_alt_text"] = "true",
700 ["tweet_mode"] = "extended",
704 param["name"] = name;
707 if (location != null)
708 param["location"] = location;
710 if (description != null)
712 // name, location, description に含まれる < > " の文字はTwitter側で除去されるが、
713 // twitter.com の挙動では description でのみ < 等の文字参照を使って表示することができる
714 var escapedDescription = description.Replace("<", "<").Replace(">", ">").Replace("\"", """);
715 param["description"] = escapedDescription;
718 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
721 public Task<LazyJson<TwitterUser>> AccountUpdateProfileImage(IMediaItem image)
723 var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative);
724 var param = new Dictionary<string, string>
726 ["include_entities"] = "true",
727 ["include_ext_alt_text"] = "true",
728 ["tweet_mode"] = "extended",
730 var paramMedia = new Dictionary<string, IMediaItem>
735 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param, paramMedia);
738 public Task<TwitterRateLimits> ApplicationRateLimitStatus()
740 var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative);
742 return this.Connection.GetAsync<TwitterRateLimits>(endpoint, null, "/application/rate_limit_status");
745 public Task<TwitterConfiguration> Configuration()
747 var endpoint = new Uri("help/configuration.json", UriKind.Relative);
749 return this.Connection.GetAsync<TwitterConfiguration>(endpoint, null, "/help/configuration");
752 public Task<LazyJson<TwitterUploadMediaInit>> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null)
754 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
755 var param = new Dictionary<string, string>
757 ["command"] = "INIT",
758 ["total_bytes"] = totalBytes.ToString(),
759 ["media_type"] = mediaType,
762 if (mediaCategory != null)
763 param["media_category"] = mediaCategory;
765 return this.Connection.PostLazyAsync<TwitterUploadMediaInit>(endpoint, param);
768 public Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media)
770 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
771 var param = new Dictionary<string, string>
773 ["command"] = "APPEND",
774 ["media_id"] = mediaId.ToString(),
775 ["segment_index"] = segmentIndex.ToString(),
777 var paramMedia = new Dictionary<string, IMediaItem>
782 return this.Connection.PostAsync(endpoint, param, paramMedia);
785 public Task<LazyJson<TwitterUploadMediaResult>> MediaUploadFinalize(long mediaId)
787 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
788 var param = new Dictionary<string, string>
790 ["command"] = "FINALIZE",
791 ["media_id"] = mediaId.ToString(),
794 return this.Connection.PostLazyAsync<TwitterUploadMediaResult>(endpoint, param);
797 public Task<TwitterUploadMediaResult> MediaUploadStatus(long mediaId)
799 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
800 var param = new Dictionary<string, string>
802 ["command"] = "STATUS",
803 ["media_id"] = mediaId.ToString(),
806 return this.Connection.GetAsync<TwitterUploadMediaResult>(endpoint, param, endpointName: null);
809 public Task MediaMetadataCreate(long mediaId, string altText)
811 var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json");
813 var escapedAltText = JsonUtils.EscapeJsonString(altText);
814 var json = $$$"""{"media_id": "{{{mediaId}}}", "alt_text": {"text": "{{{escapedAltText}}}"}}""";
816 return this.Connection.PostJsonAsync(endpoint, json);
819 public OAuthEchoHandler CreateOAuthEchoHandler(HttpMessageHandler innerHandler, Uri authServiceProvider, Uri? realm = null)
820 => ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(innerHandler, authServiceProvider, realm);
822 public void Dispose()
823 => this.ApiConnection?.Dispose();