// OpenTween - Client of Twitter
// Copyright (c) 2016 kim_upsilon (@kim_upsilon)
// All rights reserved.
//
// This file is part of OpenTween.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see , or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenTween.Api.DataModel;
using OpenTween.Connection;
namespace OpenTween.Api
{
public sealed class TwitterApi : IDisposable
{
public long CurrentUserId { get; private set; }
public string CurrentScreenName { get; private set; }
public IApiConnection Connection => this.apiConnection;
internal IApiConnection apiConnection;
public void Initialize(string accessToken, string accessSecret, long userId, string screenName)
{
var newInstance = new TwitterApiConnection(accessToken, accessSecret);
var oldInstance = Interlocked.Exchange(ref this.apiConnection, newInstance);
oldInstance?.Dispose();
this.CurrentUserId = userId;
this.CurrentScreenName = screenName;
}
public Task StatusesHomeTimeline(int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("statuses/home_timeline.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/statuses/home_timeline");
}
public Task StatusesMentionsTimeline(int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("statuses/mentions_timeline.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/statuses/mentions_timeline");
}
public Task StatusesUserTimeline(string screenName, int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("statuses/user_timeline.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
["include_rts"] = "true",
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/statuses/user_timeline");
}
public Task StatusesShow(long statusId)
{
var endpoint = new Uri("statuses/show.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.GetAsync(endpoint, param, "/statuses/show/:id");
}
public Task> StatusesUpdate(string status, long? replyToId, IReadOnlyList mediaIds)
{
var endpoint = new Uri("statuses/update.json", UriKind.Relative);
var param = new Dictionary
{
["status"] = status,
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (replyToId != null)
param["in_reply_to_status_id"] = replyToId.ToString();
if (mediaIds != null && mediaIds.Count > 0)
param.Add("media_ids", string.Join(",", mediaIds));
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> StatusesDestroy(long statusId)
{
var endpoint = new Uri("statuses/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> StatusesRetweet(long statusId)
{
var endpoint = new Uri("statuses/retweet.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task SearchTweets(string query, string lang = null, int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("search/tweets.json", UriKind.Relative);
var param = new Dictionary
{
["q"] = query,
["result_type"] = "recent",
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (lang != null)
param["lang"] = lang;
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/search/tweets");
}
public Task ListsOwnerships(string screenName, long? cursor = null)
{
var endpoint = new Uri("lists/ownerships.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/lists/ownerships");
}
public Task ListsSubscriptions(string screenName, long? cursor = null)
{
var endpoint = new Uri("lists/subscriptions.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/lists/subscriptions");
}
public Task> ListsCreate(string name, string description = null, bool? @private = null)
{
var endpoint = new Uri("lists/create.json", UriKind.Relative);
var param = new Dictionary
{
["name"] = name,
};
if (description != null)
param["description"] = description;
if (@private != null)
param["mode"] = @private.Value ? "private" : "public";
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> ListsUpdate(long listId, string name = null, string description = null, bool? @private = null)
{
var endpoint = new Uri("lists/update.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
};
if (name != null)
param["name"] = name;
if (description != null)
param["description"] = description;
if (@private != null)
param["mode"] = @private.Value ? "private" : "public";
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> ListsDestroy(long listId)
{
var endpoint = new Uri("lists/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task ListsStatuses(long listId, int? count = null, long? maxId = null, long? sinceId = null, bool? includeRTs = null)
{
var endpoint = new Uri("lists/statuses.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
if (includeRTs != null)
param["include_rts"] = includeRTs.Value ? "true" : "false";
return this.apiConnection.GetAsync(endpoint, param, "/lists/statuses");
}
public Task ListsMembers(long listId, long? cursor = null)
{
var endpoint = new Uri("lists/members.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/lists/members");
}
public Task ListsMembersShow(long listId, string screenName)
{
var endpoint = new Uri("lists/members/show.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
["screen_name"] = screenName,
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.GetAsync(endpoint, param, "/lists/members/show");
}
public Task> ListsMembersCreate(long listId, string screenName)
{
var endpoint = new Uri("lists/members/create.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
["screen_name"] = screenName,
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> ListsMembersDestroy(long listId, string screenName)
{
var endpoint = new Uri("lists/members/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["list_id"] = listId.ToString(),
["screen_name"] = screenName,
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task DirectMessagesRecv(int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("direct_messages.json", UriKind.Relative);
var param = new Dictionary
{
["full_text"] = "true",
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/direct_messages");
}
public Task DirectMessagesSent(int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("direct_messages/sent.json", UriKind.Relative);
var param = new Dictionary
{
["full_text"] = "true",
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/direct_messages/sent");
}
public Task> DirectMessagesNew(string status, string sendTo)
{
var endpoint = new Uri("direct_messages/new.json", UriKind.Relative);
var param = new Dictionary
{
["text"] = status,
["screen_name"] = sendTo,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> DirectMessagesDestroy(long statusId)
{
var endpoint = new Uri("direct_messages/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task UsersShow(string screenName)
{
var endpoint = new Uri("users/show.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
return this.apiConnection.GetAsync(endpoint, param, "/users/show/:id");
}
public Task> UsersReportSpam(string screenName)
{
var endpoint = new Uri("users/report_spam.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task FavoritesList(int? count = null, long? maxId = null, long? sinceId = null)
{
var endpoint = new Uri("favorites/list.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (count != null)
param["count"] = count.ToString();
if (maxId != null)
param["max_id"] = maxId.ToString();
if (sinceId != null)
param["since_id"] = sinceId.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/favorites/list");
}
public Task> FavoritesCreate(long statusId)
{
var endpoint = new Uri("favorites/create.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> FavoritesDestroy(long statusId)
{
var endpoint = new Uri("favorites/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["id"] = statusId.ToString(),
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task FriendshipsShow(string sourceScreenName, string targetScreenName)
{
var endpoint = new Uri("friendships/show.json", UriKind.Relative);
var param = new Dictionary
{
["source_screen_name"] = sourceScreenName,
["target_screen_name"] = targetScreenName,
};
return this.apiConnection.GetAsync(endpoint, param, "/friendships/show");
}
public Task> FriendshipsCreate(string screenName)
{
var endpoint = new Uri("friendships/create.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> FriendshipsDestroy(string screenName)
{
var endpoint = new Uri("friendships/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task NoRetweetIds(long? cursor = null)
{
var endpoint = new Uri("friendships/no_retweets/ids.json", UriKind.Relative);
return this.apiConnection.GetAsync(endpoint, null, "/friendships/no_retweets/ids");
}
public Task FollowersIds(long? cursor = null)
{
var endpoint = new Uri("followers/ids.json", UriKind.Relative);
var param = new Dictionary();
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/followers/ids");
}
public Task MutesUsersIds(long? cursor = null)
{
var endpoint = new Uri("mutes/users/ids.json", UriKind.Relative);
var param = new Dictionary();
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/mutes/users/ids");
}
public Task BlocksIds(long? cursor = null)
{
var endpoint = new Uri("blocks/ids.json", UriKind.Relative);
var param = new Dictionary();
if (cursor != null)
param["cursor"] = cursor.ToString();
return this.apiConnection.GetAsync(endpoint, param, "/blocks/ids");
}
public Task> BlocksCreate(string screenName)
{
var endpoint = new Uri("blocks/create.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> BlocksDestroy(string screenName)
{
var endpoint = new Uri("blocks/destroy.json", UriKind.Relative);
var param = new Dictionary
{
["screen_name"] = screenName,
};
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public async Task AccountVerifyCredentials()
{
var endpoint = new Uri("account/verify_credentials.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
var user = await this.apiConnection.GetAsync(endpoint, param, "/account/verify_credentials")
.ConfigureAwait(false);
this.CurrentUserId = user.Id;
this.CurrentScreenName = user.ScreenName;
return user;
}
public Task> AccountUpdateProfile(string name, string url, string location, string description)
{
var endpoint = new Uri("account/update_profile.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
if (name != null)
param["name"] = name;
if (url != null)
param["url"] = url;
if (location != null)
param["location"] = location;
if (description != null)
{
// name, location, description に含まれる < > " の文字はTwitter側で除去されるが、
// twitter.com の挙動では description でのみ < 等の文字参照を使って表示することができる
var escapedDescription = description.Replace("<", "<").Replace(">", ">").Replace("\"", """);
param["description"] = escapedDescription;
}
return this.apiConnection.PostLazyAsync(endpoint, param);
}
public Task> AccountUpdateProfileImage(IMediaItem image)
{
var endpoint = new Uri("account/update_profile_image.json", UriKind.Relative);
var param = new Dictionary
{
["include_entities"] = "true",
["include_ext_alt_text"] = "true",
};
var paramMedia = new Dictionary
{
["image"] = image,
};
return this.apiConnection.PostLazyAsync(endpoint, param, paramMedia);
}
public Task ApplicationRateLimitStatus()
{
var endpoint = new Uri("application/rate_limit_status.json", UriKind.Relative);
return this.apiConnection.GetAsync(endpoint, null, "/application/rate_limit_status");
}
public Task Configuration()
{
var endpoint = new Uri("help/configuration.json", UriKind.Relative);
return this.apiConnection.GetAsync(endpoint, null, "/help/configuration");
}
public Task> MediaUpload(IMediaItem media)
{
var endpoint = new Uri("https://upload.twitter.com/1.1/media/upload.json");
var paramMedia = new Dictionary
{
["media"] = media,
};
return this.apiConnection.PostLazyAsync(endpoint, null, paramMedia);
}
public Task MediaMetadataCreate(long mediaId, string altText)
{
var endpoint = new Uri("https://upload.twitter.com/1.1/media/metadata/create.json");
var escapedAltText = EscapeJsonString(altText);
var json = $@"{{""media_id"": ""{mediaId}"", ""alt_text"": {{""text"": ""{escapedAltText}""}}}}";
return this.apiConnection.PostJsonAsync(endpoint, json);
}
public Task UserStreams(string replies = null, string track = null)
{
var endpoint = new Uri("https://userstream.twitter.com/1.1/user.json");
var param = new Dictionary();
if (replies != null)
param["replies"] = replies;
if (track != null)
param["track"] = track;
return this.apiConnection.GetStreamAsync(endpoint, param);
}
public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
{
return ((TwitterApiConnection)this.apiConnection).CreateOAuthEchoHandler(authServiceProvider, realm);
}
public void Dispose()
{
this.apiConnection?.Dispose();
}
/// JSON に出力する文字列を ECMA-404 に従ってエスケープする
public static string EscapeJsonString(string rawText)
{
var builder = new StringBuilder(rawText.Length + 20);
foreach (var c in rawText)
{
if (c <= 0x1F || char.IsSurrogate(c))
builder.AppendFormat(@"\u{0:X4}", (int)c);
else if (c == '\\' || c == '\"')
builder.Append('\\').Append(c);
else
builder.Append(c);
}
return builder.ToString();
}
}
}