diff --git a/FacebookCore.Tests/FacebookCore.Tests.csproj b/FacebookCore.Tests/FacebookCore.IntegrationTests.csproj similarity index 94% rename from FacebookCore.Tests/FacebookCore.Tests.csproj rename to FacebookCore.Tests/FacebookCore.IntegrationTests.csproj index 1aaf82f..ec73019 100644 --- a/FacebookCore.Tests/FacebookCore.Tests.csproj +++ b/FacebookCore.Tests/FacebookCore.IntegrationTests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.2 + netcoreapp3.1 false diff --git a/FacebookCore.Tests/FacebookUserApiTests.cs b/FacebookCore.Tests/FacebookUserApiTests.cs new file mode 100644 index 0000000..2ecc7e0 --- /dev/null +++ b/FacebookCore.Tests/FacebookUserApiTests.cs @@ -0,0 +1,62 @@ +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using System.Threading.Tasks; + +namespace FacebookCore.Tests +{ + [TestClass] + public class FacebookUserApiTests + { + private readonly FacebookClient _client; + private readonly string _accessToken; + + public FacebookUserApiTests() + { + string basePath = Directory.GetCurrentDirectory(); + + var builder = new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false); + + IConfigurationRoot configurationRoot = builder.Build(); + + var clientId = configurationRoot["client_id"]; + var clientSecret = configurationRoot["client_secret"]; + + _accessToken = configurationRoot["access_token"]; + _client = new FacebookClient(clientId, clientSecret); + } + + [TestMethod] + public async Task ShouldExtendUserToken() + { + var userApi = _client.GetUserApi(_accessToken); + + var extendResult = await userApi.RequestExtendAccessToken(); + + extendResult["access_token"].ToString().Should().NotBeNullOrWhiteSpace(); + } + + [TestMethod] + public async Task ShouldGetUserInfo() + { + var userApi = _client.GetUserApi(_accessToken); + + var extendResult = await userApi.RequestInformationAsync(); + + extendResult["id"].ToString().Should().NotBeNullOrWhiteSpace(); + } + + [TestMethod] + public async Task ShouldGetUserMetaInfo() + { + var userApi = _client.GetUserApi(_accessToken); + + var metadata = await userApi.RequestMetaDataAsync(); + + metadata["metadata"].ToString().Should().NotBeNullOrWhiteSpace(); + } + } +} diff --git a/FacebookCore.Tests/appsettings.json b/FacebookCore.Tests/appsettings.json new file mode 100644 index 0000000..720be16 --- /dev/null +++ b/FacebookCore.Tests/appsettings.json @@ -0,0 +1,5 @@ +{ + "client_id": "", + "client_secret": "", + "access_token": "" +} diff --git a/FacebookCore.sln b/FacebookCore.sln index c1f3e7e..aecabba 100644 --- a/FacebookCore.sln +++ b/FacebookCore.sln @@ -1,11 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30104.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FacebookCore", "FacebookCore\FacebookCore.csproj", "{5AECB266-5389-4CE0-AF75-83F08FB469B6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FacebookCore.Tests", "FacebookCore.Tests\FacebookCore.Tests.csproj", "{BA8B6303-F772-4B12-971B-D694E4E6ED86}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FacebookCore.IntegrationTests", "FacebookCore.Tests\FacebookCore.IntegrationTests.csproj", "{BA8B6303-F772-4B12-971B-D694E4E6ED86}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/FacebookCore/APIs/FacebookAppApi.cs b/FacebookCore/APIs/FacebookAppApi.cs index d8d4436..0adb680 100644 --- a/FacebookCore/APIs/FacebookAppApi.cs +++ b/FacebookCore/APIs/FacebookAppApi.cs @@ -24,6 +24,7 @@ public class FacebookAppApi : ApiBaseClass /// Facebook client instance public FacebookAppApi(FacebookClient client) : base(client) { + _appId = client.ClientId; } /// @@ -51,7 +52,7 @@ public async Task GetAccessTokenAsync() _appAccessToken = json["access_token"].ToString(); return _appAccessToken; } - catch (Exception ex) + catch { return null; } @@ -89,7 +90,7 @@ public string GetAppId(string accessToken) string appId = accessToken.Split(new char[] { '|' })[0]; return appId; } - catch (Exception e) + catch { return null; } diff --git a/FacebookCore/APIs/FacebookUserApi.cs b/FacebookCore/APIs/FacebookUserApi.cs index 8c3f2c8..e16f1dc 100644 --- a/FacebookCore/APIs/FacebookUserApi.cs +++ b/FacebookCore/APIs/FacebookUserApi.cs @@ -42,8 +42,23 @@ public async Task RequestInformationAsync(string[] fields = null) /// JObject with user information public async Task RequestMetaDataAsync() { - var response = await FacebookClient.GetAsync($"/{FacebookClient.GraphApiVersion}/me?metadata=1", _authToken); + var response = await FacebookClient.GetAsync($"/me?metadata=1", _authToken); return response; } + + /// + /// Gets a user access token that lasts around 60 days. + /// + /// https://developers.facebook.com/docs/facebook-login/access-tokens/refreshing/#get-a-long-lived-page-access-token + /// JObject with extended access token + public async Task RequestExtendAccessToken() + { + return await FacebookClient.GetAsync( + $"/oauth/access_token" + + $"?grant_type=fb_exchange_token" + + $"&client_id={FacebookClient.ClientId}" + + $"&client_secret={FacebookClient.ClientSecret}" + + $"&fb_exchange_token={_authToken}"); + } } } diff --git a/FacebookCore/Collections/AppTestUsersCollection.cs b/FacebookCore/Collections/AppTestUsersCollection.cs index bb006f3..bad90bf 100644 --- a/FacebookCore/Collections/AppTestUsersCollection.cs +++ b/FacebookCore/Collections/AppTestUsersCollection.cs @@ -7,11 +7,7 @@ namespace FacebookCore.Collections { public class AppTestUsersCollection : FacebookCollection { - private FacebookClient _fbClient; - private string _appAccessToken; - private string _appId; - - public FacebookCursor Cursor { get; internal set; } + public new FacebookCursor Cursor { get; internal set; } public AppTestUsersCollection(FacebookClient client, string query, string token, FacebookCursor cursor = null) : base(client, query, token, TestUserMapper, cursor) { diff --git a/FacebookCore/FacebookApiException.cs b/FacebookCore/FacebookApiException.cs new file mode 100644 index 0000000..a2fdb79 --- /dev/null +++ b/FacebookCore/FacebookApiException.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Runtime.Serialization; + +namespace FacebookCore +{ + [Serializable] + internal class FacebookApiException : Exception + { + public FacebookApiException() + { + } + + public FacebookApiException(string message) : base(message) + { + } + + public FacebookApiException(string message, Exception innerException) : base(message, innerException) + { + } + + protected FacebookApiException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/FacebookCore/FacebookClient.cs b/FacebookCore/FacebookClient.cs index 25c6113..fb307b3 100644 --- a/FacebookCore/FacebookClient.cs +++ b/FacebookCore/FacebookClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; using System.IO; using System.Net; using System.Threading.Tasks; @@ -17,7 +18,7 @@ namespace FacebookCore public class FacebookClient { private FacebookAppApi _app; - + internal string ClientId { get; private set; } internal string ClientSecret { get; private set; } @@ -30,7 +31,7 @@ public class FacebookClient /// Application API /// public FacebookAppApi App => _app ?? (_app = new FacebookAppApi(this)); - + public FacebookClient(string clientId, string clientSecret) { ClientId = clientId; @@ -38,6 +39,12 @@ public FacebookClient(string clientId, string clientSecret) RestClient = new RestClient("https://graph.facebook.com/"); } + public FacebookClient(FacebookConfig facebookConfig) + : this(facebookConfig.ClientId, facebookConfig.ClientSecret) + { + GraphApiVersion = facebookConfig.GraphApiVersion; + } + /// /// Create a new instance of the Users API /// @@ -67,55 +74,73 @@ public FacebookPlacesApi GetPlacesApi(string authToken) /// What set of results should be taken (after or before the current cursor) /// public async Task GetAsync(string path, string accessToken = null, FacebookCursor cursor = null, Direction cursorDirection = Direction.None) + { + path = StandardizePath(path); + path = AddAccessTokenToPathIfNeeded(path, accessToken); + path = AddCursorToPathIfNeeded(path, cursor, cursorDirection); + + return SerializeResponse(await RestClient.GetAsync($"/{GraphApiVersion}{path}", false)); + } + + private string StandardizePath(string path) { if (!path.StartsWith("/")) { path = "/" + path; } - if (accessToken == null) - { - accessToken = string.Empty; - } - else + return path; + } + + private string AddAccessTokenToPathIfNeeded(string path, string accessToken) + { + var accessTokenPart = string.Empty; + if (accessToken != null) { - accessToken = (path.Contains("?") ? "&" : "?") + "access_token=" + accessToken; + accessTokenPart = (path.Contains("?") ? "&" : "?") + "access_token=" + accessToken; } - string cursorStr = string.Empty; + return path + accessTokenPart; + } + + private string AddCursorToPathIfNeeded(string path, FacebookCursor cursor, Direction cursorDirection) + { + string cursorPart = string.Empty; + if (cursor != null && cursorDirection != Direction.None) { - if ((cursorDirection == Direction.After || cursorDirection == Direction.Next) && !string.IsNullOrWhiteSpace(cursor.After)) + if (!string.IsNullOrWhiteSpace(cursor.After) && + (cursorDirection == Direction.After || cursorDirection == Direction.Next)) { - cursorStr = (path.Contains("?") ? "&" : "?") + "after=" + cursor.After; + cursorPart = (path.Contains("?") ? "&" : "?") + "after=" + cursor.After; } else if (!string.IsNullOrWhiteSpace(cursor.Before)) { - cursorStr = (path.Contains("?") ? "&" : "?") + "before=" + cursor.Before; + cursorPart = (path.Contains("?") ? "&" : "?") + "before=" + cursor.Before; } } - var response = await RestClient.GetAsync($"/{GraphApiVersion}{path}{accessToken}{cursorStr}", false); - var serializedResponse = SerializeResponse(response); - return serializedResponse; + return path + cursorPart; } internal JObject SerializeResponse(IRestResponse response) { - try + if (response.StatusCode == HttpStatusCode.OK) { - if (response.StatusCode == HttpStatusCode.OK) + try { var jsreader = new JsonTextReader(new StringReader(response.RawData.ToString())); - var json = (JObject)new JsonSerializer().Deserialize(jsreader); - return json; + return (JObject)new JsonSerializer().Deserialize(jsreader); + } + catch + { + return null; } - return null; } - catch (Exception e) + else { - return null; + throw new FacebookApiException(response.RawData.ToString(), response.Exception); } } } -} \ No newline at end of file +} diff --git a/FacebookCore/FacebookCoreExceptions.cs b/FacebookCore/FacebookCollectionException.cs similarity index 100% rename from FacebookCore/FacebookCoreExceptions.cs rename to FacebookCore/FacebookCollectionException.cs diff --git a/FacebookCore/FacebookConfig.cs b/FacebookCore/FacebookConfig.cs new file mode 100644 index 0000000..1033458 --- /dev/null +++ b/FacebookCore/FacebookConfig.cs @@ -0,0 +1,9 @@ +namespace FacebookCore +{ + public interface FacebookConfig + { + string ClientId { get; set; } + string ClientSecret { get; set; } + string GraphApiVersion { get; set; } + } +}