diff --git a/Octokit.Reactive/Clients/IObservableFollowersClient.cs b/Octokit.Reactive/Clients/IObservableFollowersClient.cs
new file mode 100644
index 0000000000..f959b833bd
--- /dev/null
+++ b/Octokit.Reactive/Clients/IObservableFollowersClient.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reactive;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Octokit.Reactive
+{
+ public interface IObservableFollowersClient
+ {
+ ///
+ /// List the authenticated user’s followers
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the authenticated user.
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ IObservable GetAllForCurrent();
+
+ ///
+ /// List a user’s followers
+ ///
+ /// The login name for the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the passed user.
+ IObservable GetAll(string login);
+
+ ///
+ /// List who the authenticated user is following
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the authenticated user follows.
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ IObservable GetFollowingForCurrent();
+
+ ///
+ /// List who a user is following
+ ///
+ /// The login name of the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the passed user follows.
+ IObservable GetFollowing(string login);
+
+ ///
+ /// Check if the authenticated user follows another user
+ ///
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ IObservable IsFollowingForCurrent(string following);
+
+ ///
+ /// Check if one user follows another user
+ ///
+ /// The login name of the user
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ IObservable IsFollowing(string login, string following);
+
+ ///
+ /// Follow a user
+ ///
+ /// The login name of the user to follow
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ IObservable Follow(string login);
+
+ ///
+ /// Unfollow a user
+ ///
+ /// The login name of the user to unfollow
+ ///
+ /// See the API documentation for more information.
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unfollow",
+ Justification = "Unfollow is consistent with the GitHub website")]
+ IObservable Unfollow(string login);
+ }
+}
diff --git a/Octokit.Reactive/Clients/ObservableFollowersClient.cs b/Octokit.Reactive/Clients/ObservableFollowersClient.cs
new file mode 100644
index 0000000000..88e3159caf
--- /dev/null
+++ b/Octokit.Reactive/Clients/ObservableFollowersClient.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Reactive;
+using System.Reactive.Threading.Tasks;
+using Octokit.Reactive.Internal;
+
+namespace Octokit.Reactive
+{
+ public class ObservableFollowersClient : IObservableFollowersClient
+ {
+ readonly IFollowersClient _client;
+ readonly IConnection _connection;
+
+ ///
+ /// Initializes a new User Followers API client.
+ ///
+ /// An used to make the requests
+ public ObservableFollowersClient(IGitHubClient client)
+ {
+ Ensure.ArgumentNotNull(client, "client");
+
+ _client = client.User.Followers;
+ _connection = client.Connection;
+ }
+
+ ///
+ /// List the authenticated user’s followers
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the authenticated user.
+ public IObservable GetAllForCurrent()
+ {
+ return _connection.GetAndFlattenAllPages(ApiUrls.Followers());
+ }
+
+ ///
+ /// List a user’s followers
+ ///
+ /// The login name for the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the passed user.
+ public IObservable GetAll(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return _connection.GetAndFlattenAllPages(ApiUrls.Followers(login));
+ }
+
+ ///
+ /// List who the authenticated user is following
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the authenticated user follows.
+ public IObservable GetFollowingForCurrent()
+ {
+ return _connection.GetAndFlattenAllPages(ApiUrls.Following());
+ }
+
+ ///
+ /// List who a user is following
+ ///
+ /// The login name of the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the passed user follows.
+ public IObservable GetFollowing(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return _connection.GetAndFlattenAllPages(ApiUrls.Following(login));
+ }
+
+ ///
+ /// Check if the authenticated user follows another user
+ ///
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public IObservable IsFollowingForCurrent(string following)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(following, "following");
+
+ return _client.IsFollowingForCurrent(following).ToObservable();
+ }
+
+ ///
+ /// Check if one user follows another user
+ ///
+ /// The login name of the user
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public IObservable IsFollowing(string login, string following)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+ Ensure.ArgumentNotNullOrEmptyString(following, "following");
+
+ return _client.IsFollowing(login, following).ToObservable();
+ }
+
+ ///
+ /// Follow a user
+ ///
+ /// The login name of the user to follow
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public IObservable Follow(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return _client.Follow(login).ToObservable();
+ }
+
+ ///
+ /// Unfollow a user
+ ///
+ /// The login name of the user to unfollow
+ ///
+ /// See the API documentation for more information.
+ ///
+ ///
+ public IObservable Unfollow(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return _client.Unfollow(login).ToObservable();
+ }
+ }
+}
diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj
index e2db17ad7f..2f21b5e66a 100644
--- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj
@@ -120,6 +120,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
index d3ffec270d..cd6ab187ea 100644
--- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
@@ -129,6 +129,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
index c593c7dd2c..0f441e351f 100644
--- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
@@ -124,6 +124,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj
index 75e3a0ef7b..6c5c7e71fc 100644
--- a/Octokit.Reactive/Octokit.Reactive.csproj
+++ b/Octokit.Reactive/Octokit.Reactive.csproj
@@ -74,6 +74,7 @@
Properties\SolutionInfo.cs
+
@@ -120,6 +121,7 @@
+
diff --git a/Octokit.Tests.Integration/Clients/FollowersClientTests.cs b/Octokit.Tests.Integration/Clients/FollowersClientTests.cs
new file mode 100644
index 0000000000..4d7f1e0f5b
--- /dev/null
+++ b/Octokit.Tests.Integration/Clients/FollowersClientTests.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Octokit;
+using Octokit.Tests.Integration;
+using Xunit;
+
+public class FollowersClientTests : IDisposable
+{
+ readonly GitHubClient _github;
+ readonly User _currentUser;
+
+ public FollowersClientTests()
+ {
+ _github = new GitHubClient(new ProductHeaderValue("OctokitTests"))
+ {
+ Credentials = Helper.Credentials
+ };
+ _currentUser = _github.User.Current().Result;
+ }
+
+ [IntegrationTest]
+ public async Task ReturnsUsersTheCurrentUserIsFollowing()
+ {
+ await _github.User.Followers.Follow("alfhenrik");
+
+ var following = await _github.User.Followers.GetFollowingForCurrent();
+
+ Assert.NotNull(following);
+ Assert.True(following.Any(f => f.Login == "alfhenrik"));
+ }
+
+ [IntegrationTest]
+ public async Task ReturnsUsersTheUserIsFollowing()
+ {
+ var following = await _github.User.Followers.GetFollowing("alfhenrik");
+
+ Assert.NotNull(following);
+ Assert.NotEmpty(following);
+ }
+
+ [IntegrationTest]
+ public async Task ReturnsUsersFollowingTheUser()
+ {
+ await _github.User.Followers.Follow("alfhenrik");
+
+ var followers = await _github.User.Followers.GetAll("alfhenrik");
+
+ Assert.NotEmpty(followers);
+ Assert.True(followers.Any(f => f.Login == _currentUser.Login));
+ }
+
+ [IntegrationTest]
+ public async Task ChecksIfIsFollowingUserWhenFollowingUser()
+ {
+ await _github.User.Followers.Follow("alfhenrik");
+
+ var isFollowing = await _github.User.Followers.IsFollowingForCurrent("alfhenrik");
+
+ Assert.True(isFollowing);
+ }
+
+ [IntegrationTest]
+ public async Task ChecksIfIsFollowingUserWhenNotFollowingUser()
+ {
+ var isFollowing = await _github.User.Followers.IsFollowingForCurrent("alfhenrik");
+
+ Assert.False(isFollowing);
+ }
+
+ [IntegrationTest]
+ public async Task FollowUserNotBeingFollowedByTheUser()
+ {
+ var result = await _github.User.Followers.Follow("alfhenrik");
+ var following = await _github.User.Followers.GetFollowingForCurrent();
+
+ Assert.True(result);
+ Assert.NotEmpty(following);
+ Assert.True(following.Any(f => f.Login == "alfhenrik"));
+ }
+
+ [IntegrationTest]
+ public async Task UnfollowUserBeingFollowedByTheUser()
+ {
+ await _github.User.Followers.Follow("alfhenrik");
+ var followers = await _github.User.Followers.GetAll("alfhenrik");
+ Assert.True(followers.Any(f => f.Login == _currentUser.Login));
+
+ await _github.User.Followers.Unfollow("alfhenrik");
+ followers = await _github.User.Followers.GetAll("alfhenrik");
+ Assert.False(followers.Any(f => f.Login == _currentUser.Login));
+ }
+
+ [IntegrationTest]
+ public async Task UnfollowUserNotBeingFollowedTheUser()
+ {
+ var followers = await _github.User.Followers.GetAll("alfhenrik");
+ Assert.False(followers.Any(f => f.Login == _currentUser.Login));
+
+ await _github.User.Followers.Unfollow("alfhenrik");
+ }
+
+ public void Dispose()
+ {
+ _github.User.Followers.Unfollow("alfhenrik");
+ }
+}
diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
index cf1aa4aad6..9587edf948 100644
--- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
+++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
@@ -69,6 +69,7 @@
+
diff --git a/Octokit.Tests/Clients/FollowersClientTests.cs b/Octokit.Tests/Clients/FollowersClientTests.cs
new file mode 100644
index 0000000000..9c24b83951
--- /dev/null
+++ b/Octokit.Tests/Clients/FollowersClientTests.cs
@@ -0,0 +1,274 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using NSubstitute;
+using Octokit.Internal;
+using Octokit.Tests;
+using Octokit.Tests.Helpers;
+using Xunit;
+using Xunit.Extensions;
+
+namespace Octokit.Tests.Clients
+{
+ ///
+ /// Client tests mostly just need to make sure they call the IApiConnection with the correct
+ /// relative Uri. No need to fake up the response. All *those* tests are in ApiConnectionTests.cs.
+ ///
+ public class FollowersClientTests
+ {
+ public class TheConstructor
+ {
+ [Fact]
+ public void EnsuresNonNullArguments()
+ {
+ Assert.Throws(() => new FollowersClient(null));
+ }
+ }
+
+ public class TheGetAllForCurrentMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ client.GetAllForCurrent();
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "user/followers"));
+ }
+ }
+
+ public class TheGetAllMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ client.GetAll("alfhenrik");
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "users/alfhenrik/followers"));
+ }
+
+ [Fact]
+ public void EnsureNonNullArguments()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ AssertEx.Throws(async () => await client.GetAll(null));
+ AssertEx.Throws(async () => await client.GetAll(""));
+ }
+ }
+
+ public class TheGetFollowingForCurrentMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ client.GetFollowingForCurrent();
+
+ connection.Received().GetAll(Arg.Is(u => u.ToString() == "user/following"));
+ }
+ }
+
+ public class TheGetFollowingMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ client.GetFollowing("alfhenrik");
+
+ connection.Received().GetAll(Arg.Is(u => u.ToString() == "users/alfhenrik/following"));
+ }
+
+ [Fact]
+ public void EnsuresNonNullArguments()
+ {
+ var connection = Substitute.For();
+ var client = new FollowersClient(connection);
+
+ AssertEx.Throws(async () => await client.GetFollowing(null));
+ AssertEx.Throws(async () => await client.GetFollowing(""));
+ }
+ }
+
+ public class TheIsFollowingForCurrentMethod
+ {
+ [Theory]
+ [InlineData(HttpStatusCode.NoContent, true)]
+ [InlineData(HttpStatusCode.NotFound, false)]
+ public async Task RequestsCorrectValueForStatusCode(HttpStatusCode status, bool expected)
+ {
+ var response = Task.Factory.StartNew>(() =>
+ new ApiResponse
diff --git a/Octokit.Tests/Reactive/ObservableFollowersTest.cs b/Octokit.Tests/Reactive/ObservableFollowersTest.cs
new file mode 100644
index 0000000000..145c4dbc05
--- /dev/null
+++ b/Octokit.Tests/Reactive/ObservableFollowersTest.cs
@@ -0,0 +1,192 @@
+using NSubstitute;
+using Octokit;
+using Octokit.Internal;
+using Octokit.Reactive;
+using Octokit.Tests.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Octokit.Tests.Reactive
+{
+ public class ObservableFollowersTest
+ {
+ public class TheGetAllForCurrentMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.GetAllForCurrent();
+
+ githubClient.Connection.GetAsync>(
+ new Uri("user/followers", UriKind.Relative), null, null);
+ }
+ }
+
+ public class TheGetAllMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.GetAll("alfhenrik");
+
+ githubClient.Connection.GetAsync>(
+ new Uri("users/alfhenrik/followers", UriKind.Relative), null, null);
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.GetAll(null));
+ await AssertEx.Throws(async () => await client.GetAll(""));
+ }
+ }
+
+ public class TheGetFollowingForCurrentMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.GetFollowingForCurrent();
+
+ githubClient.Connection.GetAsync>(
+ new Uri("user/following", UriKind.Relative), null, null);
+ }
+ }
+
+ public class TheGetFollowingMethod
+ {
+ [Fact]
+ public void RequestsTheCorrectUrl()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.GetFollowing("alfhenrik");
+
+ githubClient.Connection.GetAsync>(
+ new Uri("users/alfhenrik/following", UriKind.Relative), null, null);
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.GetFollowing(null));
+ await AssertEx.Throws(async () => await client.GetFollowing(""));
+ }
+ }
+
+ public class TheIsFollowingForCurrentMethod
+ {
+ [Fact]
+ public void IsFollowingForCurrentFromClientUserFollowers()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.IsFollowingForCurrent("alfhenrik");
+
+ githubClient.User.Followers.Received()
+ .IsFollowingForCurrent("alfhenrik");
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.IsFollowingForCurrent(null));
+ await AssertEx.Throws(async () => await client.IsFollowingForCurrent(""));
+ }
+ }
+
+ public class TheIsFollowingMethod
+ {
+ [Fact]
+ public void IsFollowingFromClientUserFollowers()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.IsFollowing("alfhenrik", "alfhenrik-test");
+
+ githubClient.User.Followers.Received()
+ .IsFollowing("alfhenrik", "alfhenrik-test");
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.IsFollowing(null, "alfhenrik-test"));
+ await AssertEx.Throws(async () => await client.IsFollowing("", "alfhenrik-test"));
+ await AssertEx.Throws(async () => await client.IsFollowing("alfhenrik", null));
+ await AssertEx.Throws(async () => await client.IsFollowing("alfhenrik", ""));
+ }
+ }
+
+ public class TheFollowMethod
+ {
+ [Fact]
+ public void FollowFromClientUserFollowers()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.Follow("alfhenrik");
+
+ githubClient.User.Followers.Received()
+ .Follow("alfhenrik");
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.Follow(null));
+ await AssertEx.Throws(async () => await client.Follow(""));
+ }
+ }
+
+ public class TheUnfollowMethod
+ {
+ [Fact]
+ public void UnfollowFromClientUserFollowers()
+ {
+ var githubClient = Substitute.For();
+ var client = new ObservableFollowersClient(githubClient);
+
+ client.Unfollow("alfhenrik");
+
+ githubClient.User.Followers.Received()
+ .Unfollow("alfhenrik");
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableFollowersClient(Substitute.For());
+
+ await AssertEx.Throws(async () => await client.Unfollow(null));
+ await AssertEx.Throws(async () => await client.Unfollow(""));
+ }
+ }
+ }
+}
diff --git a/Octokit/Clients/FollowersClient.cs b/Octokit/Clients/FollowersClient.cs
new file mode 100644
index 0000000000..76f1851d56
--- /dev/null
+++ b/Octokit/Clients/FollowersClient.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Octokit
+{
+ ///
+ /// A client for GitHub's User Followers API
+ ///
+ ///
+ /// See the Followers API documentation for more information.
+ ///
+ public class FollowersClient : ApiClient, IFollowersClient
+ {
+ ///
+ /// Initializes a new GitHub User Followers API client.
+ ///
+ /// An API connection
+ public FollowersClient(IApiConnection apiConnection) : base(apiConnection)
+ {
+ }
+
+ ///
+ /// List the authenticated user’s followers
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the authenticated user.
+ public Task> GetAllForCurrent()
+ {
+ return ApiConnection.GetAll(ApiUrls.Followers());
+ }
+
+ ///
+ /// List a user’s followers
+ ///
+ /// The login name for the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the passed user.
+ public Task> GetAll(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return ApiConnection.GetAll(ApiUrls.Followers(login));
+ }
+
+ ///
+ /// List who the authenticated user is following
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the authenticated user follows.
+ public Task> GetFollowingForCurrent()
+ {
+ return ApiConnection.GetAll(ApiUrls.Following());
+ }
+
+ ///
+ /// List who a user is following
+ ///
+ /// The login name of the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the passed user follows.
+ public Task> GetFollowing(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return ApiConnection.GetAll(ApiUrls.Following(login));
+ }
+
+ ///
+ /// Check if the authenticated user follows another user
+ ///
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public async Task IsFollowingForCurrent(string following)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(following, "following");
+
+ try
+ {
+ var response = await Connection.GetAsync(ApiUrls.IsFollowing(following), null, null)
+ .ConfigureAwait(false);
+ if(response.StatusCode != HttpStatusCode.NotFound && response.StatusCode != HttpStatusCode.NoContent)
+ {
+ throw new ApiException("Invalid Status Code returned. Expected a 204 or a 404", response.StatusCode);
+ }
+ return response.StatusCode == HttpStatusCode.NoContent;
+ }
+ catch (NotFoundException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Check if one user follows another user
+ ///
+ /// The login name of the user
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public async Task IsFollowing(string login, string following)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+ Ensure.ArgumentNotNullOrEmptyString(following, "following");
+
+ try
+ {
+ var response = await Connection.GetAsync(ApiUrls.IsFollowing(login, following), null, null)
+ .ConfigureAwait(false);
+ if (response.StatusCode != HttpStatusCode.NotFound && response.StatusCode != HttpStatusCode.NoContent)
+ {
+ throw new ApiException("Invalid Status Code returned. Expected a 204 or a 404", response.StatusCode);
+ }
+ return response.StatusCode == HttpStatusCode.NoContent;
+ }
+ catch (NotFoundException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Follow a user
+ ///
+ /// The login name of the user to follow
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ public async Task Follow(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ try
+ {
+ var requestData = new { };
+ var response = await Connection.PutAsync(ApiUrls.IsFollowing(login), requestData)
+ .ConfigureAwait(false);
+ if (response.StatusCode != HttpStatusCode.NoContent)
+ {
+ throw new ApiException("Invalid Status Code returned. Expected a 204", response.StatusCode);
+ }
+ return response.StatusCode == HttpStatusCode.NoContent;
+ }
+ catch (NotFoundException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Unfollow a user
+ ///
+ /// The login name of the user to unfollow
+ ///
+ /// See the API documentation for more information.
+ ///
+ ///
+ public Task Unfollow(string login)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(login, "login");
+
+ return ApiConnection.Delete(ApiUrls.IsFollowing(login));
+ }
+ }
+}
diff --git a/Octokit/Clients/IFollowersClient.cs b/Octokit/Clients/IFollowersClient.cs
new file mode 100644
index 0000000000..b73dc5833c
--- /dev/null
+++ b/Octokit/Clients/IFollowersClient.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+
+namespace Octokit
+{
+ ///
+ /// A client for GitHub's User Followers API
+ ///
+ ///
+ /// See the Followers API documentation for more information.
+ ///
+ public interface IFollowersClient
+ {
+ ///
+ /// List the authenticated user’s followers
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the authenticated user.
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ Task> GetAllForCurrent();
+
+ ///
+ /// List a user’s followers
+ ///
+ /// The login name for the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that follow the passed user.
+ Task> GetAll(string login);
+
+ ///
+ /// List who the authenticated user is following
+ ///
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the authenticated user follows.
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ Task> GetFollowingForCurrent();
+
+ ///
+ /// List who a user is following
+ ///
+ /// The login name of the user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A of s that the passed user follows.
+ Task> GetFollowing(string login);
+
+ ///
+ /// Check if the authenticated user follows another user
+ ///
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ Task IsFollowingForCurrent(string following);
+
+ ///
+ /// Check if one user follows another user
+ ///
+ /// The login name of the user
+ /// The login name of the other user
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ Task IsFollowing(string login, string following);
+
+ ///
+ /// Follow a user
+ ///
+ /// The login name of the user to follow
+ ///
+ /// See the API documentation for more information.
+ ///
+ /// A bool representing the success of the operation.
+ Task Follow(string login);
+
+ ///
+ /// Unfollow a user
+ ///
+ /// The login name of the user to unfollow
+ ///
+ /// See the API documentation for more information.
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unfollow",
+ Justification = "Unfollow is consistent with the GitHub website")]
+ Task Unfollow(string login);
+ }
+}
diff --git a/Octokit/Clients/IUsersClient.cs b/Octokit/Clients/IUsersClient.cs
index e5c91a88c7..eddc0039ae 100644
--- a/Octokit/Clients/IUsersClient.cs
+++ b/Octokit/Clients/IUsersClient.cs
@@ -41,5 +41,13 @@ public interface IUsersClient
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task> GetEmails();
+
+ ///
+ /// A client for GitHub's User Followers API
+ ///
+ ///
+ /// See the Followers API documentation for more information.
+ ///
+ IFollowersClient Followers { get; }
}
}
diff --git a/Octokit/Clients/UsersClient.cs b/Octokit/Clients/UsersClient.cs
index 48ff6746fc..c68ece71f5 100644
--- a/Octokit/Clients/UsersClient.cs
+++ b/Octokit/Clients/UsersClient.cs
@@ -23,6 +23,7 @@ public class UsersClient : ApiClient, IUsersClient
/// An API connection
public UsersClient(IApiConnection apiConnection) : base(apiConnection)
{
+ Followers = new FollowersClient(apiConnection);
}
///
@@ -68,5 +69,13 @@ public Task> GetEmails()
{
return ApiConnection.GetAll(ApiUrls.Emails(), null);
}
+
+ ///
+ /// A client for GitHub's User Followers API
+ ///
+ ///
+ /// See the Followers API documentation for more information.
+ ///
+ public IFollowersClient Followers { get; private set; }
}
}
diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs
index acede7739c..de29e397b3 100644
--- a/Octokit/Helpers/ApiUrls.cs
+++ b/Octokit/Helpers/ApiUrls.cs
@@ -833,5 +833,35 @@ public static Uri SearchCode()
{
return "search/code".FormatUri();
}
+
+ public static Uri Followers()
+ {
+ return "user/followers".FormatUri();
+ }
+
+ public static Uri Followers(string login)
+ {
+ return "users/{0}/followers".FormatUri(login);
+ }
+
+ public static Uri Following()
+ {
+ return "user/following".FormatUri();
+ }
+
+ public static Uri Following(string login)
+ {
+ return "users/{0}/following".FormatUri(login);
+ }
+
+ public static Uri IsFollowing(string following)
+ {
+ return "user/following/{0}".FormatUri(following);
+ }
+
+ public static Uri IsFollowing(string login, string following)
+ {
+ return "users/{0}/following/{1}".FormatUri(login, following);
+ }
}
}
diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj
index 9d18a928ab..cc349c2569 100644
--- a/Octokit/Octokit-Mono.csproj
+++ b/Octokit/Octokit-Mono.csproj
@@ -253,6 +253,8 @@
+
+
\ No newline at end of file
diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj
index 67fed48458..f196e297bf 100644
--- a/Octokit/Octokit-MonoAndroid.csproj
+++ b/Octokit/Octokit-MonoAndroid.csproj
@@ -263,6 +263,8 @@
+
+
\ No newline at end of file
diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj
index f34fd75ebd..1908e4b1a4 100644
--- a/Octokit/Octokit-Monotouch.csproj
+++ b/Octokit/Octokit-Monotouch.csproj
@@ -258,6 +258,8 @@
+
+
\ No newline at end of file
diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj
index b8c62ad0f6..c855b507ca 100644
--- a/Octokit/Octokit-netcore45.csproj
+++ b/Octokit/Octokit-netcore45.csproj
@@ -57,6 +57,7 @@
+
@@ -66,6 +67,7 @@
+
diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj
index a1e1e691c1..6402442c89 100644
--- a/Octokit/Octokit.csproj
+++ b/Octokit/Octokit.csproj
@@ -58,6 +58,8 @@
+
+ Code