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 public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
47 var newInstance = new TwitterApiConnection(accessToken, accessSecret);
48 var oldInstance = Interlocked.Exchange(ref this.apiConnection, newInstance);
49 oldInstance?.Dispose();
51 this.CurrentUserId = userId;
52 this.CurrentScreenName = screenName;
55 public Task<TwitterStatus[]> StatusesHomeTimeline(int? count = null, long? maxId = null, long? sinceId = null)
57 var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative);
58 var param = new Dictionary<string, string>
60 ["include_entities"] = "true",
61 ["include_ext_alt_text"] = "true",
62 ["tweet_mode"] = "extended",
66 param["count"] = count.ToString();
68 param["max_id"] = maxId.ToString();
70 param["since_id"] = sinceId.ToString();
72 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/home_timeline");
75 public Task<TwitterStatus[]> StatusesMentionsTimeline(int? count = null, long? maxId = null, long? sinceId = null)
77 var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative);
78 var param = new Dictionary<string, string>
80 ["include_entities"] = "true",
81 ["include_ext_alt_text"] = "true",
82 ["tweet_mode"] = "extended",
86 param["count"] = count.ToString();
88 param["max_id"] = maxId.ToString();
90 param["since_id"] = sinceId.ToString();
92 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/mentions_timeline");
95 public Task<TwitterStatus[]> StatusesUserTimeline(string screenName, int? count = null, long? maxId = null, long? sinceId = null)
97 var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative);
98 var param = new Dictionary<string, string>
100 ["screen_name"] = screenName,
101 ["include_rts"] = "true",
102 ["include_entities"] = "true",
103 ["include_ext_alt_text"] = "true",
104 ["tweet_mode"] = "extended",
108 param["count"] = count.ToString();
110 param["max_id"] = maxId.ToString();
112 param["since_id"] = sinceId.ToString();
114 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/statuses/user_timeline");
117 public Task<TwitterStatus> StatusesShow(long statusId)
119 var endpoint = new Uri("statuses/show.json", UriKind.Relative);
120 var param = new Dictionary<string, string>
122 ["id"] = statusId.ToString(),
123 ["include_entities"] = "true",
124 ["include_ext_alt_text"] = "true",
125 ["tweet_mode"] = "extended",
128 return this.Connection.GetAsync<TwitterStatus>(endpoint, param, "/statuses/show/:id");
131 public Task<LazyJson<TwitterStatus>> StatusesUpdate(string status, long? replyToId, IReadOnlyList<long>? mediaIds,
132 bool? autoPopulateReplyMetadata = null, IReadOnlyList<long>? excludeReplyUserIds = null, string? attachmentUrl = null)
134 var endpoint = new Uri("statuses/update.json", UriKind.Relative);
135 var param = new Dictionary<string, string>
138 ["include_entities"] = "true",
139 ["include_ext_alt_text"] = "true",
140 ["tweet_mode"] = "extended",
143 if (replyToId != null)
144 param["in_reply_to_status_id"] = replyToId.ToString();
145 if (mediaIds != null && mediaIds.Count > 0)
146 param.Add("media_ids", string.Join(",", mediaIds));
147 if (autoPopulateReplyMetadata != null)
148 param["auto_populate_reply_metadata"] = autoPopulateReplyMetadata.Value ? "true" : "false";
149 if (excludeReplyUserIds != null && excludeReplyUserIds.Count > 0)
150 param["exclude_reply_user_ids"] = string.Join(",", excludeReplyUserIds);
151 if (attachmentUrl != null)
152 param["attachment_url"] = attachmentUrl;
154 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
157 public Task<LazyJson<TwitterStatus>> StatusesDestroy(long statusId)
159 var endpoint = new Uri("statuses/destroy.json", UriKind.Relative);
160 var param = new Dictionary<string, string>
162 ["id"] = statusId.ToString(),
165 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
168 public Task<LazyJson<TwitterStatus>> StatusesRetweet(long statusId)
170 var endpoint = new Uri("statuses/retweet.json", UriKind.Relative);
171 var param = new Dictionary<string, string>
173 ["id"] = statusId.ToString(),
174 ["include_entities"] = "true",
175 ["include_ext_alt_text"] = "true",
176 ["tweet_mode"] = "extended",
179 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
182 public Task<TwitterSearchResult> SearchTweets(string query, string? lang = null, int? count = null, long? maxId = null, long? sinceId = null)
184 var endpoint = new Uri("search/tweets.json", UriKind.Relative);
185 var param = new Dictionary<string, string>
188 ["result_type"] = "recent",
189 ["include_entities"] = "true",
190 ["include_ext_alt_text"] = "true",
191 ["tweet_mode"] = "extended",
195 param["lang"] = lang;
197 param["count"] = count.ToString();
199 param["max_id"] = maxId.ToString();
201 param["since_id"] = sinceId.ToString();
203 return this.Connection.GetAsync<TwitterSearchResult>(endpoint, param, "/search/tweets");
206 public Task<TwitterLists> ListsOwnerships(string screenName, long? cursor = null, int? count = null)
208 var endpoint = new Uri("lists/ownerships.json", UriKind.Relative);
209 var param = new Dictionary<string, string>
211 ["screen_name"] = screenName,
215 param["cursor"] = cursor.ToString();
217 param["count"] = count.ToString();
219 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/ownerships");
222 public Task<TwitterLists> ListsSubscriptions(string screenName, long? cursor = null, int? count = null)
224 var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative);
225 var param = new Dictionary<string, string>
227 ["screen_name"] = screenName,
231 param["cursor"] = cursor.ToString();
233 param["count"] = count.ToString();
235 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/subscriptions");
238 public Task<TwitterLists> ListsMemberships(string screenName, long? cursor = null, int? count = null, bool? filterToOwnedLists = null)
240 var endpoint = new Uri("lists/memberships.json", UriKind.Relative);
241 var param = new Dictionary<string, string>
243 ["screen_name"] = screenName,
247 param["cursor"] = cursor.ToString();
249 param["count"] = count.ToString();
250 if (filterToOwnedLists != null)
251 param["filter_to_owned_lists"] = filterToOwnedLists.Value ? "true" : "false";
253 return this.Connection.GetAsync<TwitterLists>(endpoint, param, "/lists/memberships");
256 public Task<LazyJson<TwitterList>> ListsCreate(string name, string? description = null, bool? @private = null)
258 var endpoint = new Uri("lists/create.json", UriKind.Relative);
259 var param = new Dictionary<string, string>
264 if (description != null)
265 param["description"] = description;
266 if (@private != null)
267 param["mode"] = @private.Value ? "private" : "public";
269 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
272 public Task<LazyJson<TwitterList>> ListsUpdate(long listId, string? name = null, string? description = null, bool? @private = null)
274 var endpoint = new Uri("lists/update.json", UriKind.Relative);
275 var param = new Dictionary<string, string>
277 ["list_id"] = listId.ToString(),
281 param["name"] = name;
282 if (description != null)
283 param["description"] = description;
284 if (@private != null)
285 param["mode"] = @private.Value ? "private" : "public";
287 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
290 public Task<LazyJson<TwitterList>> ListsDestroy(long listId)
292 var endpoint = new Uri("lists/destroy.json", UriKind.Relative);
293 var param = new Dictionary<string, string>
295 ["list_id"] = listId.ToString(),
298 return this.Connection.PostLazyAsync<TwitterList>(endpoint, param);
301 public Task<TwitterStatus[]> ListsStatuses(long listId, int? count = null, long? maxId = null, long? sinceId = null, bool? includeRTs = null)
303 var endpoint = new Uri("lists/statuses.json", UriKind.Relative);
304 var param = new Dictionary<string, string>
306 ["list_id"] = listId.ToString(),
307 ["include_entities"] = "true",
308 ["include_ext_alt_text"] = "true",
309 ["tweet_mode"] = "extended",
313 param["count"] = count.ToString();
315 param["max_id"] = maxId.ToString();
317 param["since_id"] = sinceId.ToString();
318 if (includeRTs != null)
319 param["include_rts"] = includeRTs.Value ? "true" : "false";
321 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/lists/statuses");
324 public Task<TwitterUsers> ListsMembers(long listId, long? cursor = null)
326 var endpoint = new Uri("lists/members.json", UriKind.Relative);
327 var param = new Dictionary<string, string>
329 ["list_id"] = listId.ToString(),
330 ["include_entities"] = "true",
331 ["include_ext_alt_text"] = "true",
332 ["tweet_mode"] = "extended",
336 param["cursor"] = cursor.ToString();
338 return this.Connection.GetAsync<TwitterUsers>(endpoint, param, "/lists/members");
341 public Task<TwitterUser> ListsMembersShow(long listId, string screenName)
343 var endpoint = new Uri("lists/members/show.json", UriKind.Relative);
344 var param = new Dictionary<string, string>
346 ["list_id"] = listId.ToString(),
347 ["screen_name"] = screenName,
348 ["include_entities"] = "true",
349 ["include_ext_alt_text"] = "true",
350 ["tweet_mode"] = "extended",
353 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/lists/members/show");
356 public Task<LazyJson<TwitterUser>> ListsMembersCreate(long listId, string screenName)
358 var endpoint = new Uri("lists/members/create.json", UriKind.Relative);
359 var param = new Dictionary<string, string>
361 ["list_id"] = listId.ToString(),
362 ["screen_name"] = screenName,
363 ["include_entities"] = "true",
364 ["include_ext_alt_text"] = "true",
365 ["tweet_mode"] = "extended",
368 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
371 public Task<LazyJson<TwitterUser>> ListsMembersDestroy(long listId, string screenName)
373 var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative);
374 var param = new Dictionary<string, string>
376 ["list_id"] = listId.ToString(),
377 ["screen_name"] = screenName,
378 ["include_entities"] = "true",
379 ["include_ext_alt_text"] = "true",
380 ["tweet_mode"] = "extended",
383 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
386 public Task<TwitterMessageEventList> DirectMessagesEventsList(int? count = null, string? cursor = null)
388 var endpoint = new Uri("direct_messages/events/list.json", UriKind.Relative);
389 var param = new Dictionary<string, string>();
392 param["count"] = count.ToString();
394 param["cursor"] = cursor;
396 return this.Connection.GetAsync<TwitterMessageEventList>(endpoint, param, "/direct_messages/events/list");
399 public Task<LazyJson<TwitterMessageEventSingle>> DirectMessagesEventsNew(long recipientId, string text, long? mediaId = null)
401 var endpoint = new Uri("direct_messages/events/new.json", UriKind.Relative);
406 attachment = "," + $@"
410 ""id"": ""{JsonUtils.EscapeJsonString(mediaId.ToString())}""
417 ""type"": ""message_create"",
418 ""message_create"": {{
420 ""recipient_id"": ""{JsonUtils.EscapeJsonString(recipientId.ToString())}""
423 ""text"": ""{JsonUtils.EscapeJsonString(text)}""{attachment}
429 return this.Connection.PostJsonAsync<TwitterMessageEventSingle>(endpoint, json);
432 public Task DirectMessagesEventsDestroy(string eventId)
434 var endpoint = new Uri("direct_messages/events/destroy.json", UriKind.Relative);
435 var param = new Dictionary<string, string>
437 ["id"] = eventId.ToString(),
440 // なぜか application/x-www-form-urlencoded でパラメーターを送ると Bad Request になる謎仕様
441 endpoint = new Uri(endpoint.OriginalString + "?" + MyCommon.BuildQueryString(param), UriKind.Relative);
443 return this.Connection.DeleteAsync(endpoint);
446 public Task<TwitterUser> UsersShow(string screenName)
448 var endpoint = new Uri("users/show.json", UriKind.Relative);
449 var param = new Dictionary<string, string>
451 ["screen_name"] = screenName,
452 ["include_entities"] = "true",
453 ["include_ext_alt_text"] = "true",
454 ["tweet_mode"] = "extended",
457 return this.Connection.GetAsync<TwitterUser>(endpoint, param, "/users/show/:id");
460 public Task<TwitterUser[]> UsersLookup(IReadOnlyList<string> userIds)
462 var endpoint = new Uri("users/lookup.json", UriKind.Relative);
463 var param = new Dictionary<string, string>
465 ["user_id"] = string.Join(",", userIds),
466 ["include_entities"] = "true",
467 ["include_ext_alt_text"] = "true",
468 ["tweet_mode"] = "extended",
471 return this.Connection.GetAsync<TwitterUser[]>(endpoint, param, "/users/lookup");
474 public Task<LazyJson<TwitterUser>> UsersReportSpam(string screenName)
476 var endpoint = new Uri("users/report_spam.json", UriKind.Relative);
477 var param = new Dictionary<string, string>
479 ["screen_name"] = screenName,
480 ["tweet_mode"] = "extended",
483 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
486 public Task<TwitterStatus[]> FavoritesList(int? count = null, long? maxId = null, long? sinceId = null)
488 var endpoint = new Uri("favorites/list.json", UriKind.Relative);
489 var param = new Dictionary<string, string>
491 ["include_entities"] = "true",
492 ["include_ext_alt_text"] = "true",
493 ["tweet_mode"] = "extended",
497 param["count"] = count.ToString();
499 param["max_id"] = maxId.ToString();
501 param["since_id"] = sinceId.ToString();
503 return this.Connection.GetAsync<TwitterStatus[]>(endpoint, param, "/favorites/list");
506 public Task<LazyJson<TwitterStatus>> FavoritesCreate(long statusId)
508 var endpoint = new Uri("favorites/create.json", UriKind.Relative);
509 var param = new Dictionary<string, string>
511 ["id"] = statusId.ToString(),
512 ["tweet_mode"] = "extended",
515 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
518 public Task<LazyJson<TwitterStatus>> FavoritesDestroy(long statusId)
520 var endpoint = new Uri("favorites/destroy.json", UriKind.Relative);
521 var param = new Dictionary<string, string>
523 ["id"] = statusId.ToString(),
524 ["tweet_mode"] = "extended",
527 return this.Connection.PostLazyAsync<TwitterStatus>(endpoint, param);
530 public Task<TwitterFriendship> FriendshipsShow(string sourceScreenName, string targetScreenName)
532 var endpoint = new Uri("friendships/show.json", UriKind.Relative);
533 var param = new Dictionary<string, string>
535 ["source_screen_name"] = sourceScreenName,
536 ["target_screen_name"] = targetScreenName,
539 return this.Connection.GetAsync<TwitterFriendship>(endpoint, param, "/friendships/show");
542 public Task<LazyJson<TwitterFriendship>> FriendshipsCreate(string screenName)
544 var endpoint = new Uri("friendships/create.json", UriKind.Relative);
545 var param = new Dictionary<string, string>
547 ["screen_name"] = screenName,
550 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
553 public Task<LazyJson<TwitterFriendship>> FriendshipsDestroy(string screenName)
555 var endpoint = new Uri("friendships/destroy.json", UriKind.Relative);
556 var param = new Dictionary<string, string>
558 ["screen_name"] = screenName,
561 return this.Connection.PostLazyAsync<TwitterFriendship>(endpoint, param);
564 public Task<long[]> NoRetweetIds()
566 var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative);
568 return this.Connection.GetAsync<long[]>(endpoint, null, "/friendships/no_retweets/ids");
571 public Task<TwitterIds> FollowersIds(long? cursor = null)
573 var endpoint = new Uri("followers/ids.json", UriKind.Relative);
574 var param = new Dictionary<string, string>();
577 param["cursor"] = cursor.ToString();
579 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/followers/ids");
582 public Task<TwitterIds> MutesUsersIds(long? cursor = null)
584 var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative);
585 var param = new Dictionary<string, string>();
588 param["cursor"] = cursor.ToString();
590 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/mutes/users/ids");
593 public Task<TwitterIds> BlocksIds(long? cursor = null)
595 var endpoint = new Uri("blocks/ids.json", UriKind.Relative);
596 var param = new Dictionary<string, string>();
599 param["cursor"] = cursor.ToString();
601 return this.Connection.GetAsync<TwitterIds>(endpoint, param, "/blocks/ids");
604 public Task<LazyJson<TwitterUser>> BlocksCreate(string screenName)
606 var endpoint = new Uri("blocks/create.json", UriKind.Relative);
607 var param = new Dictionary<string, string>
609 ["screen_name"] = screenName,
610 ["tweet_mode"] = "extended",
613 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
616 public Task<LazyJson<TwitterUser>> BlocksDestroy(string screenName)
618 var endpoint = new Uri("blocks/destroy.json", UriKind.Relative);
619 var param = new Dictionary<string, string>
621 ["screen_name"] = screenName,
622 ["tweet_mode"] = "extended",
625 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
628 public async Task<TwitterUser> AccountVerifyCredentials()
630 var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative);
631 var param = new Dictionary<string, string>
633 ["include_entities"] = "true",
634 ["include_ext_alt_text"] = "true",
635 ["tweet_mode"] = "extended",
638 var user = await this.Connection.GetAsync<TwitterUser>(endpoint, param, "/account/verify_credentials")
639 .ConfigureAwait(false);
641 this.CurrentUserId = user.Id;
642 this.CurrentScreenName = user.ScreenName;
647 public Task<LazyJson<TwitterUser>> AccountUpdateProfile(string name, string url, string? location, string? description)
649 var endpoint = new Uri("account/update_profile.json", UriKind.Relative);
650 var param = new Dictionary<string, string>
652 ["include_entities"] = "true",
653 ["include_ext_alt_text"] = "true",
654 ["tweet_mode"] = "extended",
658 param["name"] = name;
661 if (location != null)
662 param["location"] = location;
664 if (description != null)
666 // name, location, description に含まれる < > " の文字はTwitter側で除去されるが、
667 // twitter.com の挙動では description でのみ < 等の文字参照を使って表示することができる
668 var escapedDescription = description.Replace("<", "<").Replace(">", ">").Replace("\"", """);
669 param["description"] = escapedDescription;
672 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param);
675 public Task<LazyJson<TwitterUser>> AccountUpdateProfileImage(IMediaItem image)
677 var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative);
678 var param = new Dictionary<string, string>
680 ["include_entities"] = "true",
681 ["include_ext_alt_text"] = "true",
682 ["tweet_mode"] = "extended",
684 var paramMedia = new Dictionary<string, IMediaItem>
689 return this.Connection.PostLazyAsync<TwitterUser>(endpoint, param, paramMedia);
692 public Task<TwitterRateLimits> ApplicationRateLimitStatus()
694 var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative);
696 return this.Connection.GetAsync<TwitterRateLimits>(endpoint, null, "/application/rate_limit_status");
699 public Task<TwitterConfiguration> Configuration()
701 var endpoint = new Uri("help/configuration.json", UriKind.Relative);
703 return this.Connection.GetAsync<TwitterConfiguration>(endpoint, null, "/help/configuration");
706 public Task<LazyJson<TwitterUploadMediaInit>> MediaUploadInit(long totalBytes, string mediaType, string? mediaCategory = null)
708 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
709 var param = new Dictionary<string, string>
711 ["command"] = "INIT",
712 ["total_bytes"] = totalBytes.ToString(),
713 ["media_type"] = mediaType,
716 if (mediaCategory != null)
717 param["media_category"] = mediaCategory;
719 return this.Connection.PostLazyAsync<TwitterUploadMediaInit>(endpoint, param);
722 public Task MediaUploadAppend(long mediaId, int segmentIndex, IMediaItem media)
724 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
725 var param = new Dictionary<string, string>
727 ["command"] = "APPEND",
728 ["media_id"] = mediaId.ToString(),
729 ["segment_index"] = segmentIndex.ToString(),
731 var paramMedia = new Dictionary<string, IMediaItem>
736 return this.Connection.PostAsync(endpoint, param, paramMedia);
739 public Task<LazyJson<TwitterUploadMediaResult>> MediaUploadFinalize(long mediaId)
741 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
742 var param = new Dictionary<string, string>
744 ["command"] = "FINALIZE",
745 ["media_id"] = mediaId.ToString(),
748 return this.Connection.PostLazyAsync<TwitterUploadMediaResult>(endpoint, param);
751 public Task<TwitterUploadMediaResult> MediaUploadStatus(long mediaId)
753 var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
754 var param = new Dictionary<string, string>
756 ["command"] = "STATUS",
757 ["media_id"] = mediaId.ToString(),
760 return this.Connection.GetAsync<TwitterUploadMediaResult>(endpoint, param, endpointName: null);
763 public Task MediaMetadataCreate(long mediaId, string altText)
765 var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json");
767 var escapedAltText = JsonUtils.EscapeJsonString(altText);
768 var json = $@"{{""media_id"": ""{mediaId}"", ""alt_text"": {{""text"": ""{escapedAltText}""}}}}";
770 return this.Connection.PostJsonAsync(endpoint, json);
773 public TwitterStreamObservable UserStreams(string? replies = null, string? track = null)
775 var endpoint = new Uri("https://userstream.twitter.com/1.1/user.json");
776 var param = new Dictionary<string, string>();
778 if (!string.IsNullOrEmpty(replies))
779 param["replies"] = replies;
780 if (!string.IsNullOrEmpty(track))
781 param["track"] = track;
783 Task<Stream> openStream()
784 => this.Connection.GetStreamingStreamAsync(endpoint, param);
786 return new TwitterStreamObservable(openStream);
789 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri? realm = null)
790 => ((TwitterApiConnection)this.Connection).CreateOAuthEchoHandler(authServiceProvider, realm);
792 public void Dispose()
793 => this.apiConnection?.Dispose();