Removed initial Instagram client used to subscribe and unsubscribe from Instagram and instead use the popular Instasharp client from 'http://instasharp.org/'.
To just receive WebHooks, there are no dependencies on Instasharp, but it is great tool for subscribing or unsubscribing from Instagram WebHooks as well as for receiving media posted etc.
This commit is contained in:
Родитель
5d77386192
Коммит
7ce1ac9634
|
@ -1,16 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Entity;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using InstagramReceiver.Models;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.EntityFramework;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using InstagramReceiver.Models;
|
||||
|
||||
namespace InstagramReceiver
|
||||
{
|
||||
|
@ -40,9 +36,10 @@ namespace InstagramReceiver
|
|||
{
|
||||
}
|
||||
|
||||
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
|
||||
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
|
||||
{
|
||||
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
|
||||
|
||||
// Configure validation logic for usernames
|
||||
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
|
||||
{
|
||||
|
@ -81,7 +78,7 @@ namespace InstagramReceiver
|
|||
var dataProtectionProvider = options.DataProtectionProvider;
|
||||
if (dataProtectionProvider != null)
|
||||
{
|
||||
manager.UserTokenProvider =
|
||||
manager.UserTokenProvider =
|
||||
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
|
||||
}
|
||||
return manager;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using InstagramReceiver.Models;
|
||||
using InstaSharp;
|
||||
using InstaSharp.Models;
|
||||
using InstaSharp.Models.Responses;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
|
@ -47,19 +50,37 @@ namespace InstagramReceiver
|
|||
// This is similar to the RememberMe option when you log in.
|
||||
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
|
||||
|
||||
// Wire up Instagram authentication
|
||||
// Get the config used by Instasharp
|
||||
InstagramConfig config = Dependencies.InstagramConfig;
|
||||
|
||||
// Wire up Instagram authentication so that we can access the media published by a user.
|
||||
var options = new InstagramAuthenticationOptions()
|
||||
{
|
||||
ClientId = "3775be36ab65453bbe182cc4385fe3e2",
|
||||
ClientSecret = "b5d2811bf8fe4cd6908168da65a2701c",
|
||||
ClientId = config.ClientId,
|
||||
ClientSecret = config.ClientSecret,
|
||||
Provider = new InstagramAuthenticationProvider
|
||||
{
|
||||
OnAuthenticated = context =>
|
||||
{
|
||||
// Retrieve the OAuth access token to store for subsequent API calls
|
||||
OAuthResponse response = new OAuthResponse
|
||||
{
|
||||
User = new UserInfo
|
||||
{
|
||||
Id = long.Parse(context.Id),
|
||||
FullName = context.Name,
|
||||
ProfilePicture = context.ProfilePicture,
|
||||
Username = context.UserName,
|
||||
},
|
||||
AccessToken = context.AccessToken,
|
||||
};
|
||||
|
||||
string userId = context.Id;
|
||||
string accessToken = context.AccessToken;
|
||||
Dependencies.Tokens[userId] = accessToken;
|
||||
|
||||
// Store the token in memory so that we can use it for accessing media. In a real scenario
|
||||
// this would be stored somewhere else.
|
||||
Dependencies.Tokens[userId] = response;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using InstagramReceiver.Models;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using InstagramReceiver.Models;
|
||||
|
||||
namespace InstagramReceiver.Controllers
|
||||
{
|
||||
|
@ -22,7 +19,7 @@ namespace InstagramReceiver.Controllers
|
|||
{
|
||||
}
|
||||
|
||||
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
|
||||
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
|
||||
{
|
||||
UserManager = userManager;
|
||||
SignInManager = signInManager;
|
||||
|
@ -34,9 +31,9 @@ namespace InstagramReceiver.Controllers
|
|||
{
|
||||
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
|
||||
}
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
private set
|
||||
{
|
||||
_signInManager = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +117,7 @@ namespace InstagramReceiver.Controllers
|
|||
// If a user enters incorrect codes for a specified amount of time then the user account
|
||||
// will be locked out for a specified amount of time.
|
||||
// You can configure the account lockout settings in IdentityConfig
|
||||
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
|
||||
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
|
||||
switch (result)
|
||||
{
|
||||
case SignInStatus.Success:
|
||||
|
@ -155,14 +152,13 @@ namespace InstagramReceiver.Controllers
|
|||
var result = await UserManager.CreateAsync(user, model.Password);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
|
||||
|
||||
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
|
||||
|
||||
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
|
||||
// Send an email with this link
|
||||
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
|
||||
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
|
||||
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
|
||||
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
AddErrors(result);
|
||||
|
@ -212,7 +208,7 @@ namespace InstagramReceiver.Controllers
|
|||
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
|
||||
// Send an email with this link
|
||||
// string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
|
||||
// var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
|
||||
// var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
|
||||
// await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
|
||||
// return RedirectToAction("ForgotPasswordConfirmation", "Account");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.WebHooks;
|
||||
using InstaSharp.Endpoints;
|
||||
|
||||
namespace InstagramReceiver.Controllers
|
||||
{
|
||||
|
@ -10,33 +10,23 @@ namespace InstagramReceiver.Controllers
|
|||
[Route("subscribe")]
|
||||
public async Task<IHttpActionResult> PostSubscribe()
|
||||
{
|
||||
// Get our WebHook Client
|
||||
InstagramWebHookClient client = Dependencies.Client;
|
||||
// Create Instasharp subscription endpoint
|
||||
var subscriptions = new Subscription(Dependencies.InstagramConfig);
|
||||
|
||||
// Subscribe for updates from Instagram
|
||||
var sub = await client.SubscribeAsync(string.Empty, Url);
|
||||
|
||||
return Ok(sub);
|
||||
var response = await subscriptions.CreateUser();
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[Route("unsubscribe")]
|
||||
public async Task PostUnsubscribeAll()
|
||||
public async Task<IHttpActionResult> PostUnsubscribeAll()
|
||||
{
|
||||
// Get our WebHook Client
|
||||
InstagramWebHookClient client = Dependencies.Client;
|
||||
// Create Instasharp subscription endpoint
|
||||
var subscriptions = new Subscription(Dependencies.InstagramConfig);
|
||||
|
||||
// Unsubscribe from all subscriptions for the client configuration with id="".
|
||||
await client.UnsubscribeAsync(string.Empty);
|
||||
}
|
||||
|
||||
[Route("unsubscribe/{subId}")]
|
||||
public async Task PostUnsubscribe(string subId)
|
||||
{
|
||||
// Get our WebHook Client
|
||||
InstagramWebHookClient client = Dependencies.Client;
|
||||
|
||||
// Unsubscribe from the given subscription using client configuration with id="".
|
||||
await client.UnsubscribeAsync(string.Empty, subId);
|
||||
// Subscribe for updates from Instagram
|
||||
var response = await subscriptions.RemoveAllSubscriptions();
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,45 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Configuration;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.WebHooks;
|
||||
using InstaSharp;
|
||||
using InstaSharp.Models.Responses;
|
||||
|
||||
namespace InstagramReceiver
|
||||
{
|
||||
public static class Dependencies
|
||||
{
|
||||
private static InstagramWebHookClient _client;
|
||||
private static ConcurrentDictionary<string, string> _tokens;
|
||||
private static InstagramConfig _config;
|
||||
private static ConcurrentDictionary<string, OAuthResponse> _tokens;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="InstagramWebHookClient"/> used to subscribe to Instagram for WebHook notifications.
|
||||
/// Gets the <see cref="InstagramConfig"/> used by Instasharp, see <c>http://instasharp.org/</c>.
|
||||
/// </summary>
|
||||
public static InstagramWebHookClient Client
|
||||
public static InstagramConfig InstagramConfig
|
||||
{
|
||||
get { return _client; }
|
||||
get { return _config; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets cached Instagram access tokens from logged in users.
|
||||
/// </summary>
|
||||
public static IDictionary<string, string> Tokens
|
||||
public static IDictionary<string, OAuthResponse> Tokens
|
||||
{
|
||||
get { return _tokens; }
|
||||
}
|
||||
|
||||
public static void Initialize(HttpConfiguration config)
|
||||
{
|
||||
_client = new InstagramWebHookClient(config);
|
||||
_tokens = new ConcurrentDictionary<string, string>();
|
||||
var clientId = WebConfigurationManager.AppSettings["MS_WebHookReceiverSecret_InstagramId"];
|
||||
var clientSecret = WebConfigurationManager.AppSettings["MS_WebHookReceiverSecret_Instagram"];
|
||||
var webHookHost = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME") ?? "localhost";
|
||||
|
||||
// Note: you can use the 'id' field of the callbackURI to manage multiple subscriptions with each their callback.
|
||||
var callbackUri = string.Format("https://{0}/api/webhooks/incoming/instagram", webHookHost);
|
||||
_config = new InstagramConfig(clientId, clientSecret, redirectUri: null, callbackUri: callbackUri);
|
||||
|
||||
_tokens = new ConcurrentDictionary<string, OAuthResponse>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,6 +44,10 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="bouncy_castle_hmac_sha_pcl, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\BouncyCastle-PCL.1.0.0.6\lib\bouncy_castle_hmac_sha_pcl.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -52,11 +56,31 @@
|
|||
<HintPath>..\..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="InstaSharp, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\InstaSharp.2.0.3\lib\portable-net403+sl5+wp8+win8\InstaSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Owin.Security.Providers, Version=1.25.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Owin.Security.Providers.1.27\lib\net45\Owin.Security.Providers.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -64,10 +88,19 @@
|
|||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Entity" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
|
@ -136,9 +169,6 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core">
|
||||
<HintPath>..\..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -211,6 +241,7 @@
|
|||
<Content Include="README.TXT" />
|
||||
<Content Include="Scripts\bootstrap.js" />
|
||||
<Content Include="Scripts\bootstrap.min.js" />
|
||||
<None Include="Properties\PublishProfiles\hookreceivers - Web Deploy.pubxml" />
|
||||
<None Include="Scripts\jquery-1.10.2.intellisense.js" />
|
||||
<Content Include="Scripts\jquery-1.10.2.js" />
|
||||
<Content Include="Scripts\jquery-1.10.2.min.js" />
|
||||
|
@ -318,6 +349,11 @@
|
|||
<Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
|
||||
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
|
||||
<Error Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
|
|
@ -13,21 +13,29 @@ to differentiate between multiple WebHooks, for example 'secret0, id1=secret1, i
|
|||
Also, set the MS_WebHookReceiverSecret_InstagramId application setting to the application id, again optionally using IDs
|
||||
to differentiate between multiple WebHooks, for example '<c>secret0, id1=secret1, id2=secret2'.
|
||||
|
||||
We use Instasharp to subscribe, unsubscribe, as well as for downloading media from Instagram, see http://instasharp.org/
|
||||
for details.
|
||||
|
||||
To subscribe to instagram, send an empty POST request to
|
||||
|
||||
http://<host>/api/instagram/subscribe
|
||||
|
||||
To unsubscribe, send an empty POST request to
|
||||
|
||||
http://<host>/api/instagram/unsubscribe?id=<id>
|
||||
http://<host>/api/instagram/unsubscribe
|
||||
|
||||
Please see https://www.instagram.com/developer/subscriptions for more information about Instagram WebHooks for more information.
|
||||
You don't have to be logged in using your Instagram credentials to subscribe or unsubscribe. The purpose of logging in
|
||||
using Instagram credentials is so that the server can download media from your Instagram when you post data and a
|
||||
WebHook notification is received.
|
||||
|
||||
See http://www.oauthforaspnet.com/providers/instagram/ for information about how to register an application with Instagram and
|
||||
how to authenticate it. To test locally and in a deployed Web site, set the redirect URI for the Instagram app to both
|
||||
See http://www.oauthforaspnet.com/providers/instagram/ for information about how to register an application with Instagram
|
||||
and how to authenticate it.
|
||||
|
||||
To test locally and in a deployed Web site, set the redirect URI for the Instagram app to these two entries:
|
||||
|
||||
http://localhost:50006/signin-instagram
|
||||
https://<host>/signin-instagram
|
||||
|
||||
When deploying into Azure, also change the DefaultConnection connection string in Web.Config to a valid SQL Azure connection string
|
||||
When deploying into Azure, also change the DefaultConnection connection string to a valid SQL Azure connection string.
|
||||
|
||||
Please see https://www.instagram.com/developer/subscriptions for more information about Instagram WebHooks.
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<StyleCopSettings Version="105">
|
||||
<Analyzers>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.ReadabilityRules">
|
||||
<Rules>
|
||||
<Rule Name="CommentsMustContainText">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ParameterMustFollowComma">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="SplitParametersMustStartOnLineAfterDeclaration">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="DoNotUseRegions">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="UseStringEmptyForEmptyStrings">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.LayoutRules">
|
||||
<Rules>
|
||||
<Rule Name="SingleLineCommentMustBePrecededByBlankLine">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.MaintainabilityRules">
|
||||
<Rules>
|
||||
<Rule Name="FileMayOnlyContainASingleClass">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
<Analyzer AnalyzerId="StyleCop.CSharp.OrderingRules">
|
||||
<Rules>
|
||||
<Rule Name="ElementsMustAppearInTheCorrectOrder">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="ConstantsMustAppearBeforeFields">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="UsingDirectivesMustBeOrderedAlphabeticallyByNamespace">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
<Rule Name="StaticElementsMustAppearBeforeInstanceElements">
|
||||
<RuleSettings>
|
||||
<BooleanProperty Name="Enabled">False</BooleanProperty>
|
||||
</RuleSettings>
|
||||
</Rule>
|
||||
</Rules>
|
||||
<AnalyzerSettings />
|
||||
</Analyzer>
|
||||
</Analyzers>
|
||||
</StyleCopSettings>
|
|
@ -57,7 +57,7 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
|
||||
|
@ -87,6 +87,10 @@
|
|||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-5.2.2.0" newVersion="5.2.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<entityFramework>
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using InstaSharp;
|
||||
using InstaSharp.Endpoints;
|
||||
using InstaSharp.Models.Responses;
|
||||
using Microsoft.AspNet.WebHooks;
|
||||
|
||||
namespace InstagramReceiver.WebHooks
|
||||
{
|
||||
public class InstagramWebHookHandler : WebHookHandler
|
||||
{
|
||||
private const string MediaEndpointTemplate = "https://api.instagram.com/v1/media/{0}?access_token={1}";
|
||||
|
||||
private static readonly HttpClient _client = new HttpClient();
|
||||
|
||||
public InstagramWebHookHandler()
|
||||
{
|
||||
this.Receiver = InstagramWebHookReceiver.ReceiverName;
|
||||
|
@ -17,26 +15,21 @@ namespace InstagramReceiver.WebHooks
|
|||
|
||||
public override async Task ExecuteAsync(string generator, WebHookHandlerContext context)
|
||||
{
|
||||
// Get the WebHook client
|
||||
InstagramWebHookClient client = Dependencies.Client;
|
||||
|
||||
// Convert the incoming data to a collection of InstagramNotifications
|
||||
var notifications = context.GetDataOrDefault<InstagramNotificationCollection>();
|
||||
|
||||
// Get the config used by Instasharp client
|
||||
InstagramConfig config = Dependencies.InstagramConfig;
|
||||
|
||||
// Access media references in notifications
|
||||
foreach (var notification in notifications)
|
||||
{
|
||||
string token;
|
||||
if (Dependencies.Tokens.TryGetValue(notification.UserId, out token))
|
||||
// If we have an access token then get the media using Instasharp.
|
||||
OAuthResponse auth;
|
||||
if (Dependencies.Tokens.TryGetValue(notification.UserId, out auth))
|
||||
{
|
||||
string mediaEndpoint = string.Format(MediaEndpointTemplate, notification.Data.MediaId, token);
|
||||
using (var response = await _client.GetAsync(mediaEndpoint))
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
InstagramPost post = await response.Content.ReadAsAsync<InstagramPost>();
|
||||
}
|
||||
}
|
||||
var media = new Media(config, auth);
|
||||
MediaResponse mediaResponse = await media.Get(notification.Data.MediaId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<packages>
|
||||
<package id="Antlr" version="3.4.1.9004" targetFramework="net452" />
|
||||
<package id="bootstrap" version="3.0.0" targetFramework="net452" />
|
||||
<package id="BouncyCastle-PCL" version="1.0.0.6" targetFramework="net452" />
|
||||
<package id="EntityFramework" version="6.1.1" targetFramework="net452" />
|
||||
<package id="InstaSharp" version="2.0.3" targetFramework="net452" />
|
||||
<package id="jQuery" version="1.10.2" targetFramework="net452" />
|
||||
<package id="jQuery.Validation" version="1.11.1" targetFramework="net452" />
|
||||
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" />
|
||||
|
@ -16,9 +18,13 @@
|
|||
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.2" targetFramework="net452" />
|
||||
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.2" targetFramework="net452" />
|
||||
<package id="Microsoft.AspNet.WebPages" version="3.2.2" targetFramework="net452" />
|
||||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
|
||||
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net452" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net452" />
|
||||
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.0" targetFramework="net452" />
|
||||
<package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.3" targetFramework="net452" />
|
||||
<package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
|
||||
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net452" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net452" />
|
||||
|
@ -30,7 +36,7 @@
|
|||
<package id="Microsoft.Owin.Security.Twitter" version="3.0.1" targetFramework="net452" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" />
|
||||
<package id="Modernizr" version="2.6.2" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
|
||||
<package id="Owin" version="1.0" targetFramework="net452" />
|
||||
<package id="Owin.Security.Providers" version="1.27" targetFramework="net452" />
|
||||
<package id="Respond" version="1.2.0" targetFramework="net452" />
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Routing;
|
||||
using Microsoft.AspNet.WebHooks;
|
||||
using Microsoft.AspNet.WebHooks.Routes;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides various extensions for the <see cref="InstagramWebHookClient"/> class.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static class InstagramWebHookClientExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribes to posts submitted by all users authenticated with this Instagram client.
|
||||
/// </summary>
|
||||
/// <param name="client">The current <see cref="InstagramWebHookClient"/> instance.</param>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook. This makes it possible to
|
||||
/// support multiple WebHooks with individual configurations.</param>
|
||||
/// <param name="urlHelper">A <see cref="UrlHelper"/> for computing the callback URI.</param>
|
||||
/// <returns>A <see cref="InstagramSubscription"/> instance.</returns>
|
||||
public static Task<InstagramSubscription> SubscribeAsync(this InstagramWebHookClient client, string id, UrlHelper urlHelper)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException("client");
|
||||
}
|
||||
if (urlHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException("urlHelper");
|
||||
}
|
||||
|
||||
Uri callback = GetCallback(id, urlHelper);
|
||||
return client.SubscribeAsync(id, callback);
|
||||
}
|
||||
|
||||
internal static Uri GetCallback(string id, UrlHelper urlHelper)
|
||||
{
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object> { { "webHookReceiver", InstagramWebHookReceiver.ReceiverName }, { "id", id } };
|
||||
string callbackLink = urlHelper.Link(WebHookReceiverRouteNames.ReceiversAction, parameters);
|
||||
Uri callback = new Uri(callbackLink);
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,28 +58,15 @@
|
|||
<Link>Properties\CommonAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Extensions\HttpConfigurationExtensions.cs" />
|
||||
<Compile Include="Extensions\InstagramWebHookClientExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\InstagramReceiverResources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>InstagramReceiverResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Serialization\InstagramUnixTimeConverter.cs" />
|
||||
<Compile Include="WebHooks\InstagramCaption.cs" />
|
||||
<Compile Include="WebHooks\InstagramLocation.cs" />
|
||||
<Compile Include="WebHooks\InstagramImages.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotificationData.cs" />
|
||||
<Compile Include="WebHooks\InstagramPost.cs" />
|
||||
<Compile Include="WebHooks\InstagramPostMeta.cs" />
|
||||
<Compile Include="WebHooks\InstagramVideos.cs" />
|
||||
<Compile Include="WebHooks\InstagramPostData.cs" />
|
||||
<Compile Include="WebHooks\InstagramMedia.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotificationCollection.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotification.cs" />
|
||||
<Compile Include="WebHooks\InstagramSubscription.cs" />
|
||||
<Compile Include="WebHooks\InstagramUser.cs" />
|
||||
<Compile Include="WebHooks\InstagramWebHookClient.cs" />
|
||||
<Compile Include="WebHooks\InstagramWebHookReceiver.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the Instagram string representation of a Unix time stamp to and from a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
internal class InstagramUnixTimeConverter : UnixTimeConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public InstagramUnixTimeConverter() : base(true)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.WebHooks.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about a media caption from Instagram.
|
||||
/// </summary>
|
||||
public class InstagramCaption
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of this caption.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation time for this caption.
|
||||
/// </summary>
|
||||
[JsonProperty("created_time")]
|
||||
[JsonConverter(typeof(InstagramUnixTimeConverter))]
|
||||
public DateTime CreatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text of this caption.
|
||||
/// </summary>
|
||||
[JsonProperty("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="InstagramUser"/> that this caption is from.
|
||||
/// </summary>
|
||||
[JsonProperty("from")]
|
||||
public InstagramUser From { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about images contained in an Instagram post.
|
||||
/// </summary>
|
||||
public class InstagramImages
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a thumbnail of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("thumbnail")]
|
||||
public InstagramMedia Thumbnail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a low resolution version of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("low_resolution")]
|
||||
public InstagramMedia LowResolution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a standard resolution version of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("standard_resolution")]
|
||||
public InstagramMedia StandardResolution { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the location where the media uploaded to Instagram was recorded.
|
||||
/// </summary>
|
||||
public class InstagramLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the location.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the location.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the latitude of the location.
|
||||
/// </summary>
|
||||
[JsonProperty("latitude")]
|
||||
public double Latitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the latitude of the location.
|
||||
/// </summary>
|
||||
[JsonProperty("longitude")]
|
||||
public double Longitude { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the size and address of the media uploaded to Instagram.
|
||||
/// </summary>
|
||||
public class InstagramMedia
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the URI of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("url")]
|
||||
public Uri Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the media in pixels.
|
||||
/// </summary>
|
||||
[JsonProperty("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the media in pixels.
|
||||
/// </summary>
|
||||
[JsonProperty("height")]
|
||||
public int Height { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the top-level entry for media posted to Instagram
|
||||
/// </summary>
|
||||
public class InstagramPost
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the meta data portion of an Instagram post
|
||||
/// </summary>
|
||||
[JsonProperty("meta")]
|
||||
public InstagramPostMeta Meta { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data portion of an Instagram post.
|
||||
/// </summary>
|
||||
[JsonProperty("data")]
|
||||
public InstagramPostData Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.AspNet.WebHooks.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the data entry for media posted to Instagram
|
||||
/// </summary>
|
||||
public class InstagramPostData
|
||||
{
|
||||
private Collection<string> _tags = new Collection<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a unique ID for this post.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a direct link to the media in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("link")]
|
||||
public Uri Link { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data and time when this post was created.
|
||||
/// </summary>
|
||||
[JsonProperty("created_time")]
|
||||
[JsonConverter(typeof(InstagramUnixTimeConverter))]
|
||||
public DateTime CreatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about the type of media included in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tags included in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("tags")]
|
||||
public Collection<string> Tags
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location of the poster.
|
||||
/// </summary>
|
||||
[JsonProperty("location")]
|
||||
public InstagramLocation Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about any images included in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("images")]
|
||||
public InstagramImages Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about any videos included in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("videos")]
|
||||
public InstagramVideos Videos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about the caption included in this post.
|
||||
/// </summary>
|
||||
[JsonProperty("caption")]
|
||||
public InstagramCaption Caption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets information about the user posting the media.
|
||||
/// </summary>
|
||||
[JsonProperty("user")]
|
||||
public InstagramUser User { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the meta entry for media posted to Instagram
|
||||
/// </summary>
|
||||
public class InstagramPostMeta
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the HTTP status code for this Instagram post.
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes an Instagram WebHook subscription. For details about Instagram WebHooks, please
|
||||
/// see <c>https://www.instagram.com/developer/subscriptions/</c>.
|
||||
/// </summary>
|
||||
public class InstagramSubscription
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique ID of this subscription.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the object type for this subscription. Currently, the only type provided
|
||||
/// by Instagram is 'user'.
|
||||
/// </summary>
|
||||
[JsonProperty("object")]
|
||||
public string Object { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the aspect of the object for this subscription. Currently only 'media' is supported,
|
||||
/// but other types of subscriptions may be added in the future.
|
||||
/// </summary>
|
||||
[JsonProperty("aspect")]
|
||||
public string Aspect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback URI where event notifications are sent.
|
||||
/// </summary>
|
||||
[JsonProperty("callback_url")]
|
||||
public Uri Callback { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about an Instagram user.
|
||||
/// </summary>
|
||||
public class InstagramUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the ID for this user.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user name.
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the full name of this user.
|
||||
/// </summary>
|
||||
[JsonProperty("full_name")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile picture for this user.
|
||||
/// </summary>
|
||||
[JsonProperty("profile_picture")]
|
||||
public Uri ProfilePicture { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about videos contained in an Instagram post.
|
||||
/// </summary>
|
||||
public class InstagramVideos
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a low bandwidth version of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("low_bandwidth")]
|
||||
public InstagramMedia LowBandwidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a low resolution version of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("low_resolution")]
|
||||
public InstagramMedia LowResolution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a standard resolution version of the media.
|
||||
/// </summary>
|
||||
[JsonProperty("standard_resolution")]
|
||||
public InstagramMedia StandardResolution { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,285 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.WebHooks.Properties;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="InstagramWebHookClient"/> provides support for managing Instagram WebHook subscriptions programmatically.
|
||||
/// For more information about Instagram WebHooks, please see <c>https://www.instagram.com/developer/subscriptions/</c>.
|
||||
/// </summary>
|
||||
public class InstagramWebHookClient : IDisposable
|
||||
{
|
||||
internal const string DataKey = "data";
|
||||
internal const string InstagramApi = "https://api.instagram.com/v1/";
|
||||
internal const string SubscriptionAddress = InstagramApi + "subscriptions";
|
||||
internal const string SubscriptionAddressTemplate = SubscriptionAddress + "?client_id={0}&client_secret={1}{2}";
|
||||
|
||||
private static readonly string ClientIdKey = InstagramWebHookReceiver.ReceiverName + "Id";
|
||||
|
||||
private readonly HttpConfiguration _config;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly Uri _subscriptionAddress;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InstagramWebHookClient"/> which can be used to create and delete WebHooks
|
||||
/// with Instagram. For more information about Instagram WebHooks, please see <c>https://www.instagram.com/developer/subscriptions/</c>.
|
||||
/// Set the application settings '<c>MS_WebHookReceiverSecret_Instagram</c>' and '<c>MS_WebHookReceiverSecret_InstagramId</c>' to
|
||||
/// the Instagram client ID and secret respectively, optionally using the 'id' syntax to accommodate multiple configurations
|
||||
/// using the same model as for receivers.
|
||||
/// </summary>
|
||||
/// <param name="config">The current <see cref="HttpConfiguration"/>.</param>
|
||||
public InstagramWebHookClient(HttpConfiguration config)
|
||||
: this(config, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InstagramWebHookClient"/> with the given <paramref name="httpClient"/>.
|
||||
/// This constructor is intended for unit testing purposes.
|
||||
/// </summary>
|
||||
internal InstagramWebHookClient(HttpConfiguration config, HttpClient httpClient)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException("config");
|
||||
}
|
||||
|
||||
_config = config;
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
_subscriptionAddress = new Uri(SubscriptionAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current set of subscriptions for the given client.
|
||||
/// </summary>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook. This makes it possible to
|
||||
/// support multiple WebHooks with individual configurations.</param>
|
||||
public virtual async Task<Collection<InstagramSubscription>> GetAllSubscriptionsAsync(string id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
// Get client ID and secret for the given ID.
|
||||
Tuple<string, string> clientInfo = await GetClientConfig(id);
|
||||
|
||||
string address = string.Format(CultureInfo.InvariantCulture, SubscriptionAddressTemplate, clientInfo.Item1, clientInfo.Item2, null);
|
||||
using (HttpResponseMessage response = await _httpClient.GetAsync(address))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
string errorMessage = await GetErrorContent(response);
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Client_GetSubscriptionsFailure, response.StatusCode, errorMessage);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
|
||||
JObject subscriptionData = await response.Content.ReadAsAsync<JObject>();
|
||||
JArray subs = subscriptionData.Value<JArray>(DataKey);
|
||||
Collection<InstagramSubscription> result = subs.ToObject<Collection<InstagramSubscription>>();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to posts submitted by all users authenticated with this Instagram client.
|
||||
/// </summary>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook. This makes it possible to
|
||||
/// support multiple WebHooks with individual configurations.</param>
|
||||
/// <param name="callback">The URI where WebHooks for the given subscription will be received. Typically this will
|
||||
/// be of the form <c>https://<host>/api/webhooks/incoming/instagram/{id}</c>.</param>
|
||||
/// <returns>A <see cref="InstagramSubscription"/> instance.</returns>
|
||||
public virtual Task<InstagramSubscription> SubscribeAsync(string id, Uri callback)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException("callback");
|
||||
}
|
||||
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "object", "user" },
|
||||
{ "aspect", "media" }
|
||||
};
|
||||
return CreateSubscription(id, callback, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all active subscriptions for this client.
|
||||
/// </summary>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook. This makes it possible to
|
||||
/// support multiple WebHooks with individual configurations.</param>
|
||||
public virtual Task UnsubscribeAsync(string id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
string parameter = "&object=all";
|
||||
return DeleteSubscription(id, parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the subscription with the given <paramref name="subscriptionId"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook. This makes it possible to
|
||||
/// support multiple WebHooks with individual configurations.</param>
|
||||
/// <param name="subscriptionId">The ID of the subscription to delete.</param>
|
||||
public virtual Task UnsubscribeAsync(string id, string subscriptionId)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
if (subscriptionId == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
string parameter = "&id=" + subscriptionId;
|
||||
return DeleteSubscription(id, parameter);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal static void ValidateConfig(string key, string value, string id, int minLength, int maxLength)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Receiver_BadSecret, key, id, minLength, maxLength);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<string> GetErrorContent(HttpResponseMessage response)
|
||||
{
|
||||
string errorMessage = string.Empty;
|
||||
if (response.Content != null)
|
||||
{
|
||||
errorMessage = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <b>false</b> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
if (_httpClient != null)
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Client ID and Client Secret for the Instagram Client application.
|
||||
/// </summary>
|
||||
/// <param name="id">A (potentially empty) ID of a particular configuration for this WebHook.</param>
|
||||
/// <returns>A <see cref="Tuple{T1,T2}"/> containing the Client ID and Client Secret.</returns>
|
||||
protected virtual async Task<Tuple<string, string>> GetClientConfig(string id)
|
||||
{
|
||||
IWebHookReceiverConfig receiverConfig = _config.DependencyResolver.GetReceiverConfig();
|
||||
|
||||
string clientId = await receiverConfig.GetReceiverConfigAsync(ClientIdKey, id, InstagramWebHookReceiver.SecretMinLength, InstagramWebHookReceiver.SecretMaxLength);
|
||||
ValidateConfig(ClientIdKey, clientId, id, InstagramWebHookReceiver.SecretMinLength, InstagramWebHookReceiver.SecretMaxLength);
|
||||
|
||||
string clientSecret = await receiverConfig.GetReceiverConfigAsync(InstagramWebHookReceiver.ReceiverName, id, InstagramWebHookReceiver.SecretMinLength, InstagramWebHookReceiver.SecretMaxLength);
|
||||
ValidateConfig(InstagramWebHookReceiver.ReceiverName, clientSecret, id, InstagramWebHookReceiver.SecretMinLength, InstagramWebHookReceiver.SecretMaxLength);
|
||||
|
||||
return Tuple.Create(clientId, clientSecret);
|
||||
}
|
||||
|
||||
private async Task<InstagramSubscription> CreateSubscription(string id, Uri callback, IDictionary<string, object> parameters)
|
||||
{
|
||||
if (!callback.IsAbsoluteUri)
|
||||
{
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Client_NotAbsoluteCallback, "https://<host>/api/webhooks/incoming/instagram");
|
||||
throw new ArgumentException(msg, "receiver");
|
||||
}
|
||||
|
||||
if (!callback.IsHttps())
|
||||
{
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Client_NoHttps, Uri.UriSchemeHttps);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
|
||||
// Build up subscription request body
|
||||
MultipartFormDataContent content = new MultipartFormDataContent();
|
||||
|
||||
// Add default properties
|
||||
Tuple<string, string> clientInfo = await GetClientConfig(id);
|
||||
parameters["client_id"] = clientInfo.Item1;
|
||||
parameters["client_secret"] = clientInfo.Item2;
|
||||
parameters["callback_url"] = callback.AbsoluteUri;
|
||||
|
||||
// Add subscription specific properties
|
||||
foreach (KeyValuePair<string, object> parameter in parameters)
|
||||
{
|
||||
StringContent p = new StringContent(parameter.Value.ToString());
|
||||
p.Headers.ContentType = null;
|
||||
content.Add(p, parameter.Key);
|
||||
}
|
||||
|
||||
using (HttpResponseMessage response = await _httpClient.PostAsync(_subscriptionAddress, content))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
string errorMessage = await GetErrorContent(response);
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Client_SubscribeFailure, response.StatusCode, errorMessage);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
|
||||
JObject subscriptionData = await response.Content.ReadAsAsync<JObject>();
|
||||
InstagramSubscription subs = subscriptionData.Value<JObject>(DataKey).ToObject<InstagramSubscription>();
|
||||
return subs;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteSubscription(string id, string parameter)
|
||||
{
|
||||
Tuple<string, string> clientInfo = await GetClientConfig(id);
|
||||
string address = string.Format(CultureInfo.InvariantCulture, SubscriptionAddressTemplate, clientInfo.Item1, clientInfo.Item2, parameter);
|
||||
|
||||
using (HttpResponseMessage response = await _httpClient.DeleteAsync(address))
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
string errorMessage = await GetErrorContent(response);
|
||||
string msg = string.Format(CultureInfo.CurrentCulture, InstagramReceiverResources.Client_UnsubscribeFailure, response.StatusCode, errorMessage);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Routing;
|
||||
using Microsoft.AspNet.WebHooks;
|
||||
using Microsoft.AspNet.WebHooks.Routes;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
public class InstagramWebHookClientExtensionsTests
|
||||
{
|
||||
private const string Link = "http://localhost/some/path";
|
||||
private const string TestId = "12b2431e389abdc9c3632516";
|
||||
|
||||
private HttpConfiguration _config;
|
||||
private Mock<UrlHelper> _helperMock;
|
||||
private Mock<InstagramWebHookClient> _clientMock;
|
||||
private Uri _callback;
|
||||
private InstagramSubscription _sub;
|
||||
|
||||
public InstagramWebHookClientExtensionsTests()
|
||||
{
|
||||
_config = new HttpConfiguration();
|
||||
_helperMock = new Mock<UrlHelper>();
|
||||
_clientMock = new Mock<InstagramWebHookClient>(_config);
|
||||
_callback = new Uri(Link);
|
||||
_sub = new InstagramSubscription();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCallback_CreatesExpectedReceiverUri()
|
||||
{
|
||||
// Arrange
|
||||
_helperMock.Setup(u => u.Link(WebHookReceiverRouteNames.ReceiversAction, It.Is<Dictionary<string, object>>(d => (string)d["webHookReceiver"] == InstagramWebHookReceiver.ReceiverName && (string)d["id"] == TestId)))
|
||||
.Returns(Link)
|
||||
.Verifiable();
|
||||
|
||||
// Act
|
||||
Uri actual = InstagramWebHookClientExtensions.GetCallback(TestId, _helperMock.Object);
|
||||
|
||||
// Assert
|
||||
_helperMock.Verify();
|
||||
Assert.Equal(new Uri(Link), actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_SubscribersUser()
|
||||
{
|
||||
// Arrange
|
||||
_helperMock.Setup(u => u.Link(WebHookReceiverRouteNames.ReceiversAction, It.Is<Dictionary<string, object>>(d => (string)d["webHookReceiver"] == InstagramWebHookReceiver.ReceiverName && (string)d["id"] == TestId)))
|
||||
.Returns(Link)
|
||||
.Verifiable();
|
||||
_clientMock.Setup(c => c.SubscribeAsync(TestId, _callback))
|
||||
.ReturnsAsync(_sub)
|
||||
.Verifiable();
|
||||
|
||||
// Act
|
||||
InstagramSubscription actual = await _clientMock.Object.SubscribeAsync(TestId, _helperMock.Object);
|
||||
|
||||
// Assert
|
||||
_helperMock.Verify();
|
||||
_clientMock.Verify();
|
||||
Assert.Equal(_sub, actual);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
{
|
||||
"attribution": null,
|
||||
"videos": {
|
||||
"low_bandwidth": {
|
||||
"url": "https://scontent.cdninstagram.com/LowBw.mp4",
|
||||
"width": 480,
|
||||
"height": 270
|
||||
},
|
||||
"standard_resolution": {
|
||||
"url": "https://scontent.cdninstagram.com/StdRes.mp4",
|
||||
"width": 640,
|
||||
"height": 360
|
||||
},
|
||||
"low_resolution": {
|
||||
"url": "https://scontent.cdninstagram.com/LowRes.mp4",
|
||||
"width": 480,
|
||||
"height": 270
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"compilation",
|
||||
"handstand",
|
||||
"drill"
|
||||
],
|
||||
"location": {
|
||||
"latitude": 40.7437744,
|
||||
"name": "Equinox At The High Line",
|
||||
"longitude": -74.0068283,
|
||||
"id": 225623404
|
||||
},
|
||||
"comments": {
|
||||
"count": 0,
|
||||
"data": [ ]
|
||||
},
|
||||
"filter": "Filter",
|
||||
"created_time": "3600",
|
||||
"link": "https://instagram.com/p/3sFga24da/",
|
||||
"likes": {
|
||||
"count": 1,
|
||||
"data": [
|
||||
{
|
||||
"username": "other",
|
||||
"profile_picture": "https://igcdn-photos-d-a.akamaihd.net/otherprofile.jpg",
|
||||
"id": "269124117",
|
||||
"full_name": "Other User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"images": {
|
||||
"low_resolution": {
|
||||
"url": "https://scontent.cdninstagram.com/LowRes.jpg",
|
||||
"width": 320,
|
||||
"height": 320
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://scontent.cdninstagram.com/Thumbnail.jpg",
|
||||
"width": 150,
|
||||
"height": 150
|
||||
},
|
||||
"standard_resolution": {
|
||||
"url": "https://scontent.cdninstagram.com/StdRes.jpg",
|
||||
"width": 640,
|
||||
"height": 640
|
||||
}
|
||||
},
|
||||
"users_in_photo": [ ],
|
||||
"caption": {
|
||||
"created_time": "3600",
|
||||
"text": "#handstand #drill #compilation",
|
||||
"from": {
|
||||
"username": "user",
|
||||
"profile_picture": "https://scontent.cdninstagram.com/userprofile.jpg",
|
||||
"id": "194771423",
|
||||
"full_name": "Some User"
|
||||
},
|
||||
"id": "1077852735856538330"
|
||||
},
|
||||
"type": "video",
|
||||
"id": "1077852647225486162_194771465",
|
||||
"user": {
|
||||
"username": "user",
|
||||
"profile_picture": "https://scontent.cdninstagram.com/userprofile.jpg",
|
||||
"id": "194771423",
|
||||
"full_name": "Some User"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"object": "tag",
|
||||
"object_id": "mytag",
|
||||
"aspect": "media",
|
||||
"callback_url": "http://requestb.in/18jwdvk1",
|
||||
"type": "subscription",
|
||||
"id": "19985884"
|
||||
}
|
|
@ -59,25 +59,14 @@
|
|||
<Compile Include="..\Common\EmbeddedResource.cs">
|
||||
<Link>Common\EmbeddedResource.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Extensions\InstagramWebHookClientExtensionsTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Serialization\InstagramUnixTimeConverterTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotificationCollectionTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotificationDataTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramPostDataTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramNotificationTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramCaptionTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramUserTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramSubscriptionTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramMediaTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramLocationTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramWebHookReceiverTests.cs" />
|
||||
<Compile Include="WebHooks\InstagramWebHookClientTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="App.config" />
|
||||
<EmbeddedResource Include="Messages\PostMessage.json" />
|
||||
<EmbeddedResource Include="Messages\SubscriptionMessage.json" />
|
||||
<EmbeddedResource Include="Messages\NotificationCollectionMessage.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks.Serialization
|
||||
{
|
||||
public class InstagramUnixTimeConverterTests
|
||||
{
|
||||
private static readonly DateTime _Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public static TheoryData<string> NullDateTimeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>
|
||||
{
|
||||
"{ \"Value\": null }",
|
||||
"{ \"Value\": { } }",
|
||||
"{ \"Value\": [ ] }",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string> InvalidDateTimeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>
|
||||
{
|
||||
"{ \"Value\": \"invalid\" }",
|
||||
"{ \"Value\": 123456 }",
|
||||
"{ \"Value\": 1.23456 }",
|
||||
"{ \"Value\": true }",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<long> ValidReadDateTimeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<long>
|
||||
{
|
||||
0,
|
||||
1442710070,
|
||||
-144271007,
|
||||
int.MinValue,
|
||||
int.MaxValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<DateTime, string> ValidWriteDateTimeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<DateTime, string>
|
||||
{
|
||||
{ new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), "{\"Value\":\"0\"}" },
|
||||
{ new DateTime(1960, 1, 1, 0, 0, 0, DateTimeKind.Utc), "{\"Value\":\"-315619200\"}" },
|
||||
{ new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc), "{\"Value\":\"1420070400\"}" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<DateTime> RoundtripValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<DateTime>
|
||||
{
|
||||
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
new DateTime(1960, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("NullDateTimeValues")]
|
||||
public void ReadJson_ThrowsOnNull(string input)
|
||||
{
|
||||
// Act
|
||||
InvalidOperationException iex = Assert.Throws<InvalidOperationException>(() => JsonConvert.DeserializeObject<TestClass>(input));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Cannot convert null value to type 'DateTime'.", iex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("InvalidDateTimeValues")]
|
||||
public void ReadJson_ThrowsOnInvalidValue(string input)
|
||||
{
|
||||
// Act
|
||||
InvalidOperationException iex = Assert.Throws<InvalidOperationException>(() => JsonConvert.DeserializeObject<TestClass>(input));
|
||||
|
||||
// Assert
|
||||
Assert.StartsWith("Cannot read value '", iex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("ValidReadDateTimeValues")]
|
||||
public void ReadJson_ReadsValue_AsUtc(long delta)
|
||||
{
|
||||
// Arrange
|
||||
DateTime expected = _Epoch.AddSeconds(delta);
|
||||
string input = string.Format("{{ \"Value\": \"{0}\" }}", delta);
|
||||
|
||||
// Act
|
||||
TestClass actual = JsonConvert.DeserializeObject<TestClass>(input);
|
||||
DateTime utcActual = actual.Value.ToUniversalTime();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual.Value);
|
||||
Assert.Equal(expected, utcActual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("ValidWriteDateTimeValues")]
|
||||
public void WriteJson_WritesValue_AsUtc(DateTime input, string expected)
|
||||
{
|
||||
// Arrange
|
||||
TestClass data = new TestClass() { Value = input };
|
||||
|
||||
// Act
|
||||
string actual = JsonConvert.SerializeObject(data);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("RoundtripValues")]
|
||||
public void WriteJson_ReadJson_Roundtrips(DateTime expected)
|
||||
{
|
||||
// Arrange
|
||||
TestClass data = new TestClass() { Value = expected };
|
||||
|
||||
// Act
|
||||
string serialized = JsonConvert.SerializeObject(data);
|
||||
TestClass actualData = JsonConvert.DeserializeObject<TestClass>(serialized);
|
||||
DateTime actual = actualData.Value;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
private class TestClass
|
||||
{
|
||||
[JsonConverter(typeof(InstagramUnixTimeConverter))]
|
||||
public DateTime Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.TestUtilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramCaptionTests
|
||||
{
|
||||
private InstagramCaption _caption = new InstagramCaption();
|
||||
|
||||
[Fact]
|
||||
public void Id_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_caption, c => c.Id, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatedTime_Roundtrips()
|
||||
{
|
||||
DateTime roundtrip = DateTime.UtcNow;
|
||||
PropertyAssert.Roundtrips(_caption, c => c.CreatedTime, defaultValue: DateTime.MinValue, roundtripValue: roundtrip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Text_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_caption, c => c.Text, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void From_Roundtrips()
|
||||
{
|
||||
InstagramUser roundtrip = new InstagramUser();
|
||||
PropertyAssert.Roundtrips(_caption, c => c.From, PropertySetter.NullRoundtrips, roundtripValue: roundtrip);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.TestUtilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramLocationTests
|
||||
{
|
||||
private InstagramLocation _location = new InstagramLocation();
|
||||
|
||||
[Fact]
|
||||
public void Id_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_location, l => l.Id, defaultValue: 0, roundtripValue: 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_location, l => l.Name, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Latitude_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_location, l => l.Latitude, defaultValue: 0, roundtripValue: 1.2345);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Longitude_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_location, l => l.Longitude, defaultValue: 0, roundtripValue: 1.2345);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.TestUtilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramMediaTests
|
||||
{
|
||||
private InstagramMedia _media = new InstagramMedia();
|
||||
|
||||
[Fact]
|
||||
public void Address_Roundtrips()
|
||||
{
|
||||
Uri roundtrip = new Uri("http://localhost");
|
||||
PropertyAssert.Roundtrips(_media, m => m.Address, PropertySetter.NullRoundtrips, roundtripValue: roundtrip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Width_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_media, m => m.Width, defaultValue: 0, roundtripValue: 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Height_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_media, m => m.Height, defaultValue: 0, roundtripValue: 1024);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PostTests", Justification = "This is the right name.")]
|
||||
public class InstagramPostDataTests
|
||||
{
|
||||
private DateTime _testTime = new DateTime(1970, 1, 1, 1, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
[Fact]
|
||||
public void InstagramPost_Roundtrips()
|
||||
{
|
||||
// Arrange
|
||||
JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.PostMessage.json");
|
||||
InstagramPostData expectedPost = new InstagramPostData
|
||||
{
|
||||
Id = "1077852647225486162_194771465",
|
||||
Link = new Uri("https://instagram.com/p/3sFga24da/"),
|
||||
CreatedTime = _testTime,
|
||||
MediaType = "video",
|
||||
Location = new InstagramLocation
|
||||
{
|
||||
Id = 225623404,
|
||||
Name = "Equinox At The High Line",
|
||||
Latitude = 40.7437744,
|
||||
Longitude = -74.0068283
|
||||
},
|
||||
Images = new InstagramImages
|
||||
{
|
||||
Thumbnail = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/Thumbnail.jpg"),
|
||||
Width = 150,
|
||||
Height = 150,
|
||||
},
|
||||
LowResolution = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/LowRes.jpg"),
|
||||
Width = 320,
|
||||
Height = 320
|
||||
},
|
||||
StandardResolution = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/StdRes.jpg"),
|
||||
Width = 640,
|
||||
Height = 640
|
||||
}
|
||||
},
|
||||
Videos = new InstagramVideos
|
||||
{
|
||||
LowBandwidth = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/LowBw.mp4"),
|
||||
Width = 480,
|
||||
Height = 270,
|
||||
},
|
||||
LowResolution = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/LowRes.mp4"),
|
||||
Width = 480,
|
||||
Height = 270
|
||||
},
|
||||
StandardResolution = new InstagramMedia
|
||||
{
|
||||
Address = new Uri("https://scontent.cdninstagram.com/StdRes.mp4"),
|
||||
Width = 640,
|
||||
Height = 360
|
||||
}
|
||||
},
|
||||
Caption = new InstagramCaption
|
||||
{
|
||||
CreatedTime = _testTime,
|
||||
Id = "1077852735856538330",
|
||||
Text = "#handstand #drill #compilation",
|
||||
From = new InstagramUser
|
||||
{
|
||||
UserName = "user",
|
||||
ProfilePicture = new Uri("https://scontent.cdninstagram.com/userprofile.jpg"),
|
||||
FullName = "Some User",
|
||||
Id = "194771423"
|
||||
}
|
||||
},
|
||||
User = new InstagramUser
|
||||
{
|
||||
Id = "194771423",
|
||||
FullName = "Some User",
|
||||
UserName = "user",
|
||||
ProfilePicture = new Uri("https://scontent.cdninstagram.com/userprofile.jpg"),
|
||||
}
|
||||
};
|
||||
expectedPost.Tags.Add("compilation");
|
||||
expectedPost.Tags.Add("handstand");
|
||||
expectedPost.Tags.Add("drill");
|
||||
|
||||
// Act
|
||||
InstagramPostData actualPost = data.ToObject<InstagramPostData>();
|
||||
|
||||
// Assert
|
||||
string expectedJson = JsonConvert.SerializeObject(expectedPost);
|
||||
string actualJson = JsonConvert.SerializeObject(actualPost);
|
||||
Assert.Equal(expectedJson, actualJson);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.TestUtilities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramSubscriptionTests
|
||||
{
|
||||
private InstagramSubscription _sub = new InstagramSubscription();
|
||||
|
||||
[Fact]
|
||||
public void Id_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_sub, s => s.Id, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Object_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_sub, s => s.Object, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Aspect_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_sub, s => s.Aspect, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Callback_Roundtrips()
|
||||
{
|
||||
Uri roundtrip = new Uri("http://localhost");
|
||||
PropertyAssert.Roundtrips(_sub, s => s.Callback, PropertySetter.NullRoundtrips, roundtripValue: roundtrip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InstagramSubscription_Roundtrips()
|
||||
{
|
||||
// Arrange
|
||||
JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.SubscriptionMessage.json");
|
||||
InstagramSubscription expectedSubscription = new InstagramSubscription
|
||||
{
|
||||
Id = "19985884",
|
||||
Object = "tag",
|
||||
Aspect = "media",
|
||||
Callback = new Uri("http://requestb.in/18jwdvk1"),
|
||||
};
|
||||
|
||||
// Act
|
||||
InstagramSubscription actualPost = data.ToObject<InstagramSubscription>();
|
||||
|
||||
// Assert
|
||||
string expectedJson = JsonConvert.SerializeObject(expectedSubscription);
|
||||
string actualJson = JsonConvert.SerializeObject(actualPost);
|
||||
Assert.Equal(expectedJson, actualJson);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.TestUtilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramUserTests
|
||||
{
|
||||
private InstagramUser _user = new InstagramUser();
|
||||
|
||||
[Fact]
|
||||
public void Id_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_user, u => u.Id, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UserName_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_user, u => u.UserName, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullName_Roundtrips()
|
||||
{
|
||||
PropertyAssert.Roundtrips(_user, u => u.FullName, PropertySetter.NullRoundtrips, roundtripValue: "Value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProfilePicture_Roundtrips()
|
||||
{
|
||||
Uri roundtrip = new Uri("http://localhost");
|
||||
PropertyAssert.Roundtrips(_user, u => u.ProfilePicture, PropertySetter.NullRoundtrips, roundtripValue: roundtrip);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.TestUtilities.Mocks;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.WebHooks
|
||||
{
|
||||
public class InstagramWebHookClientTests
|
||||
{
|
||||
private const string TestId = "";
|
||||
private const string TestSubscriptions = "{ \"meta\": { \"code\": 200 }, \"data\": [ { \"id\": \"1\", \"type\": \"subscribe\", \"object\": \"user\", \"aspect\": \"media\", \"callback_url\": \"http://your-callback.com/url/\" }, { \"id\": \"2\", \"type\": \"subscription\", \"object\": \"location\", \"object_id\": \"2345\", \"aspect\": \"media\", \"callback_url\": \"http://your-callback.com/url/\" } ] }";
|
||||
private const string TestClientId = "41225b0f627e4c31a442f1ebf55e4a6d";
|
||||
private const string TestClientSecret = "a0913d87aba24e689266ad8ecbd3832e";
|
||||
private const string TestSubAddress = "https://api.instagram.com/v1/subscriptions";
|
||||
private const string TestCallback = "https://www.exmample.org/callback";
|
||||
|
||||
private readonly HttpConfiguration _httpConfig;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpMessageHandlerMock _handlerMock;
|
||||
private readonly Mock<InstagramWebHookClient> _clientMock;
|
||||
private readonly InstagramWebHookClient _client;
|
||||
private readonly Uri _callback;
|
||||
|
||||
public InstagramWebHookClientTests()
|
||||
{
|
||||
_httpConfig = new HttpConfiguration();
|
||||
_handlerMock = new HttpMessageHandlerMock();
|
||||
_httpClient = new HttpClient(_handlerMock);
|
||||
_clientMock = new Mock<InstagramWebHookClient>(_httpConfig, _httpClient) { CallBase = true };
|
||||
_client = _clientMock.Object;
|
||||
_callback = new Uri(TestCallback);
|
||||
}
|
||||
|
||||
public static TheoryData<string> ValidIdData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>
|
||||
{
|
||||
{ string.Empty },
|
||||
{ "id" },
|
||||
{ "你好" },
|
||||
{ "1" },
|
||||
{ "1234567890" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[MemberData("ValidIdData")]
|
||||
public async Task GetAllSubscriptionsAsync_Throws_OnError(string id)
|
||||
{
|
||||
// Arrange
|
||||
Initialize(id);
|
||||
_handlerMock.Handler = (req, reqId) =>
|
||||
{
|
||||
HttpResponseMessage rsp = new HttpResponseMessage();
|
||||
rsp.Content = new StringContent(TestSubscriptions, Encoding.UTF8, "application/json");
|
||||
return Task.FromResult(rsp);
|
||||
};
|
||||
|
||||
// Act
|
||||
Collection<InstagramSubscription> actual = await _client.GetAllSubscriptionsAsync(id);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, actual.Count);
|
||||
Assert.Equal("1", actual[0].Id);
|
||||
Assert.Equal("user", actual[0].Object);
|
||||
Assert.Equal("2", actual[1].Id);
|
||||
Assert.Equal("location", actual[1].Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_FailsOnRelativeReceiverAddress()
|
||||
{
|
||||
// Arrange
|
||||
Initialize(TestId);
|
||||
Uri relative = new Uri("relative", UriKind.Relative);
|
||||
|
||||
// Act
|
||||
ArgumentException ex = await Assert.ThrowsAsync<ArgumentException>(() => _client.SubscribeAsync(TestId, relative));
|
||||
|
||||
// Assert
|
||||
Assert.Contains("The URI for where Instagram will send WebHook requests must be an absolute URI. By default this should be of the form 'https://<host>/api/webhooks/incoming/instagram'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_CreatesUserSubscription()
|
||||
{
|
||||
// Arrange
|
||||
Initialize(TestId);
|
||||
HttpResponseMessage response = new HttpResponseMessage();
|
||||
response.Content = new StringContent("{ \"meta\": { \"code\": 200 }, \"data\": { \"id\": \"1\", \"type\": \"subscribe\", \"object\": \"user\", \"aspect\": \"media\", \"callback_url\": \"" + TestCallback + "\" } }", Encoding.UTF8, "application/json");
|
||||
_handlerMock.Handler = async (req, counter) =>
|
||||
{
|
||||
MultipartFormDataContent content = await ValidateCoreSubscriptionRequest(req);
|
||||
await ValidateSubscriptionContent(content, 0, "object", "user");
|
||||
await ValidateSubscriptionContent(content, 1, "aspect", "media");
|
||||
return response;
|
||||
};
|
||||
|
||||
// Act
|
||||
InstagramSubscription actual = await _client.SubscribeAsync(TestId, _callback);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("1", actual.Id);
|
||||
Assert.Equal("user", actual.Object);
|
||||
Assert.Equal(TestCallback, actual.Callback.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_ThrowsOnErrorResponse()
|
||||
{
|
||||
// Arrange
|
||||
Initialize(TestId);
|
||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
|
||||
response.Content = new StringContent("{\"meta\":{\"error_type\":\"APISubscriptionError\",\"code\":400,\"error_message\":\"Invalid format\"}}", Encoding.UTF8, "text/plain");
|
||||
_handlerMock.Handler = (req, counter) => Task.FromResult(response);
|
||||
|
||||
// Act
|
||||
InvalidOperationException ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _client.SubscribeAsync(TestId, _callback));
|
||||
|
||||
// Assert
|
||||
Assert.StartsWith("Could not create Instagram subscription", ex.Message);
|
||||
}
|
||||
|
||||
private async Task<MultipartFormDataContent> ValidateCoreSubscriptionRequest(HttpRequestMessage req)
|
||||
{
|
||||
Assert.Equal(TestSubAddress, req.RequestUri.AbsoluteUri);
|
||||
MultipartFormDataContent content = (MultipartFormDataContent)req.Content;
|
||||
|
||||
int last = content.Count() - 1;
|
||||
await ValidateSubscriptionContent(content, last - 2, "client_id", TestClientId);
|
||||
await ValidateSubscriptionContent(content, last - 1, "client_secret", TestClientSecret);
|
||||
await ValidateSubscriptionContent(content, last, "callback_url", TestCallback);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private async Task ValidateSubscriptionContent(MultipartFormDataContent content, int index, string name, string value)
|
||||
{
|
||||
StringContent parameter = (StringContent)content.ElementAt(index);
|
||||
string actual = await parameter.ReadAsStringAsync();
|
||||
|
||||
Assert.Equal(value, actual);
|
||||
Assert.Null(parameter.Headers.ContentType);
|
||||
ContentDispositionHeaderValue cd = parameter.Headers.ContentDisposition;
|
||||
Assert.Equal(name, cd.Name);
|
||||
}
|
||||
|
||||
private void Initialize(string id)
|
||||
{
|
||||
_clientMock.Protected()
|
||||
.Setup<Task<Tuple<string, string>>>("GetClientConfig", id)
|
||||
.ReturnsAsync(Tuple.Create(TestClientId, TestClientSecret))
|
||||
.Verifiable();
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче