diff --git a/.travis.yml b/.travis.yml index 2b84fb67..ea0943ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: csharp -solution: InstagramAPI.sln +solution: InstaSharper.sln dotnet: 1.0.0-preview2-003121 sudo: required os: linux @@ -10,9 +10,9 @@ script: - dotnet --info # Run dotnet new - dotnet restore - - cd InstagramAPI + - cd InstaSharper - dotnet --verbose build - - cd ../InstagramAPI.Tests + - cd ../InstaSharper.Tests - dotnet --verbose build - - dotnet --verbose test + - dotnet --verbose test -parallel none \ No newline at end of file diff --git a/InstaSharper.Examples/App.config b/InstaSharper.Examples/App.config new file mode 100644 index 00000000..51fffc74 --- /dev/null +++ b/InstaSharper.Examples/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InstaSharper.Examples/InstaSharper.Examples.csproj b/InstaSharper.Examples/InstaSharper.Examples.csproj new file mode 100644 index 00000000..efe30f26 --- /dev/null +++ b/InstaSharper.Examples/InstaSharper.Examples.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {620D3DB5-5636-4A54-A7D7-600C6518C20E} + Exe + Properties + InstaSharper.Examples + InstaSharper.Examples + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\InstaSharper\bin\Debug\net452\InstaSharper.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InstaSharper.Examples/InstaSharper.Examples.sln b/InstaSharper.Examples/InstaSharper.Examples.sln new file mode 100644 index 00000000..fe06e8f5 --- /dev/null +++ b/InstaSharper.Examples/InstaSharper.Examples.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstaSharper.Examples", "InstaSharper.Examples.csproj", "{620D3DB5-5636-4A54-A7D7-600C6518C20E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {620D3DB5-5636-4A54-A7D7-600C6518C20E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {620D3DB5-5636-4A54-A7D7-600C6518C20E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {620D3DB5-5636-4A54-A7D7-600C6518C20E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {620D3DB5-5636-4A54-A7D7-600C6518C20E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/InstaSharper.Examples/InstaSharperExamples.cs b/InstaSharper.Examples/InstaSharperExamples.cs new file mode 100644 index 00000000..44b815fd --- /dev/null +++ b/InstaSharper.Examples/InstaSharperExamples.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using InstaSharper.API; +using InstaSharper.API.Builder; +using InstaSharper.Classes; + +namespace InstaSharper.Examples +{ + internal class InstaSharperExamples + { + private static IInstaApi _instaApi; + + private static void Main(string[] args) + { + // create user session data and provide login details + var userSession = new UserSessionData + { + UserName = "username", + Password = "password" + }; + // create new InstaApi instance using Builder + _instaApi = new InstaApiBuilder() + .SetUser(userSession) + .Build(); + // login + var logInResult = _instaApi.Login(); + if (!logInResult.Succeeded) { Console.WriteLine($"Unable to login: {logInResult.Message}"); } + else + { + // get currently logged in user + var currentUser = _instaApi.GetCurrentUser().Value; + Console.WriteLine($"Logged in: username - {currentUser.UserName}, full name - {currentUser.FullName}"); + // get followers + var followers = _instaApi.GetUserFollowersAsync(currentUser.UserName, 5).Result.Value; + Console.WriteLine($"Count of followers [{currentUser.UserName}]:{followers.Count}"); + // get user's media + var currentUserMedia = _instaApi.GetUserMedia(currentUser.UserName, 5); + if (currentUserMedia.Succeeded) + { + Console.WriteLine($"Media count [{currentUser.UserName}]: {currentUserMedia.Value.Count}"); + foreach (var media in currentUserMedia.Value) Console.WriteLine($"Media [{currentUser.UserName}]: {media.Caption.Text}, {media.Code}, likes: {media.LikesCount}, image link: {media.Images.LastOrDefault()?.Url}"); + } + + //get user feed, first 5 pages + var userFeed = _instaApi.GetUserFeed(5); + if (userFeed.Succeeded) + { + Console.WriteLine($"Feed items (in {userFeed.Value.Pages} pages) [{currentUser.UserName}]: {userFeed.Value.Items.Count}"); + foreach (var media in userFeed.Value.Items) Console.WriteLine($"Feed item - code:{media.Code}, likes: {media.LikesCount}"); + } + // get tag feed, first 5 pages + var tagFeed = _instaApi.GetTagFeed("gm", 5); + if (userFeed.Succeeded) + { + Console.WriteLine($"Tag feed items (in {tagFeed.Value.Pages} pages) [{currentUser.UserName}]: {tagFeed.Value.Count}"); + foreach (var media in tagFeed.Value) Console.WriteLine($"Tag feed item - code: {media.Code}, likes: {media.LikesCount}"); + } + var logoutResult = _instaApi.Logout(); + if (logoutResult.Value) Console.WriteLine("Logout succeed"); + } + Console.ReadKey(); + } + } +} \ No newline at end of file diff --git a/InstaSharper.Examples/Properties/AssemblyInfo.cs b/InstaSharper.Examples/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b094a7c8 --- /dev/null +++ b/InstaSharper.Examples/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("InstaSharper.Examples")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("InstaSharper.Examples")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("620d3db5-5636-4a54-a7d7-600c6518c20e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/InstagramAPI.Tests/InstagramAPI.Tests.xproj b/InstaSharper.Tests/InstaSharper.Tests.xproj similarity index 95% rename from InstagramAPI.Tests/InstagramAPI.Tests.xproj rename to InstaSharper.Tests/InstaSharper.Tests.xproj index 9e8dbfaf..042194cb 100644 --- a/InstagramAPI.Tests/InstagramAPI.Tests.xproj +++ b/InstaSharper.Tests/InstaSharper.Tests.xproj @@ -7,7 +7,7 @@ dbe6fbb7-cde7-4cdf-ab08-989a43cc4d46 - InstagramAPI.Tests + InstaSharper.Tests .\obj .\bin\ v4.5.2 diff --git a/InstagramAPI.Tests/Properties/AssemblyInfo.cs b/InstaSharper.Tests/Properties/AssemblyInfo.cs similarity index 85% rename from InstagramAPI.Tests/Properties/AssemblyInfo.cs rename to InstaSharper.Tests/Properties/AssemblyInfo.cs index def343ae..6ce07dda 100644 --- a/InstagramAPI.Tests/Properties/AssemblyInfo.cs +++ b/InstaSharper.Tests/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; +using Xunit; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -7,8 +8,10 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("InstagramApi.Tests")] +[assembly: AssemblyProduct("InstaSharper.Tests")] [assembly: AssemblyTrademark("")] +[assembly: CollectionBehavior(DisableTestParallelization = true)] + // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/InstagramAPI.Tests/Tests/ApiInstanceBuilderTest.cs b/InstaSharper.Tests/Tests/ApiInstanceBuilderTest.cs similarity index 68% rename from InstagramAPI.Tests/Tests/ApiInstanceBuilderTest.cs rename to InstaSharper.Tests/Tests/ApiInstanceBuilderTest.cs index 74f9f3fa..82e45152 100644 --- a/InstagramAPI.Tests/Tests/ApiInstanceBuilderTest.cs +++ b/InstaSharper.Tests/Tests/ApiInstanceBuilderTest.cs @@ -1,9 +1,10 @@ -using InstagramAPI.API.Builder; -using InstagramAPI.Tests.Utils; +using InstaSharper.API.Builder; +using InstaSharper.Tests.Utils; using Xunit; -namespace InstagramAPI.Tests.Tests +namespace InstaSharper.Tests.Tests { + [Collection("InstaSharper Tests")] public class ApiInstanceBuilderTest { [Fact] diff --git a/InstagramAPI.Tests/Tests/LoginTest.cs b/InstaSharper.Tests/Tests/AuthTest.cs similarity index 56% rename from InstagramAPI.Tests/Tests/LoginTest.cs rename to InstaSharper.Tests/Tests/AuthTest.cs index 12bf81f5..a631a4a1 100644 --- a/InstagramAPI.Tests/Tests/LoginTest.cs +++ b/InstaSharper.Tests/Tests/AuthTest.cs @@ -1,36 +1,34 @@ using System; -using InstagramAPI.Classes; -using InstagramAPI.Tests.Utils; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; using Xunit; using Xunit.Abstractions; -namespace InstagramAPI.Tests.Tests +namespace InstaSharper.Tests.Tests { - public class LoginTest + [Collection("InstaSharper Tests")] + public class AuthTest { - public LoginTest(ITestOutputHelper output) + public AuthTest(ITestOutputHelper output) { - this.output = output; + _output = output; } - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; [Fact] public async void UserLoginFailTest() { - //arrange var username = "alex_codegarage"; var password = "boombaby!"; var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData { UserName = username, Password = password }); - output.WriteLine("Got API instance"); - //act + _output.WriteLine("Got API instance"); var loginResult = await apiInstance.LoginAsync(); - //assert Assert.False(loginResult.Succeeded); Assert.False(apiInstance.IsUserAuthenticated); } @@ -38,19 +36,16 @@ public async void UserLoginFailTest() [Fact] public async void UserLoginSuccessTest() { - //arrange var username = "alex_codegarage"; var password = Environment.GetEnvironmentVariable("instaapiuserpassword"); - var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials - { - UserName = username, - Password = password - }); - output.WriteLine("Got API instance"); - //act + var apiInstance = TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = username, + Password = password + }); + Assert.False(apiInstance.IsUserAuthenticated); + var loginResult = await apiInstance.LoginAsync(); - //assert Assert.True(loginResult.Succeeded); Assert.True(apiInstance.IsUserAuthenticated); } diff --git a/InstaSharper.Tests/Tests/DiscoverTest.cs b/InstaSharper.Tests/Tests/DiscoverTest.cs new file mode 100644 index 00000000..c9c3acd0 --- /dev/null +++ b/InstaSharper.Tests/Tests/DiscoverTest.cs @@ -0,0 +1,40 @@ +using System; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace InstaSharper.Tests.Tests +{ + [Collection("InstaSharper Tests")] + public class DiscoverTest + { + public DiscoverTest(ITestOutputHelper output) + { + _output = output; + } + + private readonly ITestOutputHelper _output; + private readonly string _username = "alex_codegarage"; + private readonly string _password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + + [Fact] + public async void ExploreTest() + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var result = await apiInstance.GetExploreFeedAsync(0); + var exploreGeed = result.Value; + //assert + Assert.True(result.Succeeded); + Assert.NotNull(exploreGeed); + } + } +} \ No newline at end of file diff --git a/InstaSharper.Tests/Tests/FeedTest.cs b/InstaSharper.Tests/Tests/FeedTest.cs new file mode 100644 index 00000000..b0dc4588 --- /dev/null +++ b/InstaSharper.Tests/Tests/FeedTest.cs @@ -0,0 +1,82 @@ +using System; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace InstaSharper.Tests.Tests +{ + [Collection("InstaSharper Tests")] + public class FeedTest + { + public FeedTest(ITestOutputHelper output) + { + _output = output; + } + + private readonly ITestOutputHelper _output; + private readonly string _username = "alex_codegarage"; + private readonly string _password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + + [Theory] + [InlineData("christmas")] + [InlineData("rock")] + public async void GetTagFeedTest(string tag) + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var result = await apiInstance.GetTagFeedAsync(tag); + var tagFeed = result.Value; + //assert + Assert.True(result.Succeeded); + Assert.NotNull(tagFeed); + } + + + [Theory] + [InlineData("rock")] + public async void GetUserTagFeedTest(string username) + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var result = await apiInstance.GetUserTagsAsync(username, 5); + var tagFeed = result.Value; + //assert + Assert.True(result.Succeeded); + Assert.NotNull(tagFeed); + } + + [Fact] + public async void GetUserFeedTest() + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var getFeedResult = await apiInstance.GetUserFeedAsync(5); + var feed = getFeedResult.Value; + //assert + Assert.True(getFeedResult.Succeeded); + Assert.NotNull(feed); + } + } +} \ No newline at end of file diff --git a/InstaSharper.Tests/Tests/FollowersTest.cs b/InstaSharper.Tests/Tests/FollowersTest.cs new file mode 100644 index 00000000..679000a7 --- /dev/null +++ b/InstaSharper.Tests/Tests/FollowersTest.cs @@ -0,0 +1,57 @@ +using System; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace InstaSharper.Tests.Tests +{ + [Collection("InstaSharper Tests")] + public class FollowersTest + { + public FollowersTest(ITestOutputHelper output) + { + _output = output; + } + + private readonly ITestOutputHelper _output; + + [Theory] + [InlineData("discovery")] + public async void GetUserFollowersTest(string username) + { + var currentUsername = "alex_codegarage"; + var password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + var apiInstance = TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = currentUsername, + Password = password + }); + if (!TestHelpers.Login(apiInstance, _output)) return; + var result = await apiInstance.GetUserFollowersAsync(username, 10); + var followers = result.Value; + //assert + Assert.True(result.Succeeded); + Assert.NotNull(followers); + } + + [Fact] + public async void GetCurrentUserFollwersTest() + { + var username = "alex_codegarage"; + var password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + var apiInstance = TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = username, + Password = password + }); + if (!TestHelpers.Login(apiInstance, _output)) return; + if (!TestHelpers.Login(apiInstance, _output)) return; + var result = await apiInstance.GetCurrentUserFollowersAsync(); + var followers = result.Value; + //assert + Assert.True(result.Succeeded); + Assert.NotNull(followers); + } + } +} \ No newline at end of file diff --git a/InstaSharper.Tests/Tests/MediaTest.cs b/InstaSharper.Tests/Tests/MediaTest.cs new file mode 100644 index 00000000..ea791835 --- /dev/null +++ b/InstaSharper.Tests/Tests/MediaTest.cs @@ -0,0 +1,67 @@ +using System; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace InstaSharper.Tests.Tests +{ + [Collection("InstaSharper Tests")] + public class MediaTest + { + private readonly ITestOutputHelper _output; + + public MediaTest(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [InlineData("1379932752706850783")] + public async void GetMediaByCodeTest(string mediaId) + { + //arrange + var username = "alex_codegarage"; + var password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + var apiInstance = TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = username, + Password = password + }); + //act + _output.WriteLine($"Trying to login as user: {username}"); + if (!TestHelpers.Login(apiInstance, _output)) return; + _output.WriteLine($"Getting media by ID: {mediaId}"); + var media = await apiInstance.GetMediaByCodeAsync(mediaId); + //assert + Assert.NotNull(media); + } + + [Theory] + [InlineData("alex_codegarage")] + [InlineData("instagram")] + [InlineData("therock")] + public async void GetUserMediaListTest(string userToFetch) + { + //arrange + var username = "alex_codegarage"; + var password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + var apiInstance = TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = username, + Password = password + }); + var random = new Random(DateTime.Today.Millisecond); + var pages = random.Next(1, 10); + //act + _output.WriteLine($"Trying to login as user: {username}"); + if (!TestHelpers.Login(apiInstance, _output)) return; + _output.WriteLine($"Getting posts of user: {userToFetch}"); + + var posts = await apiInstance.GetUserMediaAsync(userToFetch, pages); + //assert + Assert.NotNull(posts); + Assert.Equal(userToFetch, posts.Value[random.Next(0, posts.Value.Count)].User.UserName); + } + } +} \ No newline at end of file diff --git a/InstaSharper.Tests/Tests/UserInfoTest.cs b/InstaSharper.Tests/Tests/UserInfoTest.cs new file mode 100644 index 00000000..c5ee9869 --- /dev/null +++ b/InstaSharper.Tests/Tests/UserInfoTest.cs @@ -0,0 +1,61 @@ +using System; +using InstaSharper.Classes; +using InstaSharper.Tests.Utils; +using Xunit; +using Xunit.Abstractions; + +namespace InstaSharper.Tests.Tests +{ + [Collection("InstaSharper Tests")] + public class UserInfoTest + { + public UserInfoTest(ITestOutputHelper output) + { + _output = output; + } + + private readonly ITestOutputHelper _output; + private readonly string _username = "alex_codegarage"; + private readonly string _password = Environment.GetEnvironmentVariable("instaapiuserpassword"); + + [Fact] + public async void GetCurrentUserTest() + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var getUserResult = await apiInstance.GetCurrentUserAsync(); + var user = getUserResult.Value; + //assert + Assert.True(getUserResult.Succeeded); + Assert.NotNull(user); + Assert.Equal(user.UserName, _username); + } + + [Fact] + public async void GetUserTest() + { + //arrange + var apiInstance = + TestHelpers.GetDefaultInstaApiInstance(new UserSessionData + { + UserName = _username, + Password = _password + }); + //act + if (!TestHelpers.Login(apiInstance, _output)) return; + var getUserResult = await apiInstance.GetUserAsync(_username); + var user = getUserResult.Value; + //assert + Assert.True(getUserResult.Succeeded); + Assert.NotNull(user); + Assert.Equal(user.UserName, _username); + } + } +} \ No newline at end of file diff --git a/InstagramAPI.Tests/Utils/TestHelpers.cs b/InstaSharper.Tests/Utils/TestHelpers.cs similarity index 66% rename from InstagramAPI.Tests/Utils/TestHelpers.cs rename to InstaSharper.Tests/Utils/TestHelpers.cs index 3bcfdd61..e38f6448 100644 --- a/InstagramAPI.Tests/Utils/TestHelpers.cs +++ b/InstaSharper.Tests/Utils/TestHelpers.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using InstagramAPI.API; -using InstagramAPI.API.Builder; -using InstagramAPI.Classes; +using InstaSharper.API; +using InstaSharper.API.Builder; +using InstaSharper.Classes; using Xunit.Abstractions; -namespace InstagramAPI.Tests.Utils +namespace InstaSharper.Tests.Utils { public class TestHelpers { @@ -17,7 +16,7 @@ public static IInstaApi GetDefaultInstaApiInstance(string username) return apiInstance; } - public static IInstaApi GetDefaultInstaApiInstance(UserCredentials user) + public static IInstaApi GetDefaultInstaApiInstance(UserSessionData user) { var apiInstance = new InstaApiBuilder() .SetUser(user) @@ -26,9 +25,9 @@ public static IInstaApi GetDefaultInstaApiInstance(UserCredentials user) return apiInstance; } - public static async Task Login(IInstaApi apiInstance, ITestOutputHelper output) + public static bool Login(IInstaApi apiInstance, ITestOutputHelper output) { - var loginResult = await apiInstance.LoginAsync(); + var loginResult = apiInstance.Login(); if (!loginResult.Succeeded) { output.WriteLine($"Can't login: {loginResult.Message}"); diff --git a/InstagramAPI.Tests/Utils/TestLogger.cs b/InstaSharper.Tests/Utils/TestLogger.cs similarity index 65% rename from InstagramAPI.Tests/Utils/TestLogger.cs rename to InstaSharper.Tests/Utils/TestLogger.cs index bd2cba31..89c8fa92 100644 --- a/InstagramAPI.Tests/Utils/TestLogger.cs +++ b/InstaSharper.Tests/Utils/TestLogger.cs @@ -1,6 +1,6 @@ -using InstagramAPI.Logger; +using InstaSharper.Logger; -namespace InstagramAPI.Tests.Utils +namespace InstaSharper.Tests.Utils { internal class TestLogger : ILogger { diff --git a/InstagramAPI.Tests/project.json b/InstaSharper.Tests/project.json similarity index 92% rename from InstagramAPI.Tests/project.json rename to InstaSharper.Tests/project.json index 9722838a..abf9906b 100644 --- a/InstagramAPI.Tests/project.json +++ b/InstaSharper.Tests/project.json @@ -3,7 +3,7 @@ "testRunner": "xunit", "dependencies": { "xunit": "2.2.0-beta2-build3300", - "InstagramAPI": "1.2.0", + "InstaSharper": "1.2.1", "dotnet-test-xunit": "2.2.0-preview2-build1029" }, "frameworks": { diff --git a/InstagramAPI.sln b/InstaSharper.sln similarity index 82% rename from InstagramAPI.sln rename to InstaSharper.sln index c2aaeab9..bd2eb111 100644 --- a/InstagramAPI.sln +++ b/InstaSharper.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InstagramAPI", "InstagramAPI\InstagramAPI.xproj", "{449A948D-CC65-4D1A-8159-6FA232F972D9}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InstaSharper", "InstaSharper\InstaSharper.xproj", "{449A948D-CC65-4D1A-8159-6FA232F972D9}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InstagramAPI.Tests", "InstagramAPI.Tests\InstagramAPI.Tests.xproj", "{DBE6FBB7-CDE7-4CDF-AB08-989A43CC4D46}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InstaSharper.Tests", "InstaSharper.Tests\InstaSharper.Tests.xproj", "{DBE6FBB7-CDE7-4CDF-AB08-989A43CC4D46}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/InstagramAPI/API/Builder/IInstaApiBuilder.cs b/InstaSharper/API/Builder/IInstaApiBuilder.cs similarity index 70% rename from InstagramAPI/API/Builder/IInstaApiBuilder.cs rename to InstaSharper/API/Builder/IInstaApiBuilder.cs index 1bf3ff99..8f0b7f00 100644 --- a/InstagramAPI/API/Builder/IInstaApiBuilder.cs +++ b/InstaSharper/API/Builder/IInstaApiBuilder.cs @@ -1,8 +1,8 @@ using System.Net.Http; -using InstagramAPI.Classes; -using InstagramAPI.Logger; +using InstaSharper.Classes; +using InstaSharper.Logger; -namespace InstagramAPI.API.Builder +namespace InstaSharper.API.Builder { public interface IInstaApiBuilder { @@ -11,6 +11,6 @@ public interface IInstaApiBuilder IInstaApiBuilder UseHttpClient(HttpClient httpClient); IInstaApiBuilder UseHttpClientHandler(HttpClientHandler handler); IInstaApiBuilder SetUserName(string username); - IInstaApiBuilder SetUser(UserCredentials user); + IInstaApiBuilder SetUser(UserSessionData user); } } \ No newline at end of file diff --git a/InstagramAPI/API/Builder/InstaApiBuilder.cs b/InstaSharper/API/Builder/InstaApiBuilder.cs similarity index 87% rename from InstagramAPI/API/Builder/InstaApiBuilder.cs rename to InstaSharper/API/Builder/InstaApiBuilder.cs index 955f9bae..d260db4b 100644 --- a/InstagramAPI/API/Builder/InstaApiBuilder.cs +++ b/InstaSharper/API/Builder/InstaApiBuilder.cs @@ -1,10 +1,10 @@ using System; using System.Net.Http; -using InstagramAPI.Classes; -using InstagramAPI.Classes.Android.DeviceInfo; -using InstagramAPI.Logger; +using InstaSharper.Classes; +using InstaSharper.Classes.Android.DeviceInfo; +using InstaSharper.Logger; -namespace InstagramAPI.API.Builder +namespace InstaSharper.API.Builder { public class InstaApiBuilder : IInstaApiBuilder { @@ -12,7 +12,7 @@ public class InstaApiBuilder : IInstaApiBuilder private HttpClientHandler _httpHandler = new HttpClientHandler(); private ILogger _logger; private ApiRequestMessage _requestMessage; - private UserCredentials _user; + private UserSessionData _user; public IInstaApi Build() { @@ -59,11 +59,11 @@ public IInstaApiBuilder UseHttpClientHandler(HttpClientHandler handler) public IInstaApiBuilder SetUserName(string username) { - _user = new UserCredentials {UserName = username}; + _user = new UserSessionData {UserName = username}; return this; } - public IInstaApiBuilder SetUser(UserCredentials user) + public IInstaApiBuilder SetUser(UserSessionData user) { _user = user; return this; diff --git a/InstaSharper/API/IInstaApi.cs b/InstaSharper/API/IInstaApi.cs new file mode 100644 index 00000000..e31e28b6 --- /dev/null +++ b/InstaSharper/API/IInstaApi.cs @@ -0,0 +1,54 @@ +using System.Threading.Tasks; +using InstaSharper.Classes; +using InstaSharper.Classes.Models; + +namespace InstaSharper.API +{ + public interface IInstaApi + { + #region Properties + + bool IsUserAuthenticated { get; } + + #endregion + + #region Sync Members + + IResult GetUser(string username); + Task> GetUserAsync(string username); + IResult GetUserMedia(string username, int maxPages = 0); + Task> GetUserMediaAsync(string username, int maxPages = 0); + IResult GetMediaByCode(string postCode); + Task> GetMediaByCodeAsync(string postCode); + IResult Login(); + IResult Logout(); + + IResult GetCurrentUser(); + IResult GetCurentUserFollowers(int maxPages = 0); + IResult GetUserFollowers(string username, int maxPages = 0); + + IResult GetTagFeed(string tag, int maxPages = 0); + + IResult GetExploreFeed(int maxPages = 0); + IResult GetUserTags(string username, int maxPages = 0); + + #endregion + + #region Async Members + + Task> LoginAsync(); + Task> LogoutAsync(); + IResult GetUserFeed(int maxPages = 0); + Task> GetUserFeedAsync(int maxPages = 0); + Task> GetCurrentUserAsync(); + + Task> GetTagFeedAsync(string tag, int maxPages = 0); + Task> GetUserFollowersAsync(string username, int maxPages = 0); + Task> GetCurrentUserFollowersAsync(int maxPages = 0); + + Task> GetExploreFeedAsync(int maxPages = 0); + Task> GetUserTagsAsync(string username, int maxPages = 0); + + #endregion + } +} \ No newline at end of file diff --git a/InstaSharper/API/InstaApi.cs b/InstaSharper/API/InstaApi.cs new file mode 100644 index 00000000..ac1b7f0d --- /dev/null +++ b/InstaSharper/API/InstaApi.cs @@ -0,0 +1,584 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using InstaSharper.Classes; +using InstaSharper.Classes.Android.DeviceInfo; +using InstaSharper.Classes.Models; +using InstaSharper.Converters; +using InstaSharper.Converters.Json; +using InstaSharper.Helpers; +using InstaSharper.Logger; +using InstaSharper.ResponseWrappers; +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace InstaSharper.API +{ + public class InstaApi : IInstaApi + { + private readonly AndroidDevice _deviceInfo; + private readonly HttpClient _httpClient; + private readonly HttpClientHandler _httpHandler; + private readonly ILogger _logger; + private readonly ApiRequestMessage _requestMessage; + private readonly UserSessionData _user; + + public InstaApi(UserSessionData user, + ILogger logger, + HttpClient httpClient, + HttpClientHandler httpHandler, + ApiRequestMessage requestMessage, + AndroidDevice deviceInfo) + { + _user = user; + _logger = logger; + _httpClient = httpClient; + _httpHandler = httpHandler; + _requestMessage = requestMessage; + _deviceInfo = deviceInfo; + } + + public bool IsUserAuthenticated { get; private set; } + + #region sync part + + public IResult GetMediaByCode(string postCode) + { + return GetMediaByCodeAsync(postCode).Result; + } + + public IResult GetUser(string username) + { + return GetUserAsync(username).Result; + } + + public IResult GetUserFeed(int maxPages = 0) + { + return GetUserFeedAsync(maxPages).Result; + } + + public IResult GetUserMedia(string username, int maxPages = 0) + { + return GetUserMediaAsync(username, maxPages).Result; + } + + public IResult Login() + { + return LoginAsync().Result; + } + + public IResult Logout() + { + return LogoutAsync().Result; + } + + public IResult GetUserFollowers(string username, int maxPages = 0) + { + return GetUserFollowersAsync(username, maxPages).Result; + } + + public IResult GetTagFeed(string tag, int maxPages = 0) + { + return GetTagFeedAsync(tag, maxPages).Result; + } + + public IResult GetExploreFeed(int maxPages = 0) + { + return GetExploreFeedAsync(maxPages).Result; + } + + public IResult GetUserTags(string username, int maxPages = 0) + { + return GetUserTagsAsync(username, maxPages).Result; + } + + public IResult GetCurentUserFollowers(int maxPages = 0) + { + return GetCurrentUserFollowersAsync(maxPages).Result; + } + + #endregion + + #region async part + + public async Task> GetMediaByCodeAsync(string postCode) + { + ValidateUser(); + var mediaUri = UriCreator.GetMediaUri(postCode); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, mediaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var mediaResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + if (mediaResponse.Medias?.Count != 1) + { + string errorMessage = $"Got wrong media count for request with media id={postCode}"; + _logger.Write(errorMessage); + return Result.Fail(errorMessage); + } + var converter = ConvertersFabric.GetSingleMediaConverter(mediaResponse.Medias.FirstOrDefault()); + return Result.Success(converter.Convert()); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaMedia)null); + } + + public async Task> GetUserAsync(string username) + { + ValidateUser(); + var userUri = UriCreator.GetUserUri(username); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userUri, _deviceInfo); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_TIMEZONE, InstaApiConstants.TIMEZONE_OFFSET.ToString())); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_COUNT, "1")); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_RANK_TOKEN, _user.RankToken)); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var userInfo = JsonConvert.DeserializeObject(json); + var user = userInfo.Users?.FirstOrDefault(u => u.UserName == username); + if (user == null) + { + string errorMessage = $"Can't find this user: {username}"; + _logger.Write(errorMessage); + return Result.Fail(errorMessage); + } + var converter = ConvertersFabric.GetUserConverter(user); + return Result.Success(converter.Convert()); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaUser)null); + } + + public IResult GetCurrentUser() + { + return GetCurrentUserAsync().Result; + } + + public async Task> GetCurrentUserAsync() + { + ValidateUser(); + ValidateLoggedIn(); + var instaUri = UriCreator.GetCurrentUserUri(); + dynamic jsonObject = new JObject(); + jsonObject._uuid = _deviceInfo.DeviceGuid; + jsonObject._uid = _user.LoggedInUder.Pk; + jsonObject._csrftoken = _user.CsrfToken; + var fields = new Dictionary + { + {"_uuid", _deviceInfo.DeviceGuid.ToString()}, + {"_uid", _user.LoggedInUder.Pk}, + {"_csrftoken", _user.CsrfToken} + }; + var request = HttpHelper.GetDefaultRequest(HttpMethod.Post, instaUri, _deviceInfo); + request.Content = new FormUrlEncodedContent(fields); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var user = JsonConvert.DeserializeObject(json); + var converter = ConvertersFabric.GetUserConverter(user.User); + var userConverted = converter.Convert(); + + return Result.Success(userConverted); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaUser)null); + } + + public async Task> GetUserFeedAsync(int maxPages = 0) + { + ValidateUser(); + ValidateLoggedIn(); + var userFeedUri = UriCreator.GetUserFeedUri(); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userFeedUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + var feed = new InstaFeed(); + if (response.StatusCode != HttpStatusCode.OK) return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaFeed)null); + var feedResponse = JsonConvert.DeserializeObject(json, new InstaFeedResponseDataConverter()); + var converter = ConvertersFabric.GetFeedConverter(feedResponse); + var feedConverted = converter.Convert(); + feed.Medias.AddRange(feedConverted.Medias); + var nextId = feedResponse.NextMaxId; + while (feedResponse.MoreAvailable && (feed.Pages < maxPages)) + { + if (string.IsNullOrEmpty(nextId)) break; + var nextFeed = await GetUserFeedWithMaxIdAsync(nextId); + if (!nextFeed.Succeeded) Result.Success($"Not all pages was downloaded: {nextFeed.Message}", feed); + nextId = nextFeed.Value.NextMaxId; + feed.Medias.AddRange(nextFeed.Value.Items.Select(ConvertersFabric.GetSingleMediaConverter).Select(conv => conv.Convert())); + feed.Pages++; + } + return Result.Success(feed); + } + + public async Task> GetCurrentUserFollowersAsync(int maxPages = 0) + { + ValidateUser(); + return await GetUserFollowersAsync(_user.UserName, maxPages); + } + + public async Task> GetExploreFeedAsync(int maxPages = 0) + { + ValidateUser(); + ValidateLoggedIn(); + try + { + if (maxPages == 0) maxPages = int.MaxValue; + var exploreUri = UriCreator.GetExploreUri(); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, exploreUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + var exploreFeed = new InstaFeed(); + if (response.StatusCode != HttpStatusCode.OK) return Result.Fail("", (InstaFeed)null); + var mediaResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + exploreFeed.Medias.AddRange(mediaResponse.Medias.Select(ConvertersFabric.GetSingleMediaConverter).Select(converter => converter.Convert())); + exploreFeed.Stories.AddRange(mediaResponse.Stories.Select(ConvertersFabric.GetSingleStoryConverter).Select(converter => converter.Convert())); + var pages = 1; + var nextId = mediaResponse.NextMaxId; + while (!string.IsNullOrEmpty(nextId) && (pages < maxPages)) if (string.IsNullOrEmpty(nextId) || (nextId == "0")) break; + return Result.Success(exploreFeed); + } + catch (Exception exception) + { + return Result.Fail(exception.Message, (InstaFeed)null); + } + } + + public async Task> GetUserTagsAsync(string username, int maxPages = 0) + { + ValidateUser(); + ValidateLoggedIn(); + try + { + if (maxPages == 0) maxPages = int.MaxValue; + var user = await GetUserAsync(username); + if (!user.Succeeded || string.IsNullOrEmpty(user.Value.Pk)) return Result.Fail($"Unable to get user {username}", (InstaMediaList)null); + var uri = UriCreator.GetUserTagsUri(user.Value?.Pk, _user.RankToken); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, uri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + var userTags = new InstaMediaList(); + if (response.StatusCode != HttpStatusCode.OK) return Result.Fail("", (InstaMediaList)null); + var mediaResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + var nextId = mediaResponse.NextMaxId; + userTags.AddRange(mediaResponse.Medias.Select(ConvertersFabric.GetSingleMediaConverter).Select(converter => converter.Convert())); + var pages = 1; + while (!string.IsNullOrEmpty(nextId) && (pages < maxPages)) + { + uri = UriCreator.GetUserTagsUri(user.Value?.Pk, _user.RankToken, nextId); + var nextMedia = await GetUserMediaListWithMaxIdAsync(uri); + if (!nextMedia.Succeeded) Result.Success($"Not all pages was downloaded: {nextMedia.Message}", userTags); + nextId = nextMedia.Value.NextMaxId; + userTags.AddRange(mediaResponse.Medias.Select(ConvertersFabric.GetSingleMediaConverter).Select(converter => converter.Convert())); + pages++; + } + return Result.Success(userTags); + } + catch (Exception exception) + { + return Result.Fail(exception.Message, (InstaMediaList)null); + } + } + + public async Task> GetUserFollowersAsync(string username, int maxPages = 0) + { + ValidateUser(); + ValidateLoggedIn(); + try + { + if (maxPages == 0) maxPages = int.MaxValue; + var user = await GetUserAsync(username); + var userFeedUri = UriCreator.GetUserFollowersUri(user.Value.Pk, _user.RankToken); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userFeedUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + var followers = new InstaUserList(); + if (response.StatusCode != HttpStatusCode.OK) return Result.Fail("", (InstaUserList)null); + var followersResponse = JsonConvert.DeserializeObject(json); + if (!followersResponse.IsOK()) Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaUserList)null); + followers.AddRange(followersResponse.Items.Select(ConvertersFabric.GetUserConverter).Select(converter => converter.Convert())); + if (!followersResponse.IsBigList) return Result.Success(followers); + var pages = 1; + while (!string.IsNullOrEmpty(followersResponse.NextMaxId) && (pages < maxPages)) + { + var nextFollowers = Result.Success(followersResponse); + nextFollowers = await GetUserFollowersWithMaxIdAsync(username, nextFollowers.Value.NextMaxId); + if (!nextFollowers.Succeeded) Result.Success($"Not all pages was downloaded: {nextFollowers.Message}", followers); + followers.AddRange(nextFollowers.Value.Items.Select(ConvertersFabric.GetUserConverter).Select(converter => converter.Convert())); + pages++; + } + return Result.Success(followers); + } + catch (Exception exception) + { + return Result.Fail(exception.Message, (InstaUserList)null); + } + } + + private async Task> GetUserFollowersWithMaxIdAsync(string username, string maxId) + { + ValidateUser(); + try + { + if (!IsUserAuthenticated) throw new ArgumentException("user must be authenticated"); + var user = await GetUserAsync(username); + var userFeedUri = UriCreator.GetUserFollowersUri(user.Value.Pk, _user.RankToken, maxId); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userFeedUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var followersResponse = JsonConvert.DeserializeObject(json); + if (!followersResponse.IsOK()) Result.Fail("", (InstaFollowersResponse)null); + return Result.Success(followersResponse); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaFollowersResponse)null); + } + catch (Exception exception) + { + return Result.Fail(exception.Message, (InstaFollowersResponse)null); + } + } + + public async Task> CheckpointAsync(string checkPointUrl) + { + if (string.IsNullOrEmpty(checkPointUrl)) return Result.Fail("Empty checkpoint URL", false); + var instaUri = new Uri(checkPointUrl); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) return Result.Success(true); + return Result.Fail(GetBadStatusFromJsonString(json).Message, false); + } + + public async Task> GetTagFeedAsync(string tag, int maxPages = 0) + { + ValidateUser(); + ValidateLoggedIn(); + var userFeedUri = UriCreator.GetTagFeedUri(tag); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userFeedUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var feedResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + var converter = ConvertersFabric.GetMediaListConverter(feedResponse); + var tagFeed = new InstaFeed(); + tagFeed.Medias.AddRange(converter.Convert()); + var nextId = feedResponse.NextMaxId; + while (feedResponse.MoreAvailable && (tagFeed.Pages < maxPages)) + { + var nextMedia = await GetTagFeedWithMaxIdAsync(tag, nextId); + if (!nextMedia.Succeeded) Result.Success($"Not all pages was downloaded: {nextMedia.Message}", tagFeed); + nextId = nextMedia.Value.NextMaxId; + converter = ConvertersFabric.GetMediaListConverter(nextMedia.Value); + tagFeed.Medias.AddRange(converter.Convert()); + tagFeed.Pages++; + } + return Result.Success(tagFeed); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaFeed)null); + } + + private async Task> GetTagFeedWithMaxIdAsync(string tag, string nextId) + { + ValidateUser(); + ValidateLoggedIn(); + try + { + var instaUri = UriCreator.GetTagFeedUri(tag); + instaUri = new UriBuilder(instaUri) { Query = $"max_id={nextId}" }.Uri; + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var feedResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + return Result.Success(feedResponse); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaMediaListResponse)null); + } + catch (Exception exception) + { + return Result.Fail(exception.Message, (InstaMediaListResponse)null); + } + } + + public async Task> GetUserMediaAsync(string username, int maxPages = 0) + { + ValidateUser(); + if (maxPages == 0) maxPages = int.MaxValue; + var user = GetUser(username).Value; + var instaUri = UriCreator.GetUserMediaListUri(user.Pk); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var mediaResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + var converter = ConvertersFabric.GetMediaListConverter(mediaResponse); + var mediaList = converter.Convert(); + var nextId = mediaResponse.NextMaxId; + while (mediaResponse.MoreAvailable && (mediaList.Pages < maxPages)) + { + instaUri = UriCreator.GetMediaListWithMaxIdUri(user.Pk, nextId); + var nextMedia = await GetUserMediaListWithMaxIdAsync(instaUri); + if (!nextMedia.Succeeded) Result.Success($"Not all pages was downloaded: {nextMedia.Message}", mediaList); + nextId = nextMedia.Value.NextMaxId; + mediaList.AddRange(converter.Convert()); + mediaList.Pages++; + } + return Result.Success(mediaList); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaMediaList)null); + } + + + public async Task> LoginAsync() + { + ValidateUser(); + ValidateRequestMessage(); + try + { + var csrftoken = string.Empty; + var firstResponse = await _httpClient.GetAsync(_httpClient.BaseAddress); + var cookies = _httpHandler.CookieContainer.GetCookies(_httpClient.BaseAddress); + foreach (Cookie cookie in cookies) if (cookie.Name == InstaApiConstants.CSRFTOKEN) csrftoken = cookie.Value; + _user.CsrfToken = csrftoken; + var instaUri = UriCreator.GetLoginUri(); + var signature = $"{_requestMessage.GenerateSignature()}.{_requestMessage.GetMessageString()}"; + var fields = new Dictionary + { + {InstaApiConstants.HEADER_IG_SIGNATURE, signature}, + {InstaApiConstants.HEADER_IG_SIGNATURE_KEY_VERSION, InstaApiConstants.IG_SIGNATURE_KEY_VERSION} + }; + var request = HttpHelper.GetDefaultRequest(HttpMethod.Post, instaUri, _deviceInfo); + request.Content = new FormUrlEncodedContent(fields); + request.Properties.Add(InstaApiConstants.HEADER_IG_SIGNATURE, signature); + request.Properties.Add(InstaApiConstants.HEADER_IG_SIGNATURE_KEY_VERSION, InstaApiConstants.IG_SIGNATURE_KEY_VERSION); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var loginInfo = + JsonConvert.DeserializeObject(json); + IsUserAuthenticated = (loginInfo.User != null) && (loginInfo.User.UserName == _user.UserName); + var converter = ConvertersFabric.GetUserConverter(loginInfo.User); + _user.LoggedInUder = converter.Convert(); + _user.RankToken = $"{_user.LoggedInUder.Pk}_{_requestMessage.phone_id}"; + return Result.Success(true); + } + else + { + var loginInfo = GetBadStatusFromJsonString(json); + if (loginInfo.ErrorType == "checkpoint_logged_out") + { + var checkPointResult = await CheckpointAsync(loginInfo.CheckPointUrl); + IsUserAuthenticated = checkPointResult.Succeeded; + return Result.Success(checkPointResult.Value); + } + return Result.Fail(loginInfo.Message, false); + } + } + catch (Exception exception) + { + return Result.Fail(exception.Message, false); + } + } + + public async Task> LogoutAsync() + { + ValidateUser(); + ValidateLoggedIn(); + try + { + var instaUri = UriCreator.GetLogoutUri(); + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var logoutInfo = JsonConvert.DeserializeObject(json); + IsUserAuthenticated = logoutInfo.Status == "ok"; + return Result.Success(true); + } + else + { + var logoutInfo = GetBadStatusFromJsonString(json); + return Result.Fail(logoutInfo.Message, false); + } + } + catch (Exception exception) + { + return Result.Fail(exception.Message, false); + } + } + + #endregion + + #region private part + + private void ValidateUser() + { + if (string.IsNullOrEmpty(_user.UserName) || string.IsNullOrEmpty(_user.Password)) throw new ArgumentException("user name and password must be specified"); + } + + private void ValidateLoggedIn() + { + if (!IsUserAuthenticated) throw new ArgumentException("user must be authenticated"); + } + + private void ValidateRequestMessage() + { + if ((_requestMessage == null) || _requestMessage.IsEmpty()) throw new ArgumentException("API request message null or empty"); + } + + private BadStatusResponse GetBadStatusFromJsonString(string json) + { + var badStatus = new BadStatusResponse(); + try { badStatus = JsonConvert.DeserializeObject(json); } + catch (Exception ex) + { + badStatus.Message = ex.Message; + } + return badStatus; + } + + private async Task> GetUserFeedWithMaxIdAsync(string maxId) + { + Uri instaUri; + if (!Uri.TryCreate(new Uri(InstaApiConstants.INSTAGRAM_URL), InstaApiConstants.TIMELINEFEED, out instaUri)) throw new Exception("Cant create search user URI"); + var userUriBuilder = new UriBuilder(instaUri) { Query = $"max_id={maxId}" }; + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userUriBuilder.Uri, _deviceInfo); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_PHONE_ID, _requestMessage.phone_id)); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_TIMEZONE, InstaApiConstants.TIMEZONE_OFFSET.ToString())); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var feedResponse = JsonConvert.DeserializeObject(json, new InstaFeedResponseDataConverter()); + return Result.Success(feedResponse); + } + return Result.Fail(GetBadStatusFromJsonString(json).Message, (InstaFeedResponse)null); + } + + private async Task> GetUserMediaListWithMaxIdAsync(Uri instaUri) + { + var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri, _deviceInfo); + var response = await _httpClient.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); + if (response.StatusCode == HttpStatusCode.OK) + { + var mediaResponse = JsonConvert.DeserializeObject(json, new InstaMediaListDataConverter()); + return Result.Success(mediaResponse); + } + return Result.Fail("", (InstaMediaListResponse)null); + } + + #endregion + } +} \ No newline at end of file diff --git a/InstagramAPI/API/InstaApiConstants.cs b/InstaSharper/API/InstaApiConstants.cs similarity index 72% rename from InstagramAPI/API/InstaApiConstants.cs rename to InstaSharper/API/InstaApiConstants.cs index 5ec2a3c6..e4efd530 100644 --- a/InstagramAPI/API/InstaApiConstants.cs +++ b/InstaSharper/API/InstaApiConstants.cs @@ -1,7 +1,8 @@ -namespace InstagramAPI.API +namespace InstaSharper.API { - public static class InstaApiConstants + internal static class InstaApiConstants { + public const string CURRENTUSER = API_SUFFIX + "/v1/accounts/current_user?edit=true"; public const string MAX_MEDIA_ID_POSTFIX = "/media/?max_id="; public const string HEADER_MAX_ID = "max_id"; public const string MEDIA = "/media/"; @@ -14,29 +15,32 @@ public static class InstaApiConstants public const string HEADER_XML_HTTP_REQUEST = "XMLHttpRequest"; public const string INSTAGRAM_URL = "https://i.instagram.com"; public const string API_SUFFIX = "/api"; - public const string GET_USER = API_SUFFIX + "/v1/accounts/login/"; public const string SEARCH_USERS = API_SUFFIX + "/v1/users/search"; public const string ACCOUNTS_LOGIN = API_SUFFIX + "/v1/accounts/login/"; - public const string TIMELINEFEED = API_SUFFIX + "/v1/feed/timeline/"; + public const string ACCOUNTS_LOGOUT = API_SUFFIX + "/v1/accounts/logout/"; + public const string EXPLORE = API_SUFFIX + "/v1/discover/explore/"; + public const string TIMELINEFEED = API_SUFFIX + "/v1/feed/timeline"; public const string USEREFEED = API_SUFFIX + "/v1/feed/user/"; + public const string GET_USER_TAGS = API_SUFFIX + "/v1/usertags/{0}/feed/"; public const string GET_MEDIA = API_SUFFIX + "/v1/media/{0}/info/"; - + public const string GET_USER_FOLLOWERS = API_SUFFIX + "/v1/friendships/{0}/followers/?rank_token={1}"; + public const string GET_TAG_FEED = API_SUFFIX + "/v1/feed/tag/{0}"; public const string HEADER_USER_AGENT = "User-Agent"; - public const string USER_AGENT = "Instagram 8.0.0 Android (23/6.0.1; 640dpi; 1440x2560; samsung; SM-G935F; hero2lte; samsungexynos8890; en_NZ)"; + public const string USER_AGENT = "Instagram 9.7.0 Android (23/6.0.1; 640dpi; 1440x2560; samsung; SM-G935F; hero2lte; samsungexynos8890; en_NZ)"; public const string HEADER_QUERY = "q"; public const string HEADER_RANK_TOKEN = "rank_token"; public const string HEADER_COUNT = "count"; - public const string IG_SIGNATURE_KEY = "9b3b9e55988c954e51477da115c58ae82dcae7ac01c735b4443a3c5923cb593a"; + public const string IG_SIGNATURE_KEY = "2f6dcdf76deb0d3fd008886d032162a79b88052b5f50538c1ee93c4fe7d02e60"; public const string HEADER_IG_SIGNATURE = "signed_body"; public const string IG_SIGNATURE_KEY_VERSION = "4"; public const string HEADER_IG_SIGNATURE_KEY_VERSION = "ig_sig_key_version"; - public const string IG_CAPABILITIES = "3Q=="; + public const string IG_CAPABILITIES = "3Ro="; public const string HEADER_IG_CAPABILITIES = "X-IG-Capabilities"; public const string IG_CONNECTION_TYPE = "WIFI"; public const string HEADER_IG_CONNECTION_TYPE = "X-IG-Connection-Type"; - public const string ACCEPT_LANGUAGE = "en-NZ"; + public const string ACCEPT_LANGUAGE = "en-US"; public const string HEADER_ACCEPT_LANGUAGE = "Accept-Language"; public const string ACCEPT_ENCODING = "gzip, deflate, sdch"; public const string HEADER_ACCEPT_ENCODING = "gzip, deflate, sdch"; diff --git a/InstagramAPI/Classes/Android/DeviceInfo/AndroidDevice.cs b/InstaSharper/Classes/Android/DeviceInfo/AndroidDevice.cs similarity index 94% rename from InstagramAPI/Classes/Android/DeviceInfo/AndroidDevice.cs rename to InstaSharper/Classes/Android/DeviceInfo/AndroidDevice.cs index c9b59e8f..f9e76ce1 100644 --- a/InstagramAPI/Classes/Android/DeviceInfo/AndroidDevice.cs +++ b/InstaSharper/Classes/Android/DeviceInfo/AndroidDevice.cs @@ -1,6 +1,6 @@ using System; -namespace InstagramAPI.Classes.Android.DeviceInfo +namespace InstaSharper.Classes.Android.DeviceInfo { public class AndroidDevice { diff --git a/InstagramAPI/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs b/InstaSharper/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs similarity index 99% rename from InstagramAPI/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs rename to InstaSharper/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs index d6ab237c..cbb50c8b 100644 --- a/InstagramAPI/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs +++ b/InstaSharper/Classes/Android/DeviceInfo/AndroidDeviceGenerator.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace InstagramAPI.Classes.Android.DeviceInfo +namespace InstaSharper.Classes.Android.DeviceInfo { public class AndroidDeviceGenerator { diff --git a/InstagramAPI/Classes/Android/DeviceInfo/ApiRequestMessage.cs b/InstaSharper/Classes/Android/DeviceInfo/ApiRequestMessage.cs similarity index 91% rename from InstagramAPI/Classes/Android/DeviceInfo/ApiRequestMessage.cs rename to InstaSharper/Classes/Android/DeviceInfo/ApiRequestMessage.cs index d9c4ba72..fb8b0c07 100644 --- a/InstagramAPI/Classes/Android/DeviceInfo/ApiRequestMessage.cs +++ b/InstaSharper/Classes/Android/DeviceInfo/ApiRequestMessage.cs @@ -1,9 +1,9 @@ using System; -using InstagramAPI.API; -using InstagramAPI.Helpers; +using InstaSharper.API; +using InstaSharper.Helpers; using Newtonsoft.Json; -namespace InstagramAPI.Classes.Android.DeviceInfo +namespace InstaSharper.Classes.Android.DeviceInfo { public class ApiRequestMessage { diff --git a/InstagramAPI/Classes/Android/LoginInfoAndroid.cs b/InstaSharper/Classes/Android/LoginInfoAndroid.cs similarity index 53% rename from InstagramAPI/Classes/Android/LoginInfoAndroid.cs rename to InstaSharper/Classes/Android/LoginInfoAndroid.cs index 737dd938..0d02205c 100644 --- a/InstagramAPI/Classes/Android/LoginInfoAndroid.cs +++ b/InstaSharper/Classes/Android/LoginInfoAndroid.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Android +namespace InstaSharper.Classes.Android { public class LoginInfoAndroid { diff --git a/InstagramAPI/Classes/IResult.cs b/InstaSharper/Classes/IResult.cs similarity index 80% rename from InstagramAPI/Classes/IResult.cs rename to InstaSharper/Classes/IResult.cs index eeb8af9a..e31a3bdd 100644 --- a/InstagramAPI/Classes/IResult.cs +++ b/InstaSharper/Classes/IResult.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes +namespace InstaSharper.Classes { public interface IResult { diff --git a/InstaSharper/Classes/Models/CheckPointType.cs b/InstaSharper/Classes/Models/CheckPointType.cs new file mode 100644 index 00000000..9df708ce --- /dev/null +++ b/InstaSharper/Classes/Models/CheckPointType.cs @@ -0,0 +1,7 @@ +namespace InstaSharper.Classes.Models +{ + public enum CheckPointType + { + CheckpointLoggedOut = 0 + } +} \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/Dimensions.cs b/InstaSharper/Classes/Models/Dimensions.cs similarity index 75% rename from InstagramAPI/Classes/Models/Dimensions.cs rename to InstaSharper/Classes/Models/Dimensions.cs index ef9d3838..cc1baffc 100644 --- a/InstagramAPI/Classes/Models/Dimensions.cs +++ b/InstaSharper/Classes/Models/Dimensions.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class Dimensions { diff --git a/InstagramAPI/Classes/Models/Image.cs b/InstaSharper/Classes/Models/Image.cs similarity index 88% rename from InstagramAPI/Classes/Models/Image.cs rename to InstaSharper/Classes/Models/Image.cs index 1c421f6a..4ff173fd 100644 --- a/InstagramAPI/Classes/Models/Image.cs +++ b/InstaSharper/Classes/Models/Image.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class Image { diff --git a/InstagramAPI/Classes/Models/Images.cs b/InstaSharper/Classes/Models/Images.cs similarity index 82% rename from InstagramAPI/Classes/Models/Images.cs rename to InstaSharper/Classes/Models/Images.cs index fb775956..9bb22e44 100644 --- a/InstagramAPI/Classes/Models/Images.cs +++ b/InstaSharper/Classes/Models/Images.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class Images { diff --git a/InstagramAPI/Classes/Models/InstaCaption.cs b/InstaSharper/Classes/Models/InstaCaption.cs similarity index 90% rename from InstagramAPI/Classes/Models/InstaCaption.cs rename to InstaSharper/Classes/Models/InstaCaption.cs index c3fd59bc..6975e8b5 100644 --- a/InstagramAPI/Classes/Models/InstaCaption.cs +++ b/InstaSharper/Classes/Models/InstaCaption.cs @@ -1,6 +1,6 @@ using System; -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaCaption { diff --git a/InstaSharper/Classes/Models/InstaFeed.cs b/InstaSharper/Classes/Models/InstaFeed.cs new file mode 100644 index 00000000..180ef4f9 --- /dev/null +++ b/InstaSharper/Classes/Models/InstaFeed.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace InstaSharper.Classes.Models +{ + public class InstaFeed + { + public int MediaItemsCount => Medias.Count; + public int StoriesItemsCount => Stories.Count; + + public List Medias { get; set; } = new List(); + public List Stories { get; set; } = new List(); + + public int Pages { get; set; } = 1; + } +} \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaFriendshipStatus.cs b/InstaSharper/Classes/Models/InstaFriendshipStatus.cs similarity index 85% rename from InstagramAPI/Classes/Models/InstaFriendshipStatus.cs rename to InstaSharper/Classes/Models/InstaFriendshipStatus.cs index f30dcd16..afad47fc 100644 --- a/InstagramAPI/Classes/Models/InstaFriendshipStatus.cs +++ b/InstaSharper/Classes/Models/InstaFriendshipStatus.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaFriendshipStatus { diff --git a/InstagramAPI/Classes/Models/InstaLocation.cs b/InstaSharper/Classes/Models/InstaLocation.cs similarity index 67% rename from InstagramAPI/Classes/Models/InstaLocation.cs rename to InstaSharper/Classes/Models/InstaLocation.cs index edf27f8e..df10dde8 100644 --- a/InstagramAPI/Classes/Models/InstaLocation.cs +++ b/InstaSharper/Classes/Models/InstaLocation.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaLocation { diff --git a/InstagramAPI/Classes/Models/InstaMedia.cs b/InstaSharper/Classes/Models/InstaMedia.cs similarity index 84% rename from InstagramAPI/Classes/Models/InstaMedia.cs rename to InstaSharper/Classes/Models/InstaMedia.cs index c168e7fc..5ec85af7 100644 --- a/InstagramAPI/Classes/Models/InstaMedia.cs +++ b/InstaSharper/Classes/Models/InstaMedia.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaMedia { @@ -46,5 +46,9 @@ public class InstaMedia public bool PhotoOfYou { get; set; } public bool HasLiked { get; set; } + + public List Tags { get; set; } = new List(); + + public InstaUserList Likers { get; set; } = new InstaUserList(); } } \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaMediaList.cs b/InstaSharper/Classes/Models/InstaMediaList.cs similarity index 55% rename from InstagramAPI/Classes/Models/InstaMediaList.cs rename to InstaSharper/Classes/Models/InstaMediaList.cs index e01b8dc0..fe026ac2 100644 --- a/InstagramAPI/Classes/Models/InstaMediaList.cs +++ b/InstaSharper/Classes/Models/InstaMediaList.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaMediaList : List { - public int Pages { get; set; } + public int Pages { get; set; } = -1; } } \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaMediaType.cs b/InstaSharper/Classes/Models/InstaMediaType.cs similarity index 66% rename from InstagramAPI/Classes/Models/InstaMediaType.cs rename to InstaSharper/Classes/Models/InstaMediaType.cs index 517fd1af..21437613 100644 --- a/InstagramAPI/Classes/Models/InstaMediaType.cs +++ b/InstaSharper/Classes/Models/InstaMediaType.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public enum InstaMediaType { diff --git a/InstaSharper/Classes/Models/InstaPosition.cs b/InstaSharper/Classes/Models/InstaPosition.cs new file mode 100644 index 00000000..cfaf062a --- /dev/null +++ b/InstaSharper/Classes/Models/InstaPosition.cs @@ -0,0 +1,14 @@ +namespace InstaSharper.Classes.Models +{ + public class InstaPosition + { + public InstaPosition(double x, double y) + { + X = x; + Y = y; + } + + public double X { get; set; } + public double Y { get; set; } + } +} \ No newline at end of file diff --git a/InstaSharper/Classes/Models/InstaStory.cs b/InstaSharper/Classes/Models/InstaStory.cs new file mode 100644 index 00000000..73476248 --- /dev/null +++ b/InstaSharper/Classes/Models/InstaStory.cs @@ -0,0 +1,25 @@ +using System; + +namespace InstaSharper.Classes.Models +{ + public class InstaStory + { + public bool CanReply { get; set; } + + public DateTime ExpiringAt { get; set; } + + public InstaUser User { get; set; } + + public string SourceToken { get; set; } + + public bool Seen { get; set; } + + public string LatestReelMedia { get; set; } + + public string Id { get; set; } + + public int RankedPosition { get; set; } + + public int SeenRankedPosition { get; set; } + } +} \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaUser.cs b/InstaSharper/Classes/Models/InstaUser.cs similarity index 83% rename from InstagramAPI/Classes/Models/InstaUser.cs rename to InstaSharper/Classes/Models/InstaUser.cs index 6506a129..7d41819b 100644 --- a/InstagramAPI/Classes/Models/InstaUser.cs +++ b/InstaSharper/Classes/Models/InstaUser.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaUser { @@ -25,6 +25,6 @@ public class InstaUser public string Pk { get; set; } public string MutualFollowersCount { get; set; } - public static InstaUser Empty => new InstaUser { FullName = string.Empty, UserName = string.Empty }; + public static InstaUser Empty => new InstaUser {FullName = string.Empty, UserName = string.Empty}; } } \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaUserList.cs b/InstaSharper/Classes/Models/InstaUserList.cs similarity index 72% rename from InstagramAPI/Classes/Models/InstaUserList.cs rename to InstaSharper/Classes/Models/InstaUserList.cs index 7c1a87ba..7709edd4 100644 --- a/InstagramAPI/Classes/Models/InstaUserList.cs +++ b/InstaSharper/Classes/Models/InstaUserList.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class InstaUserList : List { diff --git a/InstaSharper/Classes/Models/InstaUserTag.cs b/InstaSharper/Classes/Models/InstaUserTag.cs new file mode 100644 index 00000000..8f04d1cb --- /dev/null +++ b/InstaSharper/Classes/Models/InstaUserTag.cs @@ -0,0 +1,11 @@ +namespace InstaSharper.Classes.Models +{ + public class InstaUserTag + { + public InstaPosition Position { get; set; } + + public string TimeInVideo { get; set; } + + public InstaUser User { get; set; } + } +} \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/Likes.cs b/InstaSharper/Classes/Models/Likes.cs similarity index 77% rename from InstagramAPI/Classes/Models/Likes.cs rename to InstaSharper/Classes/Models/Likes.cs index 22db287c..6df7e061 100644 --- a/InstagramAPI/Classes/Models/Likes.cs +++ b/InstaSharper/Classes/Models/Likes.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Classes.Models +namespace InstaSharper.Classes.Models { public class Likes { diff --git a/InstagramAPI/Classes/Result.cs b/InstaSharper/Classes/Result.cs similarity index 90% rename from InstagramAPI/Classes/Result.cs rename to InstaSharper/Classes/Result.cs index 4943137e..a6bf6109 100644 --- a/InstagramAPI/Classes/Result.cs +++ b/InstaSharper/Classes/Result.cs @@ -1,12 +1,15 @@ using System; -namespace InstagramAPI.Classes +namespace InstaSharper.Classes { public static class Result { public static IResult Success(T resValue) => new Result(true, string.Empty, resValue); + public static IResult Success(string successMsg, T resValue) + => new Result(true, successMsg, resValue); + public static IResult Fail() => new Result(false, string.Empty, default(object)); diff --git a/InstagramAPI/Classes/UserCredentials.cs b/InstaSharper/Classes/UserSessionData.cs similarity index 58% rename from InstagramAPI/Classes/UserCredentials.cs rename to InstaSharper/Classes/UserSessionData.cs index cf6e894b..1a059db2 100644 --- a/InstagramAPI/Classes/UserCredentials.cs +++ b/InstaSharper/Classes/UserSessionData.cs @@ -1,8 +1,8 @@ -using InstagramAPI.Classes.Models; +using InstaSharper.Classes.Models; -namespace InstagramAPI.Classes +namespace InstaSharper.Classes { - public class UserCredentials + public class UserSessionData { public string UserName { get; set; } public string Password { get; set; } @@ -10,5 +10,6 @@ public class UserCredentials public InstaUser LoggedInUder { get; set; } public string RankToken { get; set; } + public string CsrfToken { get; set; } } } \ No newline at end of file diff --git a/InstagramAPI/Converters/ConvertersFabric.cs b/InstaSharper/Converters/ConvertersFabric.cs similarity index 50% rename from InstagramAPI/Converters/ConvertersFabric.cs rename to InstaSharper/Converters/ConvertersFabric.cs index e87939be..db360755 100644 --- a/InstagramAPI/Converters/ConvertersFabric.cs +++ b/InstaSharper/Converters/ConvertersFabric.cs @@ -1,40 +1,50 @@ -using InstagramAPI.Classes.Models; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { internal class ConvertersFabric { internal static IObjectConverter GetUserConverter(InstaUserResponse instaresponse) { - return new InstaUsersConverter { SourceObject = instaresponse }; + return new InstaUsersConverter {SourceObject = instaresponse}; } public static IObjectConverter GetSingleMediaConverter( InstaMediaItemResponse responseMedia) { - return new InstaMediaConverter { SourceObject = responseMedia }; + return new InstaMediaConverter {SourceObject = responseMedia}; } internal static IObjectConverter GetFeedConverter( InstaFeedResponse feedResponse) { - return new InstaFeedConverter { SourceObject = feedResponse }; + return new InstaFeedConverter {SourceObject = feedResponse}; } public static IObjectConverter GetMediaListConverter(InstaMediaListResponse mediaResponse) { - return new InstaMediaListConverter { SourceObject = mediaResponse }; + return new InstaMediaListConverter {SourceObject = mediaResponse}; } public static IObjectConverter GetCaptionConverter(InstaCaptionResponse captionResponse) { - return new InstaCaptionConverter { SourceObject = captionResponse }; + return new InstaCaptionConverter {SourceObject = captionResponse}; } public static IObjectConverter GetFriendShipStatusConverter(InstaFriendshipStatusResponse friendshipStatusResponse) { - return new InstaFriendshipStatusConverter { SourceObject = friendshipStatusResponse }; + return new InstaFriendshipStatusConverter {SourceObject = friendshipStatusResponse}; + } + + public static IObjectConverter GetSingleStoryConverter(InstaStoryResponse storyResponse) + { + return new InstaStoryConverter {SourceObject = storyResponse}; + } + + public static IObjectConverter GetUserTagConverter(InstaUserTagResponse tag) + { + return new InstaUserTagConverter {SourceObject = tag}; } } } \ No newline at end of file diff --git a/InstagramAPI/Converters/IObjectConverter.cs b/InstaSharper/Converters/IObjectConverter.cs similarity index 76% rename from InstagramAPI/Converters/IObjectConverter.cs rename to InstaSharper/Converters/IObjectConverter.cs index 1f2d8834..ba4da960 100644 --- a/InstagramAPI/Converters/IObjectConverter.cs +++ b/InstaSharper/Converters/IObjectConverter.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { internal interface IObjectConverter { diff --git a/InstagramAPI/Converters/InstaCaptionConverter.cs b/InstaSharper/Converters/InstaCaptionConverter.cs similarity index 75% rename from InstagramAPI/Converters/InstaCaptionConverter.cs rename to InstaSharper/Converters/InstaCaptionConverter.cs index 9d80a4d0..1d205963 100644 --- a/InstagramAPI/Converters/InstaCaptionConverter.cs +++ b/InstaSharper/Converters/InstaCaptionConverter.cs @@ -1,10 +1,10 @@ -using InstagramAPI.Classes.Models; -using InstagramAPI.Helpers; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.Helpers; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { - public class InstaCaptionConverter : IObjectConverter + internal class InstaCaptionConverter : IObjectConverter { public InstaCaptionResponse SourceObject { get; set; } diff --git a/InstagramAPI/Converters/InstaFeedConverter.cs b/InstaSharper/Converters/InstaFeedConverter.cs similarity index 69% rename from InstagramAPI/Converters/InstaFeedConverter.cs rename to InstaSharper/Converters/InstaFeedConverter.cs index 222718ae..910064ca 100644 --- a/InstagramAPI/Converters/InstaFeedConverter.cs +++ b/InstaSharper/Converters/InstaFeedConverter.cs @@ -1,7 +1,7 @@ -using InstagramAPI.Classes.Models; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { internal class InstaFeedConverter : IObjectConverter { @@ -13,9 +13,9 @@ public InstaFeed Convert() foreach (var instaUserFeedItemResponse in SourceObject.Items) { - if (instaUserFeedItemResponse.Type != 0) continue; + if (instaUserFeedItemResponse?.Type != 0) continue; var feedItem = ConvertersFabric.GetSingleMediaConverter(instaUserFeedItemResponse).Convert(); - feed.Items.Add(feedItem); + feed.Medias.Add(feedItem); } return feed; } diff --git a/InstagramAPI/Converters/InstaFriendshipStatusConverter.cs b/InstaSharper/Converters/InstaFriendshipStatusConverter.cs similarity index 71% rename from InstagramAPI/Converters/InstaFriendshipStatusConverter.cs rename to InstaSharper/Converters/InstaFriendshipStatusConverter.cs index 4176ea4d..5fa8b250 100644 --- a/InstagramAPI/Converters/InstaFriendshipStatusConverter.cs +++ b/InstaSharper/Converters/InstaFriendshipStatusConverter.cs @@ -1,9 +1,9 @@ -using InstagramAPI.Classes.Models; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { - public class InstaFriendshipStatusConverter : IObjectConverter + internal class InstaFriendshipStatusConverter : IObjectConverter { public InstaFriendshipStatusResponse SourceObject { get; set; } diff --git a/InstagramAPI/Converters/InstaMediaConverter.cs b/InstaSharper/Converters/InstaMediaConverter.cs similarity index 75% rename from InstagramAPI/Converters/InstaMediaConverter.cs rename to InstaSharper/Converters/InstaMediaConverter.cs index c3dca404..f8f5aa3b 100644 --- a/InstagramAPI/Converters/InstaMediaConverter.cs +++ b/InstaSharper/Converters/InstaMediaConverter.cs @@ -1,11 +1,11 @@ using System; -using InstagramAPI.Classes.Models; -using InstagramAPI.Helpers; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.Helpers; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { - public class InstaMediaConverter : IObjectConverter + internal class InstaMediaConverter : IObjectConverter { public InstaMediaItemResponse SourceObject { get; set; } @@ -33,6 +33,8 @@ public InstaMedia Convert() if (SourceObject.User != null) media.User = ConvertersFabric.GetUserConverter(SourceObject.User).Convert(); if (SourceObject.Caption != null) media.Caption = ConvertersFabric.GetCaptionConverter(SourceObject.Caption).Convert(); if (SourceObject.NextMaxId != null) media.NextMaxId = SourceObject.NextMaxId; + if (SourceObject.Likers?.Count > 0) foreach (var liker in SourceObject.Likers) media.Likers.Add(ConvertersFabric.GetUserConverter(liker).Convert()); + if (SourceObject.UserTagList?.In?.Count > 0) foreach (var tag in SourceObject.UserTagList.In) media.Tags.Add(ConvertersFabric.GetUserTagConverter(tag).Convert()); if (SourceObject.Images?.Candidates == null) return media; foreach (var image in SourceObject.Images.Candidates) media.Images.Add(new Image(image.Url, image.Width, image.Height)); return media; diff --git a/InstagramAPI/Converters/InstaMediaListConverter.cs b/InstaSharper/Converters/InstaMediaListConverter.cs similarity index 64% rename from InstagramAPI/Converters/InstaMediaListConverter.cs rename to InstaSharper/Converters/InstaMediaListConverter.cs index a563bc7e..5dcadfa4 100644 --- a/InstagramAPI/Converters/InstaMediaListConverter.cs +++ b/InstaSharper/Converters/InstaMediaListConverter.cs @@ -1,9 +1,9 @@ using System; using System.Linq; -using InstagramAPI.Classes.Models; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { internal class InstaMediaListConverter : IObjectConverter { @@ -13,7 +13,7 @@ public InstaMediaList Convert() { if (SourceObject == null) throw new ArgumentNullException($"Source object"); var mediaList = new InstaMediaList(); - mediaList.AddRange(SourceObject.Items.Select(ConvertersFabric.GetSingleMediaConverter).Select(converter => converter.Convert())); + mediaList.AddRange(SourceObject.Medias.Select(ConvertersFabric.GetSingleMediaConverter).Select(converter => converter.Convert())); return mediaList; } } diff --git a/InstaSharper/Converters/InstaStoryConverter.cs b/InstaSharper/Converters/InstaStoryConverter.cs new file mode 100644 index 00000000..1c498677 --- /dev/null +++ b/InstaSharper/Converters/InstaStoryConverter.cs @@ -0,0 +1,30 @@ +using System; +using InstaSharper.Classes.Models; +using InstaSharper.Helpers; +using InstaSharper.ResponseWrappers; + +namespace InstaSharper.Converters +{ + internal class InstaStoryConverter : IObjectConverter + { + public InstaStoryResponse SourceObject { get; set; } + + public InstaStory Convert() + { + if (SourceObject == null) throw new ArgumentNullException($"Source object"); + var story = new InstaStory + { + CanReply = SourceObject.CanReply, + ExpiringAt = DateTimeHelper.UnixTimestampToDateTime(SourceObject.ExpiringAt), + Id = SourceObject.Id, + LatestReelMedia = SourceObject.LatestReelMedia, + RankedPosition = SourceObject.RankedPosition, + Seen = SourceObject.Seen, + SeenRankedPosition = SourceObject.SeenRankedPosition, + SourceToken = SourceObject.SourceToken + }; + if (SourceObject.User != null) story.User = ConvertersFabric.GetUserConverter(SourceObject.User).Convert(); + return story; + } + } +} \ No newline at end of file diff --git a/InstaSharper/Converters/InstaUserTagConverter.cs b/InstaSharper/Converters/InstaUserTagConverter.cs new file mode 100644 index 00000000..8b01604e --- /dev/null +++ b/InstaSharper/Converters/InstaUserTagConverter.cs @@ -0,0 +1,21 @@ +using System; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; + +namespace InstaSharper.Converters +{ + internal class InstaUserTagConverter : IObjectConverter + { + public InstaUserTagResponse SourceObject { get; set; } + + public InstaUserTag Convert() + { + if (SourceObject == null) throw new ArgumentNullException($"Source object"); + var userTag = new InstaUserTag(); + if (SourceObject.Position?.Length == 2) userTag.Position = new InstaPosition(SourceObject.Position[0], SourceObject.Position[1]); + userTag.TimeInVideo = SourceObject.TimeInVideo; + if (SourceObject.User != null) userTag.User = ConvertersFabric.GetUserConverter(SourceObject.User).Convert(); + return userTag; + } + } +} \ No newline at end of file diff --git a/InstagramAPI/Converters/InstaUsersConverter.cs b/InstaSharper/Converters/InstaUsersConverter.cs similarity index 93% rename from InstagramAPI/Converters/InstaUsersConverter.cs rename to InstaSharper/Converters/InstaUsersConverter.cs index 74e4c505..80e42e28 100644 --- a/InstagramAPI/Converters/InstaUsersConverter.cs +++ b/InstaSharper/Converters/InstaUsersConverter.cs @@ -1,8 +1,8 @@ using System; -using InstagramAPI.Classes.Models; -using InstagramAPI.ResponseWrappers; +using InstaSharper.Classes.Models; +using InstaSharper.ResponseWrappers; -namespace InstagramAPI.Converters +namespace InstaSharper.Converters { internal class InstaUsersConverter : IObjectConverter { diff --git a/InstaSharper/Converters/Json/InstaFeedResponseDataConverter.cs b/InstaSharper/Converters/Json/InstaFeedResponseDataConverter.cs new file mode 100644 index 00000000..39cfa3d4 --- /dev/null +++ b/InstaSharper/Converters/Json/InstaFeedResponseDataConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using InstaSharper.ResponseWrappers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace InstaSharper.Converters.Json +{ + internal class InstaFeedResponseDataConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(InstaFeedResponse); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var token = JToken.Load(reader); + var feed = token.ToObject(); + var items = token["feed_items"]; + if (items != null) + { + foreach (var item in items) + { + var mediaOrAd = item["media_or_ad"]; + if (mediaOrAd == null) continue; + var media = mediaOrAd.ToObject(); + feed.Items.Add(media); + } + } + else + { + items = token["items"]; + feed.Items = items.ToObject>(); + } + + return feed; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + } +} \ No newline at end of file diff --git a/InstaSharper/Converters/Json/InstaMediaListDataConverter.cs b/InstaSharper/Converters/Json/InstaMediaListDataConverter.cs new file mode 100644 index 00000000..8c76c840 --- /dev/null +++ b/InstaSharper/Converters/Json/InstaMediaListDataConverter.cs @@ -0,0 +1,45 @@ +using System; +using InstaSharper.ResponseWrappers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace InstaSharper.Converters.Json +{ + public class InstaMediaListDataConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(InstaMediaListResponse); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var root = JToken.Load(reader); + var feed = root.ToObject(); + feed.Medias.Clear(); + feed.Stories.Clear(); + var items = root.SelectToken("items"); + var storiesTray = root.SelectToken("items[0].stories.tray"); + foreach (var item in items) + { + var media = item["media"]?.ToObject(); + if (media == null) media = item.ToObject(); + feed.Medias.Add(media); + } + if (storiesTray == null) return feed; + foreach (var storyItem in storiesTray) + { + var story = storyItem.ToObject(); + if (story == null) continue; + feed.Stories.Add(story); + } + + return feed; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + } +} \ No newline at end of file diff --git a/InstagramAPI/Helpers/CryptoHelper.cs b/InstaSharper/Helpers/CryptoHelper.cs similarity index 97% rename from InstagramAPI/Helpers/CryptoHelper.cs rename to InstaSharper/Helpers/CryptoHelper.cs index 6f6d4e8d..b43de7d4 100644 --- a/InstagramAPI/Helpers/CryptoHelper.cs +++ b/InstaSharper/Helpers/CryptoHelper.cs @@ -3,9 +3,9 @@ using System.Security.Cryptography; using System.Text; -namespace InstagramAPI.Helpers +namespace InstaSharper.Helpers { - public class CryptoHelper + internal class CryptoHelper { public static string CalculateMD5(string message) { diff --git a/InstagramAPI/Helpers/DateTimeHelper.cs b/InstaSharper/Helpers/DateTimeHelper.cs similarity index 84% rename from InstagramAPI/Helpers/DateTimeHelper.cs rename to InstaSharper/Helpers/DateTimeHelper.cs index 9fe8318a..a7f9affc 100644 --- a/InstagramAPI/Helpers/DateTimeHelper.cs +++ b/InstaSharper/Helpers/DateTimeHelper.cs @@ -1,8 +1,8 @@ using System; -namespace InstagramAPI.Helpers +namespace InstaSharper.Helpers { - public static class DateTimeHelper + internal static class DateTimeHelper { public static DateTime UnixTimestampToDateTime(double unixTime) { @@ -12,8 +12,7 @@ public static DateTime UnixTimestampToDateTime(double unixTime) dateTime = dateTime.AddSeconds(unixTime).ToUniversalTime(); return dateTime; } - catch (Exception) - { + catch (Exception) { return DateTime.MinValue; } } diff --git a/InstagramAPI/Helpers/HttpHelper.cs b/InstaSharper/Helpers/HttpHelper.cs similarity index 50% rename from InstagramAPI/Helpers/HttpHelper.cs rename to InstaSharper/Helpers/HttpHelper.cs index 9bea98ec..f2a84c0e 100644 --- a/InstagramAPI/Helpers/HttpHelper.cs +++ b/InstaSharper/Helpers/HttpHelper.cs @@ -1,21 +1,21 @@ using System; +using System.Collections.Generic; using System.Net.Http; -using InstagramAPI.API; +using InstaSharper.API; +using InstaSharper.Classes.Android.DeviceInfo; -namespace InstagramAPI.Helpers +namespace InstaSharper.Helpers { - public class HttpHelper + internal class HttpHelper { - public static HttpRequestMessage GetDefaultRequest(HttpMethod method, Uri uri) + public static HttpRequestMessage GetDefaultRequest(HttpMethod method, Uri uri, AndroidDevice deviceInfo) { var request = new HttpRequestMessage(method, uri); - request.Headers.Add(InstaApiConstants.HEADER_ACCEPT_LANGUAGE, - InstaApiConstants.ACCEPT_LANGUAGE); - request.Headers.Add(InstaApiConstants.HEADER_IG_CAPABILITIES, - InstaApiConstants.IG_CAPABILITIES); - request.Headers.Add(InstaApiConstants.HEADER_IG_CONNECTION_TYPE, - InstaApiConstants.IG_CONNECTION_TYPE); + request.Headers.Add(InstaApiConstants.HEADER_ACCEPT_LANGUAGE, InstaApiConstants.ACCEPT_LANGUAGE); + request.Headers.Add(InstaApiConstants.HEADER_IG_CAPABILITIES, InstaApiConstants.IG_CAPABILITIES); + request.Headers.Add(InstaApiConstants.HEADER_IG_CONNECTION_TYPE, InstaApiConstants.IG_CONNECTION_TYPE); request.Headers.Add(InstaApiConstants.HEADER_USER_AGENT, InstaApiConstants.USER_AGENT); + request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_XGOOGLE_AD_IDE, deviceInfo.GoogleAdId.ToString())); return request; } } diff --git a/InstagramAPI/Helpers/UriCreator.cs b/InstaSharper/Helpers/UriCreator.cs similarity index 50% rename from InstagramAPI/Helpers/UriCreator.cs rename to InstaSharper/Helpers/UriCreator.cs index 0b7b0f2d..07abdf98 100644 --- a/InstagramAPI/Helpers/UriCreator.cs +++ b/InstaSharper/Helpers/UriCreator.cs @@ -1,9 +1,9 @@ using System; -using InstagramAPI.API; +using InstaSharper.API; -namespace InstagramAPI.Helpers +namespace InstaSharper.Helpers { - public class UriCreator + internal class UriCreator { private static readonly Uri BaseInstagramUri = new Uri(InstaApiConstants.INSTAGRAM_URL); @@ -35,7 +35,7 @@ public static Uri GetUserMediaListUri(string userPk) return instaUri; } - public static Uri GetLogintUri() + public static Uri GetLoginUri() { Uri instaUri; if (!Uri.TryCreate(BaseInstagramUri, InstaApiConstants.ACCOUNTS_LOGIN, out instaUri)) throw new Exception("Cant create URI for user login"); @@ -57,5 +57,52 @@ public static Uri GetMediaListWithMaxIdUri(string userPk, string nextId) var uriBuilder = new UriBuilder(instaUri) {Query = $"max_id={nextId}"}; return uriBuilder.Uri; } + + public static Uri GetCurrentUserUri() + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, InstaApiConstants.CURRENTUSER, out instaUri)) throw new Exception("Cant create URI for current user info"); + return instaUri; + } + + internal static Uri GetUserFollowersUri(string userPk, string rankToken, string maxId = "") + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, string.Format(InstaApiConstants.GET_USER_FOLLOWERS, userPk, rankToken), out instaUri)) throw new Exception("Cant create URI for user followers"); + if (string.IsNullOrEmpty(maxId)) return instaUri; + var uriBuilder = new UriBuilder(instaUri) {Query = $"max_id={maxId}"}; + return uriBuilder.Uri; + } + + public static Uri GetTagFeedUri(string tag) + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, string.Format(InstaApiConstants.GET_TAG_FEED, tag), out instaUri)) throw new Exception("Cant create URI for discover tag feed"); + return instaUri; + } + + public static Uri GetLogoutUri() + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, InstaApiConstants.ACCOUNTS_LOGOUT, out instaUri)) throw new Exception("Cant create URI for user logout"); + return instaUri; + } + + public static Uri GetExploreUri() + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, InstaApiConstants.EXPLORE, out instaUri)) throw new Exception("Cant create URI for explore posts"); + return instaUri; + } + + public static Uri GetUserTagsUri(string userPk, string rankToken, string maxId = null) + { + Uri instaUri; + if (!Uri.TryCreate(BaseInstagramUri, string.Format(InstaApiConstants.GET_USER_TAGS, userPk), out instaUri)) throw new Exception("Cant create URI for get user tags"); + string query = $"rank_token={rankToken}&ranked_content=true"; + if (!string.IsNullOrEmpty(maxId)) query += $"max_id={maxId}"; + var uriBuilder = new UriBuilder(instaUri) {Query = query}; + return uriBuilder.Uri; + } } } \ No newline at end of file diff --git a/InstagramAPI/InstagramAPI.xproj b/InstaSharper/InstaSharper.xproj similarity index 95% rename from InstagramAPI/InstagramAPI.xproj rename to InstaSharper/InstaSharper.xproj index 81bd4037..d167e9b1 100644 --- a/InstagramAPI/InstagramAPI.xproj +++ b/InstaSharper/InstaSharper.xproj @@ -8,7 +8,7 @@ 449a948d-cc65-4d1a-8159-6fa232f972d9 - InstagramAPI + InstaSharper .\obj .\bin\ v4.5.2 diff --git a/InstaSharper/Logger/DebugLogger.cs b/InstaSharper/Logger/DebugLogger.cs new file mode 100644 index 00000000..f7f782e4 --- /dev/null +++ b/InstaSharper/Logger/DebugLogger.cs @@ -0,0 +1,9 @@ +namespace InstaSharper.Logger +{ + internal class DebugLogger : ILogger + { + public void Write(string logMessage) + { + } + } +} \ No newline at end of file diff --git a/InstagramAPI/Logger/ILogger.cs b/InstaSharper/Logger/ILogger.cs similarity index 71% rename from InstagramAPI/Logger/ILogger.cs rename to InstaSharper/Logger/ILogger.cs index f65cd95a..3ca0f377 100644 --- a/InstagramAPI/Logger/ILogger.cs +++ b/InstaSharper/Logger/ILogger.cs @@ -1,4 +1,4 @@ -namespace InstagramAPI.Logger +namespace InstaSharper.Logger { public interface ILogger { diff --git a/InstagramAPI/Properties/AssemblyInfo.cs b/InstaSharper/Properties/AssemblyInfo.cs similarity index 94% rename from InstagramAPI/Properties/AssemblyInfo.cs rename to InstaSharper/Properties/AssemblyInfo.cs index e6114e8b..686b5a22 100644 --- a/InstagramAPI/Properties/AssemblyInfo.cs +++ b/InstaSharper/Properties/AssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("InstagramApi")] +[assembly: AssemblyProduct("InstaSharper")] [assembly: AssemblyTrademark("")] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/InstaSharper/ResponseWrappers/BadStatusResponse.cs b/InstaSharper/ResponseWrappers/BadStatusResponse.cs new file mode 100644 index 00000000..2645cc9c --- /dev/null +++ b/InstaSharper/ResponseWrappers/BadStatusResponse.cs @@ -0,0 +1,17 @@ +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class BadStatusResponse : BaseStatusResponse + { + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("error_type")] + public string ErrorType { get; set; } + + [JsonProperty("checkpoint_url")] + public string CheckPointUrl { get; set; } + } +} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs b/InstaSharper/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs similarity index 67% rename from InstagramAPI/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs rename to InstaSharper/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs index 02cbcbe4..42c73d3a 100644 --- a/InstagramAPI/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs +++ b/InstaSharper/ResponseWrappers/BaseResponse/BaseLoadableResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers.BaseResponse +namespace InstaSharper.ResponseWrappers.BaseResponse { - public class BaseLoadableResponse : BaseStatusResponse + internal class BaseLoadableResponse : BaseStatusResponse { [JsonProperty("more_available")] public bool MoreAvailable { get; set; } @@ -10,6 +10,9 @@ public class BaseLoadableResponse : BaseStatusResponse [JsonProperty("num_results")] public int ResultsCount { get; set; } + [JsonProperty("total_count")] + public int TotalCount { get; set; } + [JsonProperty("auto_load_more_enabled")] public bool AutoLoadMoreEnabled { get; set; } diff --git a/InstagramAPI/ResponseWrappers/BaseResponse/BaseStatusResponse.cs b/InstaSharper/ResponseWrappers/BaseResponse/BaseStatusResponse.cs similarity index 56% rename from InstagramAPI/ResponseWrappers/BaseResponse/BaseStatusResponse.cs rename to InstaSharper/ResponseWrappers/BaseResponse/BaseStatusResponse.cs index 58032787..5c667347 100644 --- a/InstagramAPI/ResponseWrappers/BaseResponse/BaseStatusResponse.cs +++ b/InstaSharper/ResponseWrappers/BaseResponse/BaseStatusResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers.BaseResponse +namespace InstaSharper.ResponseWrappers.BaseResponse { - public class BaseStatusResponse + internal class BaseStatusResponse { [JsonProperty("status")] public string Status { get; set; } diff --git a/InstagramAPI/ResponseWrappers/FollowedByResponse.cs b/InstaSharper/ResponseWrappers/FollowedByResponse.cs similarity index 59% rename from InstagramAPI/ResponseWrappers/FollowedByResponse.cs rename to InstaSharper/ResponseWrappers/FollowedByResponse.cs index cf88efca..66a38e40 100644 --- a/InstagramAPI/ResponseWrappers/FollowedByResponse.cs +++ b/InstaSharper/ResponseWrappers/FollowedByResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class FollowedByResponse + internal class FollowedByResponse { [JsonProperty("count")] public int Count { get; set; } diff --git a/InstagramAPI/ResponseWrappers/ImageResponse.cs b/InstaSharper/ResponseWrappers/ImageResponse.cs similarity index 78% rename from InstagramAPI/ResponseWrappers/ImageResponse.cs rename to InstaSharper/ResponseWrappers/ImageResponse.cs index f6a3ede1..efa259dd 100644 --- a/InstagramAPI/ResponseWrappers/ImageResponse.cs +++ b/InstaSharper/ResponseWrappers/ImageResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class ImageResponse + internal class ImageResponse { [JsonProperty("url")] public string Url { get; set; } diff --git a/InstagramAPI/ResponseWrappers/ImagesResponse.cs b/InstaSharper/ResponseWrappers/ImagesResponse.cs similarity index 82% rename from InstagramAPI/ResponseWrappers/ImagesResponse.cs rename to InstaSharper/ResponseWrappers/ImagesResponse.cs index c80a9752..4dfd6b7c 100644 --- a/InstagramAPI/ResponseWrappers/ImagesResponse.cs +++ b/InstaSharper/ResponseWrappers/ImagesResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class ImagesResponse + internal class ImagesResponse { [JsonProperty("low_resolution")] public ImageResponse LowResolution { get; set; } diff --git a/InstagramAPI/ResponseWrappers/InstaCaptionResponse.cs b/InstaSharper/ResponseWrappers/InstaCaptionResponse.cs similarity index 79% rename from InstagramAPI/ResponseWrappers/InstaCaptionResponse.cs rename to InstaSharper/ResponseWrappers/InstaCaptionResponse.cs index 535c3bba..bb01f4b4 100644 --- a/InstagramAPI/ResponseWrappers/InstaCaptionResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaCaptionResponse.cs @@ -1,9 +1,9 @@ -using InstagramAPI.ResponseWrappers.BaseResponse; +using InstaSharper.ResponseWrappers.BaseResponse; using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaCaptionResponse : BaseStatusResponse + internal class InstaCaptionResponse : BaseStatusResponse { [JsonProperty("user_id")] public long UserId { get; set; } diff --git a/InstaSharper/ResponseWrappers/InstaCurrentUserResponse.cs b/InstaSharper/ResponseWrappers/InstaCurrentUserResponse.cs new file mode 100644 index 00000000..d980e0f3 --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaCurrentUserResponse.cs @@ -0,0 +1,11 @@ +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaCurrentUserResponse : BaseStatusResponse + { + [JsonProperty("user")] + public InstaUserResponse User { get; set; } + } +} \ No newline at end of file diff --git a/InstaSharper/ResponseWrappers/InstaFeedResponse.cs b/InstaSharper/ResponseWrappers/InstaFeedResponse.cs new file mode 100644 index 00000000..6965f268 --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaFeedResponse.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaFeedResponse : BaseLoadableResponse + { + [JsonProperty("is_direct_v2_enabled")] + public bool IsDirectV2Enabled { get; set; } + + [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)] + public List Items { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/InstaSharper/ResponseWrappers/InstaFollowersResponse.cs b/InstaSharper/ResponseWrappers/InstaFollowersResponse.cs new file mode 100644 index 00000000..03cc43c0 --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaFollowersResponse.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaFollowersResponse : BaseStatusResponse + { + [JsonProperty("users")] + public List Items { get; set; } + + [JsonProperty("big_list")] + public bool IsBigList { get; set; } + + [JsonProperty("page_size")] + public int PageSize { get; set; } + + [JsonProperty("next_max_id")] + public string NextMaxId { get; set; } + + public bool IsOK() + { + return !string.IsNullOrEmpty(Status) && (Status.ToLower() == "ok"); + } + } +} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/InstaFriendshipStatusResponse.cs b/InstaSharper/ResponseWrappers/InstaFriendshipStatusResponse.cs similarity index 81% rename from InstagramAPI/ResponseWrappers/InstaFriendshipStatusResponse.cs rename to InstaSharper/ResponseWrappers/InstaFriendshipStatusResponse.cs index 9aff97cb..d719235f 100644 --- a/InstagramAPI/ResponseWrappers/InstaFriendshipStatusResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaFriendshipStatusResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaFriendshipStatusResponse + internal class InstaFriendshipStatusResponse { [JsonProperty("following")] public bool Foolowing { get; set; } diff --git a/InstagramAPI/ResponseWrappers/InstaImageCandidatesResponse.cs b/InstaSharper/ResponseWrappers/InstaImageCandidatesResponse.cs similarity index 66% rename from InstagramAPI/ResponseWrappers/InstaImageCandidatesResponse.cs rename to InstaSharper/ResponseWrappers/InstaImageCandidatesResponse.cs index c26a6b93..42f2257a 100644 --- a/InstagramAPI/ResponseWrappers/InstaImageCandidatesResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaImageCandidatesResponse.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaImageCandidatesResponse + internal class InstaImageCandidatesResponse { [JsonProperty("candidates")] public List Candidates { get; set; } diff --git a/InstagramAPI/ResponseWrappers/InstaLoginResponse.cs b/InstaSharper/ResponseWrappers/InstaLoginResponse.cs similarity index 72% rename from InstagramAPI/ResponseWrappers/InstaLoginResponse.cs rename to InstaSharper/ResponseWrappers/InstaLoginResponse.cs index 0cb47d32..be5aeab5 100644 --- a/InstagramAPI/ResponseWrappers/InstaLoginResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaLoginResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaLoginResponse + internal class InstaLoginResponse { [JsonProperty("status")] public string Status { get; set; } diff --git a/InstagramAPI/ResponseWrappers/InstaMediaItemResponse.cs b/InstaSharper/ResponseWrappers/InstaMediaItemResponse.cs similarity index 83% rename from InstagramAPI/ResponseWrappers/InstaMediaItemResponse.cs rename to InstaSharper/ResponseWrappers/InstaMediaItemResponse.cs index f08f2544..79cea2f4 100644 --- a/InstagramAPI/ResponseWrappers/InstaMediaItemResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaMediaItemResponse.cs @@ -1,9 +1,10 @@ -using InstagramAPI.Classes.Models; +using System.Collections.Generic; +using InstaSharper.Classes.Models; using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaMediaItemResponse + internal class InstaMediaItemResponse { [JsonProperty("taken_at")] public string TakenAtUnixLike { get; set; } @@ -65,5 +66,11 @@ public class InstaMediaItemResponse [JsonProperty("type")] public int Type { get; set; } + + [JsonProperty("usertags")] + public InstaUserTagListResponse UserTagList { get; set; } + + [JsonProperty("likers")] + public List Likers { get; set; } } } \ No newline at end of file diff --git a/InstaSharper/ResponseWrappers/InstaMediaListResponse.cs b/InstaSharper/ResponseWrappers/InstaMediaListResponse.cs new file mode 100644 index 00000000..4d116b8a --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaMediaListResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using InstaSharper.ResponseWrappers.BaseResponse; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaMediaListResponse : BaseLoadableResponse + { + [JsonProperty("items")] + public List Medias { get; set; } = new List(); + + public List Stories { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/InstaSearchUserResponse.cs b/InstaSharper/ResponseWrappers/InstaSearchUserResponse.cs similarity index 80% rename from InstagramAPI/ResponseWrappers/InstaSearchUserResponse.cs rename to InstaSharper/ResponseWrappers/InstaSearchUserResponse.cs index fcd57f79..c5535369 100644 --- a/InstagramAPI/ResponseWrappers/InstaSearchUserResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaSearchUserResponse.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaSearchUserResponse + internal class InstaSearchUserResponse { [JsonProperty("has_more")] public bool MoreAvailable { get; set; } diff --git a/InstaSharper/ResponseWrappers/InstaStoryResponse.cs b/InstaSharper/ResponseWrappers/InstaStoryResponse.cs new file mode 100644 index 00000000..c211f3c1 --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaStoryResponse.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaStoryResponse + { + [JsonProperty("can_reply")] + public bool CanReply { get; set; } + + [JsonProperty("expiring_at")] + public string ExpiringAt { get; set; } + + [JsonProperty("user")] + public InstaUserResponse User { get; set; } + + [JsonProperty("source_token")] + public string SourceToken { get; set; } + + [JsonProperty("seen")] + public bool Seen { get; set; } + + [JsonProperty("latest_reel_media")] + public string LatestReelMedia { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("ranked_position")] + public int RankedPosition { get; set; } + + [JsonProperty("seen_ranked_position")] + public int SeenRankedPosition { get; set; } + } +} \ No newline at end of file diff --git a/InstaSharper/ResponseWrappers/InstaTagFeedResponse.cs b/InstaSharper/ResponseWrappers/InstaTagFeedResponse.cs new file mode 100644 index 00000000..788af637 --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaTagFeedResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaTagFeedResponse + { + [JsonProperty("ranked_items")] + public List Items { get; set; } + } +} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/InstaUserResponse.cs b/InstaSharper/ResponseWrappers/InstaUserResponse.cs similarity index 94% rename from InstagramAPI/ResponseWrappers/InstaUserResponse.cs rename to InstaSharper/ResponseWrappers/InstaUserResponse.cs index ffd5122d..a676069a 100644 --- a/InstagramAPI/ResponseWrappers/InstaUserResponse.cs +++ b/InstaSharper/ResponseWrappers/InstaUserResponse.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; -namespace InstagramAPI.ResponseWrappers +namespace InstaSharper.ResponseWrappers { - public class InstaUserResponse + internal class InstaUserResponse { [JsonProperty("username")] public string UserName { get; set; } diff --git a/InstaSharper/ResponseWrappers/InstaUserTagListResponse.cs b/InstaSharper/ResponseWrappers/InstaUserTagListResponse.cs new file mode 100644 index 00000000..ff3eadcf --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaUserTagListResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaUserTagListResponse + { + [JsonProperty("in")] + public List In { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/InstaSharper/ResponseWrappers/InstaUserTagResponse.cs b/InstaSharper/ResponseWrappers/InstaUserTagResponse.cs new file mode 100644 index 00000000..54d3105e --- /dev/null +++ b/InstaSharper/ResponseWrappers/InstaUserTagResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace InstaSharper.ResponseWrappers +{ + internal class InstaUserTagResponse + { + [JsonProperty("position")] + public double[] Position { get; set; } + + [JsonProperty("time_in_video")] + public string TimeInVideo { get; set; } + + [JsonProperty("user")] + public InstaUserResponse User { get; set; } + } +} \ No newline at end of file diff --git a/InstagramAPI/project.json b/InstaSharper/project.json similarity index 79% rename from InstagramAPI/project.json rename to InstaSharper/project.json index 9408a848..e6e1665c 100644 --- a/InstagramAPI/project.json +++ b/InstaSharper/project.json @@ -1,5 +1,5 @@ { - "version": "1.2.0", + "version": "1.2.1", "dependencies": { "NETStandard.Library": "1.6.0", @@ -9,6 +9,9 @@ "frameworks": { "netstandard1.6": { "imports": "dnxcore50" + }, + "net452": { + } } } diff --git a/InstagramAPI.Tests/Tests/GetMediaTest.cs b/InstagramAPI.Tests/Tests/GetMediaTest.cs deleted file mode 100644 index 9f672f05..00000000 --- a/InstagramAPI.Tests/Tests/GetMediaTest.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using InstagramAPI.Classes; -using InstagramAPI.Tests.Utils; -using Xunit; -using Xunit.Abstractions; - -namespace InstagramAPI.Tests.Tests -{ - public class GetMediaTest - { - private readonly ITestOutputHelper output; - private readonly string password = Environment.GetEnvironmentVariable("instaapiuserpassword"); - private readonly string username = "alex_codegarage"; - - public GetMediaTest(ITestOutputHelper output) - { - this.output = output; - } - - [Theory] - [InlineData("1379932752706850783")] - public async void GetMediaByCodeTest(string mediaId) - { - //arrange - var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials - { - UserName = username, - Password = password - }); - //act - output.WriteLine($"Trying to login as user: {username}"); - if (!await TestHelpers.Login(apiInstance, output)) return; - output.WriteLine($"Getting media by ID: {mediaId}"); - var media = await apiInstance.GetMediaByCodeAsync(mediaId); - //assert - Assert.NotNull(media); - } - - [Theory] - [InlineData("alex_codegarage")] - [InlineData("instagram")] - [InlineData("therock")] - public async void GetUserMediaListTest(string userToFetch) - { - //arrange - var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials - { - UserName = username, - Password = password - }); - //act - output.WriteLine($"Trying to login as user: {username}"); - if (!await TestHelpers.Login(apiInstance, output)) return; - output.WriteLine($"Getting posts of user: {userToFetch}"); - var posts = await apiInstance.GetUserMediaAsync(userToFetch); - //assert - Assert.NotNull(posts); - } - } -} \ No newline at end of file diff --git a/InstagramAPI.Tests/Tests/InstaApiTest.cs b/InstagramAPI.Tests/Tests/InstaApiTest.cs deleted file mode 100644 index 1332b04a..00000000 --- a/InstagramAPI.Tests/Tests/InstaApiTest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using InstagramAPI.Classes; -using InstagramAPI.Tests.Utils; -using Xunit; -using Xunit.Abstractions; - -namespace InstagramAPI.Tests.Tests -{ - public class InstaApiTest - { - public InstaApiTest(ITestOutputHelper output) - { - this.output = output; - } - - private readonly ITestOutputHelper output; - private readonly string username = "alex_codegarage"; - private readonly string password = Environment.GetEnvironmentVariable("instaapiuserpassword"); - - [Fact] - public async void GetUserFeedTest() - { - //arrange - var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials - { - UserName = username, - Password = password - }); - //act - if (!await TestHelpers.Login(apiInstance, output)) return; - var getFeedResult = await apiInstance.GetUserFeedAsync(5); - var feed = getFeedResult.Value; - //assert - Assert.True(getFeedResult.Succeeded); - Assert.NotNull(feed); - } - - [Fact] - public async void GetUserTest() - { - //arrange - var apiInstance = - TestHelpers.GetDefaultInstaApiInstance(new UserCredentials - { - UserName = username, - Password = password - }); - //act - if (!await TestHelpers.Login(apiInstance, output)) return; - var getUserResult = await apiInstance.GetUserAsync(username); - var user = getUserResult.Value; - //assert - Assert.True(getUserResult.Succeeded); - Assert.NotNull(user); - Assert.Equal(user.UserName, username); - } - } -} \ No newline at end of file diff --git a/InstagramAPI/API/IInstaApi.cs b/InstagramAPI/API/IInstaApi.cs deleted file mode 100644 index fdba9a06..00000000 --- a/InstagramAPI/API/IInstaApi.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using InstagramAPI.Classes; -using InstagramAPI.Classes.Models; - -namespace InstagramAPI.API -{ - public interface IInstaApi - { - bool IsUserAuthenticated { get; } - IResult GetUser(string username); - Task> GetUserAsync(string username); - IResult GetUserMedia(string username, int maxPages = 0); - Task> GetUserMediaAsync(string username, int maxPages = 0); - IResult GetMediaByCode(string postCode); - Task> GetMediaByCodeAsync(string postCode); - IResult Login(); - Task> LoginAsync(); - IResult GetUserFeed(int maxPages = 0); - Task> GetUserFeedAsync(int maxPages = 0); - } -} \ No newline at end of file diff --git a/InstagramAPI/API/InstaApi.cs b/InstagramAPI/API/InstaApi.cs deleted file mode 100644 index 08e4c81b..00000000 --- a/InstagramAPI/API/InstaApi.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using InstagramAPI.Classes; -using InstagramAPI.Classes.Android.DeviceInfo; -using InstagramAPI.Classes.Models; -using InstagramAPI.Converters; -using InstagramAPI.Helpers; -using InstagramAPI.Logger; -using InstagramAPI.ResponseWrappers; -using Newtonsoft.Json; - -namespace InstagramAPI.API -{ - public class InstaApi : IInstaApi - { - private readonly AndroidDevice _deviceInfo; - private readonly HttpClient _httpClient; - private readonly HttpClientHandler _httpHandler; - private readonly ILogger _logger; - private readonly ApiRequestMessage _requestMessage; - private readonly UserCredentials _user; - - public InstaApi(UserCredentials user, - ILogger logger, - HttpClient httpClient, - HttpClientHandler httpHandler, - ApiRequestMessage requestMessage, - AndroidDevice deviceInfo) - { - _user = user; - _logger = logger; - _httpClient = httpClient; - _httpHandler = httpHandler; - _requestMessage = requestMessage; - _deviceInfo = deviceInfo; - } - - public bool IsUserAuthenticated { get; private set; } - - #region sync part - - public IResult GetMediaByCode(string postCode) - { - return GetMediaByCodeAsync(postCode).Result; - } - - public IResult GetUser(string username) - { - return GetUserAsync(username).Result; - } - - public IResult GetUserFeed(int maxPages = 0) - { - return GetUserFeedAsync(maxPages).Result; - } - - public IResult GetUserMedia(string username, int maxPages = 0) - { - return GetUserMediaAsync(username, maxPages).Result; - } - - public IResult Login() - { - return LoginAsync().Result; - } - - #endregion - - #region async part - - public async Task> GetMediaByCodeAsync(string postCode) - { - ValidateUser(); - var mediaUri = UriCreator.GetMediaUri(postCode); - var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, mediaUri); - var response = await _httpClient.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - if (response.StatusCode == HttpStatusCode.OK) - { - var mediaResponse = JsonConvert.DeserializeObject(json); - if (mediaResponse.Items?.Count != 1) - { - string errorMessage = $"Got wrong media count for request with media id={postCode}"; - _logger.Write(errorMessage); - return Result.Fail(errorMessage); - } - var converter = ConvertersFabric.GetSingleMediaConverter(mediaResponse.Items.FirstOrDefault()); - return Result.Success(converter.Convert()); - } - var badRequest = JsonConvert.DeserializeObject(json); - _logger.Write(badRequest.Message); - return Result.Fail(badRequest.Message, (InstaMedia)null); - } - - public async Task> GetUserAsync(string username) - { - ValidateUser(); - var userUri = UriCreator.GetUserUri(username); - var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userUri); - request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_TIMEZONE, InstaApiConstants.TIMEZONE_OFFSET.ToString())); - request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_COUNT, "1")); - request.Properties.Add(new KeyValuePair(InstaApiConstants.HEADER_RANK_TOKEN, _user.RankToken)); - var response = await _httpClient.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - if (response.StatusCode == HttpStatusCode.OK) - { - var userInfo = JsonConvert.DeserializeObject(json); - var user = userInfo.Users?.FirstOrDefault(u => u.UserName == username); - if (user == null) - { - string errorMessage = $"Can't find this user: {username}"; - _logger.Write(errorMessage); - return Result.Fail(errorMessage); - } - var converter = ConvertersFabric.GetUserConverter(user); - return Result.Success(converter.Convert()); - } - var badRequest = JsonConvert.DeserializeObject(json); - _logger.Write(badRequest.Message); - return Result.Fail(badRequest.Message, (InstaUser)null); - - } - - public async Task> GetUserFeedAsync(int maxPages = 0) - { - ValidateUser(); - if (!IsUserAuthenticated) throw new ArgumentException("user must be authenticated"); - var userFeedUri = UriCreator.GetUserFeedUri(); - var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, userFeedUri); - request.Headers.Add(InstaApiConstants.HEADER_XGOOGLE_AD_IDE, _deviceInfo.GoogleAdId.ToString()); - var response = await _httpClient.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - var feed = new InstaFeed(); - if (response.StatusCode == HttpStatusCode.OK) - { - var feedResponse = JsonConvert.DeserializeObject(json); - var converter = ConvertersFabric.GetFeedConverter(feedResponse); - var feedConverted = converter.Convert(); - feed.Items.AddRange(feedConverted.Items); - while (feedResponse.MoreAvailable && (feed.Pages < maxPages)) - { - feedResponse = _getFeedResponseWithMaxId(feedResponse.NextMaxId); - converter = ConvertersFabric.GetFeedConverter(feedResponse); - feedConverted = converter.Convert(); - feed.Items.AddRange(feedConverted.Items); - feed.Pages++; - } - return Result.Success(feed); - } - return Result.Fail("", (InstaFeed)null); - } - - public async Task> GetUserMediaAsync(string username, int maxPages = 0) - { - ValidateUser(); - if (maxPages == 0) maxPages = int.MaxValue; - var user = GetUser(username).Value; - var instaUri = UriCreator.GetUserMediaListUri(user.Pk); - var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri); - var response = await _httpClient.SendAsync(request); - var json = await response.Content.ReadAsStringAsync(); - if (response.StatusCode == HttpStatusCode.OK) - { - var mediaResponse = JsonConvert.DeserializeObject(json); - var converter = ConvertersFabric.GetMediaListConverter(mediaResponse); - var mediaList = converter.Convert(); - while (mediaResponse.MoreAvailable && (mediaList.Pages < maxPages)) - { - mediaResponse = _getMediaListResponseWithMaxId(user.Pk, mediaResponse.NextMaxId); - converter = ConvertersFabric.GetMediaListConverter(mediaResponse); - mediaList.AddRange(converter.Convert()); - mediaList.Pages++; - } - return Result.Success(mediaList); - } - var badRequest = JsonConvert.DeserializeObject(json); - _logger.Write(badRequest.Message); - return Result.Fail(badRequest.Message, (InstaMediaList)null); - } - - public async Task> LoginAsync() - { - ValidateUser(); - ValidateRequestMessage(); - _httpClient.DefaultRequestHeaders.Add(InstaApiConstants.HEADER_USER_AGENT, InstaApiConstants.USER_AGENT); - var instaUri = UriCreator.GetLogintUri(); - var signature = $"{_requestMessage.GenerateSignature()}.{_requestMessage.GetMessageString()}"; - var fields = new Dictionary - { - {InstaApiConstants.HEADER_IG_SIGNATURE, signature}, - { - InstaApiConstants.HEADER_IG_SIGNATURE_KEY_VERSION, - InstaApiConstants.IG_SIGNATURE_KEY_VERSION - } - }; - var request = HttpHelper.GetDefaultRequest(HttpMethod.Post, instaUri); - request.Content = new FormUrlEncodedContent(fields); - request.Properties.Add(InstaApiConstants.HEADER_IG_SIGNATURE, signature); - request.Properties.Add(InstaApiConstants.HEADER_IG_SIGNATURE_KEY_VERSION, InstaApiConstants.IG_SIGNATURE_KEY_VERSION); - var response = await _httpClient.SendAsync(request); - - if (response.StatusCode == HttpStatusCode.OK) - { - var loginInfo = - JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - IsUserAuthenticated = (loginInfo.User != null) && (loginInfo.User.UserName == _user.UserName); - var converter = ConvertersFabric.GetUserConverter(loginInfo.User); - _user.LoggedInUder = converter.Convert(); - _user.RankToken = $"{_user.LoggedInUder.Pk}_{Guid.NewGuid()}"; - return Result.Success(true); - } - else - { - var loginInfo = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - _logger.Write(loginInfo.Message); - return Result.Fail(loginInfo.Message, false); - } - } - - #endregion - - #region private part - - private void ValidateUser() - { - if (string.IsNullOrEmpty(_user.UserName) || string.IsNullOrEmpty(_user.Password)) throw new ArgumentException("user name and password must be specified"); - } - - private void ValidateRequestMessage() - { - if ((_requestMessage == null) || _requestMessage.IsEmpty()) throw new ArgumentException("API request message null or empty"); - } - - private InstaFeedResponse _getFeedResponseWithMaxId(string nextId) - { - Uri instaUri; - if (!Uri.TryCreate(new Uri(InstaApiConstants.INSTAGRAM_URL), InstaApiConstants.TIMELINEFEED, out instaUri)) throw new Exception("Cant create search user URI"); - var userUriBuilder = new UriBuilder(instaUri) { Query = $"max_id={nextId}" }; - var request = new HttpRequestMessage(HttpMethod.Get, userUriBuilder.Uri); - request.Headers.Clear(); - request.Headers.Add(InstaApiConstants.HEADER_PHONE_ID, _requestMessage.phone_id); - request.Headers.Add(InstaApiConstants.HEADER_TIMEZONE, InstaApiConstants.TIMEZONE_OFFSET.ToString()); - request.Headers.Add(InstaApiConstants.HEADER_XGOOGLE_AD_IDE, _deviceInfo.GoogleAdId.ToString()); - var response = _httpClient.SendAsync(request); - var json = response.Result.Content.ReadAsStringAsync().Result; - if (response.Result.StatusCode == HttpStatusCode.OK) return JsonConvert.DeserializeObject(json); - return null; - } - - private InstaMediaListResponse _getMediaListResponseWithMaxId(string userPk, string nextId) - { - var instaUri = UriCreator.GetMediaListWithMaxIdUri(userPk, nextId); - var request = HttpHelper.GetDefaultRequest(HttpMethod.Get, instaUri); - request.Headers.Add(InstaApiConstants.HEADER_XGOOGLE_AD_IDE, _deviceInfo.GoogleAdId.ToString()); - var response = _httpClient.SendAsync(request); - var json = response.Result.Content.ReadAsStringAsync().Result; - if (response.Result.StatusCode == HttpStatusCode.OK) return JsonConvert.DeserializeObject(json); - return null; - } - - #endregion - } -} \ No newline at end of file diff --git a/InstagramAPI/Classes/Models/InstaFeed.cs b/InstagramAPI/Classes/Models/InstaFeed.cs deleted file mode 100644 index 21fb18e1..00000000 --- a/InstagramAPI/Classes/Models/InstaFeed.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace InstagramAPI.Classes.Models -{ - public class InstaFeed - { - public int FeedItemsCount => Items.Count; - public List Items { get; set; } = new List(); - public int Pages { get; set; } = 1; - } -} \ No newline at end of file diff --git a/InstagramAPI/Logger/DebugLogger.cs b/InstagramAPI/Logger/DebugLogger.cs deleted file mode 100644 index 2171a008..00000000 --- a/InstagramAPI/Logger/DebugLogger.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace InstagramAPI.Logger -{ - public class DebugLogger : ILogger - { - public void Write(string logMessage) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/BadStatusResponse.cs b/InstagramAPI/ResponseWrappers/BadStatusResponse.cs deleted file mode 100644 index d449e039..00000000 --- a/InstagramAPI/ResponseWrappers/BadStatusResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using InstagramAPI.ResponseWrappers.BaseResponse; -using Newtonsoft.Json; - -namespace InstagramAPI.ResponseWrappers -{ - public class BadStatusResponse : BaseStatusResponse - { - [JsonProperty("message")] - public string Message { get; set; } - - [JsonProperty("error_type")] - public string ErrorType { get; set; } - } -} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/InstaFeedResponse.cs b/InstagramAPI/ResponseWrappers/InstaFeedResponse.cs deleted file mode 100644 index b8379ca5..00000000 --- a/InstagramAPI/ResponseWrappers/InstaFeedResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using InstagramAPI.ResponseWrappers.BaseResponse; -using Newtonsoft.Json; - -namespace InstagramAPI.ResponseWrappers -{ - public class InstaFeedResponse : BaseLoadableResponse - { - [JsonProperty("is_direct_v2_enabled")] - public bool IsDirectV2Enabled { get; set; } - - [JsonProperty("items")] - public List Items { get; set; } - } -} \ No newline at end of file diff --git a/InstagramAPI/ResponseWrappers/InstaMediaListResponse.cs b/InstagramAPI/ResponseWrappers/InstaMediaListResponse.cs deleted file mode 100644 index bb3abf2a..00000000 --- a/InstagramAPI/ResponseWrappers/InstaMediaListResponse.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using InstagramAPI.ResponseWrappers.BaseResponse; -using Newtonsoft.Json; - -namespace InstagramAPI.ResponseWrappers -{ - public class InstaMediaListResponse : BaseLoadableResponse - { - [JsonProperty("items")] - public List Items { get; set; } - } -} \ No newline at end of file diff --git a/README.md b/README.md index 3c5b9d69..8aff9a41 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# InstagramApi +# InstagramApi [InstaSharper] Tokenless, butthurtless private API for Instagram. Get account information, media, explore tags and user feed without any applications and other crap. -This wrapper provides basic media from instagram, some of them even without authorization. + Note that: there is a simple [Instagram API](https://github.com/a-legotin/InstagramAPI-Web) based on web-version of Instagram. This repository based on Instagram API for mobile devices. -[![Build status](https://ci.appveyor.com/api/projects/status/tgdu2w1xr2qmtmrh?svg=true)](https://ci.appveyor.com/project/a-legotin/instagramapi-xk3ds) -[![Build Status](https://travis-ci.org/a-legotin/InstagramAPI.svg?branch=master)](https://travis-ci.org/a-legotin/InstagramAPI) +[![Build status](https://ci.appveyor.com/api/projects/status/6os0fhi1awbplbka?svg=true)](https://ci.appveyor.com/project/a-legotin/instasharper) -#### Current version: 1.0.0 [Under development] +#### Current version: 1.2.0 [Stable], 1.2.1 [Under development] -#### [Why two separate repos with same mission?](https://github.com/a-legotin/InstagramAPI-Web/wiki/Difference-between-API-Web-and-just-API-repositories) +## Overview +This project intends to provide all the features available in the Instagram API up to v9.7.0. It is being developed in C# for .NET Framework 4.6 and .NET Standart 1.6 -#### [Wiki](https://github.com/a-legotin/InstagramAPI/wiki/) +* Please note that this project is still in design and development phase; the libraries may suffer major changes even at the interface level, so don't rely (yet) in this software for production uses. * ## Cross-platform by design Build with dotnet core. Can be used on Mac, Linux, Windows. @@ -18,6 +18,21 @@ Build with dotnet core. Can be used on Mac, Linux, Windows. ## Easy to install Use library as dll, reference from nuget or clone source code. +##Features + +Currently the library supports following coverage of the following Instagram APIs: + * Login + * Logout + * Get user timeline feed + * Get current logged in user media + * Get user media by username + * Get media by its id + * Get tag feed + * Get user by its user name + * Get current user + +######for more details please check [Project roadmap](https://github.com/a-legotin/InstaSharper/wiki/Project-roadmap/_edit) + ## Easy to use #### Use builder to get Insta API instance: ```c# @@ -28,6 +43,8 @@ var api = new InstaApiBuilder() .Build(); ``` ##### Note: every API method has Async implementation as well + +### Quick Examples #### Login ```c# bool loggedIn = api.Login(); @@ -53,16 +70,21 @@ InstaMedia mediaItem = api.GetMediaByCode(mediaCode); InstaFeed feed = api.GetUserFeed(); ``` +#### [Why two separate repos with same mission?](https://github.com/a-legotin/InstagramAPI-Web/wiki/Difference-between-API-Web-and-just-API-repositories) + +#### [Wiki](https://github.com/a-legotin/InstagramAPI/wiki/) + # License MIT # Terms and conditions +- Anyone who uses this wrapper MUST follow [Instagram Policy](https://www.instagram.com/about/legal/terms/api/) - Provided project MUST NOT be used for marketing purposes - I will not provide support to anyone who wants this API to send massive messages/likes/follows and so on - Use this API at your own risk ## Legal -This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API. +This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API wrapper. diff --git a/appveyor.yml b/appveyor.yml index 362be5b5..5502ff8a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.2.{build} +version: 1.2.1 ({build}) os: Visual Studio 2015 platform: Any CPU configuration: Release @@ -7,6 +7,8 @@ branches: only: - release - master +skip_commits: + message: /skip ci/ # Regex for matching commit message skip_tags: true @@ -18,29 +20,22 @@ build: build_script: - ps: dotnet --info - ps: dotnet restore - - ps: cd InstagramAPI + - ps: cd InstaSharper - ps: dotnet --verbose build - ps: dotnet --verbose pack -o ../nugetpack -c release - - ps: cd ../InstagramAPI.Tests + - ps: cd ../InstaSharper.Tests - ps: dotnet restore - ps: dotnet --verbose build - - ps: dotnet --verbose test + - ps: dotnet --verbose test -parallel none - ps: cd .. -assembly_info: - patch: true - file: project.json - assembly_version: "{version}" - assembly_file_version: "{version}" - assembly_informational_version: "{version}" - environment: instaapiuserpassword: - secure: 2DmbP+MHwes0M3ahyTWDkA== - + secure: 7eAl+O5i5BxXWexd26z+wg== + artifacts: - - path: InstagramAPI\bin\$(configuration)\netstandard1.6\*.dll + - path: InstaSharper\bin\$(configuration)\netstandard1.6\*.dll name: dlls - path: '**\*.nupkg'