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;
29 using System.Threading;
30 using System.Threading.Tasks;
31 using OpenTween.Api.DataModel;
32 using OpenTween.Connection;
34 namespace OpenTween.Api
36 public sealed class TwitterApi : IDisposable
38 public long CurrentUserId { get; private set; }
39 public string CurrentScreenName { get; private set; } = "";
41 public IApiConnection Connection => this.apiConnection ?? throw new InvalidOperationException();
43 internal IApiConnection? apiConnection;
45 private readonly ApiKey consumerKey;
46 private readonly ApiKey consumerSecret;
48 public TwitterApi(ApiKey consumerKey, ApiKey consumerSecret)
50 this.consumerKey = consumerKey;
51 this.consumerSecret = consumerSecret;
54 public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
56 var newInstance = new TwitterApiConnection(this.consumerKey, this.consumerSecret, accessToken, accessSecret);
57 var oldInstance = Interlocked.Exchange(ref this.apiConnection, newInstance);
58 oldInstance?.Dispose();
60 this.CurrentUserId = userId;
61 this.CurrentScreenName = screenName;
64 public Task<TwitterStatus[]> StatusesHomeTimeline(int? count = null, long? maxId = null, long? sinceId = null)
66 var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative);
67 var param = new Dictionary<string, string>
69 ["include_entities"] = "true",
70 ["include_ext_alt_text"] = "true",
71 ["tweet_mode"] = "extended",
75 param["count"] = count.ToString();
77 param["max_id"] = maxId.ToString();
79 param["since_id"] = sinceId.ToString();
81 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/home_timeline");
84 public Task<TwitterStatus[]> StatusesMentionsTimeline(int? count = null, long? maxId = null, long? sinceId = null)
86 var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative);
87 var param = new Dictionary<string, string>
89 ["include_entities"] = "true",
90 ["include_ext_alt_text"] = "true",
91 ["tweet_mode"] = "extended",
95 param["count"] = count.ToString();
97 param["max_id"] = maxId.ToString();
99 param["since_id"] = sinceId.ToString();
101 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/mentions_timeline");
104 public Task<TwitterStatus[]> StatusesUserTimeline(string screenName, int? count = null, long? maxId = null, long? sinceId = null)
106 var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative);
107 var param = new Dictionary<string, string>
109 ["screen_name"] = screenName,
110 ["include_rts"] = "true",
111 ["include_entities"] = "true",
112 ["include_ext_alt_text"] = "true",
113 ["tweet_mode"] = "extended",
117 param["count"] = count.ToString();
119 param["max_id"] = maxId.ToString();
121 param["since_id"] = sinceId.ToString();
123 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/user_timeline");
126 public Task<TwitterStatus> StatusesShow(long statusId)
128 var endpoint = new Uri("statuses/show.json", UriKind.Relative);
129 var param = new Dictionary<string, string>
131 ["id"] = statusId.ToString(),
132 ["include_entities"] = "true",
133 ["include_ext_alt_text"] = "true",
134 ["tweet_mode"] = "extended",
137 return this.Connection.GetAsync<TwitterStatus>(endpoint, param, "/statuses/show/:id");
140 public Task<LazyJson<TwitterStatus>> StatusesUpdate(string status, long? replyToId, IReadOnlyList<long>? mediaIds,
141 bool? autoPopulateReplyMetadata = null, IReadOnlyList<long>? excludeReplyUserIds = null, string? attachmentUrl = null)
143 var endpoint = new Uri("statuses/update.json", UriKind.Relative);
144 var param = new Dictionary<string, string>
147 ["include_entities"] = "true",
148 ["include_ext_alt_text"] = "true",
149 ["tweet_mode"] = "extended",
152 if (replyToId != null)
153 param["in_reply_to_status_id"] = replyToId.ToString();
154 if (mediaIds != null && mediaIds.Count > 0)
155 param.Add("media_ids", string.Join(",", mediaIds));
156 if (autoPopulateReplyMetadata != null)
157 param["auto_populate_reply_metadata"] = autoPopulateReplyMetadata.Value ? "true" : "false";
158 if (excludeReplyUserIds != null && excludeReplyUserIds.Count > 0)
159 param["exclude_reply_user_ids"] = string.Join(",", excludeReplyUserIds);
160 if (attachmentUrl != null)
161 param["attachment_url"] = attachmentUrl;
163 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
166 public Task<LazyJson<TwitterStatus>> StatusesDestroy(long statusId)
168 var endpoint = new Uri("statuses/destroy.json", UriKind.Relative);
169 var param = new Dictionary<string, string>
171 ["id"] = statusId.ToString(),
174 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
177 public Task<LazyJson<TwitterStatus>> StatusesRetweet(long statusId)
179 var endpoint = new Uri("statuses/retweet.json", UriKind.Relative);
180 var param = new Dictionary<string, string>
182 ["id"] = statusId.ToString(),
183 ["include_entities"] = "true",
184 ["include_ext_alt_text"] = "true",
185 ["tweet_mode"] = "extended",
188 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
191 public Task<TwitterSearchResult> SearchTweets(string query, string? lang = null, int? count = null, long? maxId = null, long? sinceId = null)
193 var endpoint = new Uri("search/tweets.json", UriKind.Relative);
194 var param = new Dictionary<string, string>
197 ["result_type"] = "recent",
198 ["include_entities"] = "true",
199 ["include_ext_alt_text"] = "true",
200 ["tweet_mode"] = "extended",
204 param["lang"] = lang;
206 param["count"] = count.ToString();
208 param["max_id"] = maxId.ToString();
210 param["since_id"] = sinceId.ToString();
212 return this.Connection.GetAsync<TwitterSearchResult>(endpoint, param, "/search/tweets");
215 public Task<TwitterLists> ListsOwnerships(string screenName, long? cursor = null, int? count = null)
217 var endpoint = new Uri("lists/ownerships.json", UriKind.Relative);
218 var param = new Dictionary<string, string>
220 ["screen_name"] = screenName,
224 param["cursor"] = cursor.ToString();
226 param["count"] = count.ToString();
228 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/ownerships");
231 public Task<TwitterLists> ListsSubscriptions(string screenName, long? cursor = null, int? count = null)
233 var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative);
234 var param = new Dictionary<string, string>
236 ["screen_name"] = screenName,
240 param["cursor"] = cursor.ToString();
242 param["count"] = count.ToString();
244 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/subscriptions");
247 public Task<TwitterLists> ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null)
249 var endpoint = new Uri("lists/memberships.json", UriKind.Relative);
250 var param = new Dictionary<string, string>
252 ["screen_name"] = screenName,
256 param["cursor"] = cursor.ToString();
258 param["count"] = count.ToString();
259 if (filterToOwnedLists != null)
260 param["filter_to_owned_lists"] = filterToOwnedLists.Value ? "true" : "false";
262 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/memberships");
265 public Task<LazyJson<TwitterList>> ListsCreate(string name, string? description = null, bool? @private = null)
267 var endpoint = new Uri("lists/create.json", UriKind.Relative);
268 var param = new Dictionary<string, string>
273 if (description != null)
274 param["description"] = description;
275 if (@private != null)
276 param["mode"] = @private.Value ? "private" : "public";
278 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
281 public Task<LazyJson<TwitterList>> ListsUpdate(long listId, string? name = null, string? description = null, bool? @private = null)
283 var endpoint = new Uri("lists/update.json", UriKind.Relative);
284 var param = new Dictionary<string, string>
286 ["list_id"] = listId.ToString(),
290 param["name"] = name;
291 if (description != null)
292 param["description"] = description;
293 if (@private != null)
294 param["mode"] = @private.Value ? "private" : "public";
296 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
299 public Task<LazyJson<TwitterList>> ListsDestroy(long listId)
301 var endpoint = new Uri("lists/destroy.json", UriKind.Relative);
302 var param = new Dictionary<string, string>
304 ["list_id"] = listId.ToString(),
307 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
310 public Task<TwitterStatus[]> ListsStatuses(long listId, int? count = null, long? maxId = null, long? sinceId = null, bool? includeRTs = null)
312 var endpoint = new Uri("lists/statuses.json", UriKind.Relative);
313 var param = new Dictionary<string, string>
315 ["list_id"] = listId.ToString(),
316 ["include_entities"] = "true",
317 ["include_ext_alt_text"] = "true",
318 ["tweet_mode"] = "extended",
322 param["count"] = count.ToString();
324 param["max_id"] = maxId.ToString();
326 param["since_id"] = sinceId.ToString();
327 if (includeRTs != null)
328 param["include_rts"] = includeRTs.Value ? "true" : "false";
330 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/lists/statuses");
333 public Task<TwitterUsers> ListsMembers(long listId, long? cursor = null)
335 var endpoint = new Uri("lists/members.json", UriKind.Relative);
336 var param = new Dictionary<string, string>
338 ["list_id"] = listId.ToString(),
339 ["include_entities"] = "true",
340 ["include_ext_alt_text"] = "true",
341 ["tweet_mode"] = "extended",
345 param["cursor"] = cursor.ToString();
347 return this.Connection.GetAsync<TwitterUsers>(endpoint, param, "/lists/members");
350 public Task<TwitterUser> ListsMembersShow(long listId, string screenName)
352 var endpoint = new Uri("lists/members/show.json", UriKind.Relative);
353 var param = new Dictionary<string, string>
355 ["list_id"] = listId.ToString(),
356 ["screen_name"] = screenName,
357 ["include_entities"] = "true",
358 ["include_ext_alt_text"] = "true",
359 ["tweet_mode"] = "extended",
362 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/lists/members/show");
365 public Task<LazyJson<TwitterUser>> ListsMembersCreate(long listId, string screenName)
367 var endpoint = new Uri("lists/members/create.json", UriKind.Relative);
368 var param = new Dictionary<string, string>
370 ["list_id"] = listId.ToString(),
371 ["screen_name"] = screenName,
372 ["include_entities"] = "true",
373 ["include_ext_alt_text"] = "true",
374 ["tweet_mode"] = "extended",
377 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
380 public Task<LazyJson<TwitterUser>> ListsMembersDestroy(long listId, string screenName)
382 var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative);
383 var param = new Dictionary<string, string>
385 ["list_id"] = listId.ToString(),
386 ["screen_name"] = screenName,
387 ["include_entities"] = "true",
388 ["include_ext_alt_text"] = "true",
389 ["tweet_mode"] = "extended",
392 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
395 public Task<TwitterMessageEventList> DirectMessagesEventsList(int? count = null, string? cursor = null)
397 var endpoint = new Uri("direct_messages/events/list.json", UriKind.Relative);
398 var param = new Dictionary<string, string>();
401 param["count"] = count.ToString();
403 param["cursor"] = cursor;
405 return this.Connection.GetAsync<TwitterMessageEventList>(endpoint, param, "/direct_messages/events/list");
408 public Task<LazyJson<TwitterMessageEventSingle>> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null)
410 var endpoint = new Uri("direct_messages/events/new.json", UriKind.Relative);
415 attachment = "," + $@"
419 ""id"": ""{JsonUtils.EscapeJsonString(mediaId.ToString())}""
426 ""type"": ""message_create"",
427 ""message_create"": {{
429 ""recipient_id"": ""{JsonUtils.EscapeJsonString(recipientId.ToString())}""
432 ""text"": ""{JsonUtils.EscapeJsonString(text)}""{attachment}
438 return this.Connection.PostJsonAsync<TwitterMessageEventSingle>(endpoint, json);
441 public Task DirectMessagesEventsDestroy(string eventId)
443 var endpoint = new Uri("direct_messages/events/destroy.json", UriKind.Relative);
444 var param = new Dictionary<string, string>
446 ["id"] = eventId.ToString(),
449 // なぜか application/x-www-form-urlencoded でパラメーターを送ると Bad Request になる謎仕様
450 endpoint = new Uri(endpoint.OriginalString + "?" + MyCommon.BuildQueryString(param), UriKind.Relative);
452 return this.Connection.DeleteAsync(endpoint);
455 public Task<TwitterUser> UsersShow(string screenName)
457 var endpoint = new Uri("users/show.json", UriKind.Relative);
458 var param = new Dictionary<string, string>
460 ["screen_name"] = screenName,
461 ["include_entities"] = "true",
462 ["include_ext_alt_text"] = "true",
463 ["tweet_mode"] = "extended",
466 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/users/show/:id");
469 public Task<TwitterUser[]> UsersLookup(IReadOnlyList<string> userIds)
471 var endpoint = new Uri("users/lookup.json", UriKind.Relative);
472 var param = new Dictionary<string, string>
474 ["user_id"] = string.Join(",", userIds),
475 ["include_entities"] = "true",
476 ["include_ext_alt_text"] = "true",
477 ["tweet_mode"] = "extended",
480 return this.Connection.GetAsync<TwitterUser[]>(endpoint, param, "/users/lookup");
483 public Task<LazyJson<TwitterUser>> UsersReportSpam(string screenName)
485 var endpoint = new Uri("users/report_spam.json", UriKind.Relative);
486 var param = new Dictionary<string, string>
488 ["screen_name"] = screenName,
489 ["tweet_mode"] = "extended",
492 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
495 public Task<TwitterStatus[]> FavoritesList(int? count = null, long? maxId = null, long? sinceId = null)
497 var endpoint = new Uri("favorites/list.json", UriKind.Relative);
498 var param = new Dictionary<string, string>
500 ["include_entities"] = "true",
501 ["include_ext_alt_text"] = "true",
502 ["tweet_mode"] = "extended",
506 param["count"] = count.ToString();
508 param["max_id"] = maxId.ToString();
510 param["since_id"] = sinceId.ToString();
512 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/favorites/list");
515 public Task<LazyJson<TwitterStatus>> FavoritesCreate(long statusId)
517 var endpoint = new Uri("favorites/create.json", UriKind.Relative);
518 var param = new Dictionary<string, string>
520 ["id"] = statusId.ToString(),
521 ["tweet_mode"] = "extended",
524 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
527 public Task<LazyJson<TwitterStatus>> FavoritesDestroy(long statusId)
529 var endpoint = new Uri("favorites/destroy.json", UriKind.Relative);
530 var param = new Dictionary<string, string>
532 ["id"] = statusId.ToString(),
533 ["tweet_mode"] = "extended",
536 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
539 public Task<TwitterFriendship> FriendshipsShow(string sourceScreenName, string targetScreenName)
541 var endpoint = new Uri("friendships/show.json", UriKind.Relative);
542 var param = new Dictionary<string, string>
544 ["source_screen_name"] = sourceScreenName,
545 ["target_screen_name"] = targetScreenName,
548 return this.Connection.GetAsync<TwitterFriendship>(endpoint, param, "/friendships/show");
551 public Task<LazyJson<TwitterFriendship>> FriendshipsCreate(string screenName)
553 var endpoint = new Uri("friendships/create.json", UriKind.Relative);
554 var param = new Dictionary<string, string>
556 ["screen_name"] = screenName,
559 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
562 public Task<LazyJson<TwitterFriendship>> FriendshipsDestroy(string screenName)
564 var endpoint = new Uri("friendships/destroy.json", UriKind.Relative);
565 var param = new Dictionary<string, string>
567 ["screen_name"] = screenName,
570 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
573 public Task<long[]> NoRetweetIds()
575 var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative);
577 return this.Connection.GetAsync<long[]>(endpoint, null, "/friendships/no_retweets/ids");
580 public Task<TwitterIds> FollowersIds(long? cursor = null)
582 var endpoint = new Uri("followers/ids.json", UriKind.Relative);
583 var param = new Dictionary<string, string>();
586 param["cursor"] = cursor.ToString();
588 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/followers/ids");
591 public Task<TwitterIds> MutesUsersIds(long? cursor = null)
593 var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative);
594 var param = new Dictionary<string, string>();
597 param["cursor"] = cursor.ToString();
599 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/mutes/users/ids");
602 public Task<TwitterIds> BlocksIds(long? cursor = null)
604 var endpoint = new Uri("blocks/ids.json", UriKind.Relative);
605 var param = new Dictionary<string, string>();
608 param["cursor"] = cursor.ToString();
610 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/blocks/ids");
613 public Task<LazyJson<TwitterUser>> BlocksCreate(string screenName)
615 var endpoint = new Uri("blocks/create.json", UriKind.Relative);
616 var param = new Dictionary<string, string>
618 ["screen_name"] = screenName,
619 ["tweet_mode"] = "extended",
622 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
625 public Task<LazyJson<TwitterUser>> BlocksDestroy(string screenName)
627 var endpoint = new Uri("blocks/destroy.json", UriKind.Relative);
628 var param = new Dictionary<string, string>
630 ["screen_name"] = screenName,
631 ["tweet_mode"] = "extended",
634 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
637 public async Task<TwitterUser> AccountVerifyCredentials()
639 var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative);
640 var param = new Dictionary<string, string>
642 ["include_entities"] = "true",
643 ["include_ext_alt_text"] = "true",
644 ["tweet_mode"] = "extended",
647 var user = await this.Connection.GetAsync<TwitterUser>(endpoint, param, "/account/verify_credentials")
648 .ConfigureAwait(false);
650 this.CurrentUserId = user.Id;
651 this.CurrentScreenName = user.ScreenName;
656 public Task<LazyJson<TwitterUser>> AccountUpdateProfile(string name, string url, string? location, string? description)
658 var endpoint = new Uri("account/update_profile.json", UriKind.Relative);
659 var param = new Dictionary<string, string>
661 ["include_entities"] = "true",
662 ["include_ext_alt_text"] = "true",
663 ["tweet_mode"] = "extended",
667 param["name"] = name;
670 if (location != null)
671 param["location"] = location;
673 if (description != null)
675 // name, location, description に含まれる < > " の文字はTwitter側で除去されるが、
676 // twitter.com の挙動では description でのみ < 等の文字参照を使って表示することができる
677 var escapedDescription = description.Replace("<", "<").Replace(">", ">").Replace("\"", """);
678 param["description"] = escapedDescription;
681 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
684 public Task<LazyJson<TwitterUser>> AccountUpdateProfileImage(IMediaItem image)
686 var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative);
687 var param = new Dictionary<string, string>
689 ["include_entities"] = "true",
690 ["include_ext_alt_text"] = "true",
691 ["tweet_mode"] = "extended",
693 var paramMedia = new Dictionary<string, IMediaItem>
698 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param, paramMedia);
701 public Task<TwitterRateLimits> ApplicationRateLimitStatus()
703 var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative);
705 return this.Connection.GetAsync<TwitterRateLimits>(endpoint, null, "/application/rate_limit_status");
708 public Task<TwitterConfiguration> Configuration()
710 var endpoint = new Uri("help/configuration.json", UriKind.Relative);
712 return this.Connection.GetAsync<TwitterConfiguration>(endpoint, null, "/help/configuration");
715 public Task<LazyJson<TwitterUploadMediaInit>> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null)
717 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
718 var param = new Dictionary<string, string>
720 ["command"] = "INIT",
721 ["total_bytes"] = totalBytes.ToString(),
722 ["media_type"] = mediaType,
725 if (mediaCategory != null)
726 param["media_category"] = mediaCategory;
728 return this.Connection.PostLazyAsync<TwitterUploadMediaInit>(endpoint, param);
731 public Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media)
733 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
734 var param = new Dictionary<string, string>
736 ["command"] = "APPEND",
737 ["media_id"] = mediaId.ToString(),
738 ["segment_index"] = segmentIndex.ToString(),
740 var paramMedia = new Dictionary<string, IMediaItem>
745 return this.Connection.PostAsync(endpoint, param, paramMedia);
748 public Task<LazyJson<TwitterUploadMediaResult>> MediaUploadFinalize(long mediaId)
750 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
751 var param = new Dictionary<string, string>
753 ["command"] = "FINALIZE",
754 ["media_id"] = mediaId.ToString(),
757 return this.Connection.PostLazyAsync<TwitterUploadMediaResult>(endpoint, param);
760 public Task<TwitterUploadMediaResult> MediaUploadStatus(long mediaId)
762 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
763 var param = new Dictionary<string, string>
765 ["command"] = "STATUS",
766 ["media_id"] = mediaId.ToString(),
769 return this.Connection.GetAsync<TwitterUploadMediaResult>(endpoint, param, endpointName: null);
772 public Task MediaMetadataCreate(long mediaId, string altText)
774 var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json");
776 var escapedAltText = JsonUtils.EscapeJsonString(altText);
777 var json = $@"{{""media_id"": ""{mediaId}"", ""alt_text"": {{""text"": ""{escapedAltText}""}}}}";
779 return this.Connection.PostJsonAsync(endpoint, json);
782 public TwitterStreamObservable UserStreams(string? replies = null, string? track = null)
784 var endpoint = new Uri("https://userstream.twitter.com/1.1/user.json");
785 var param = new Dictionary<string, string>();
787 if (!MyCommon.IsNullOrEmpty(replies))
788 param["replies"] = replies;
789 if (!MyCommon.IsNullOrEmpty(track))
790 param["track"] = track;
792 Task<Stream> openStream()
793 => this.Connection.GetStreamingStreamAsync(endpoint, param);
795 return new TwitterStreamObservable(openStream);
798 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri? realm = null)
799 => ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(authServiceProvider, realm);
801 public void Dispose()
802 => this.apiConnection?.Dispose();