diff --git a/Directory.Packages.props b/Directory.Packages.props index 93a0439c7..331a0ad17 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,13 +1,15 @@ - true true - + + + + @@ -23,6 +25,9 @@ + + + + - - + \ No newline at end of file diff --git a/Documentation/guides/basic-concepts/http-interactions.md b/Documentation/guides/basic-concepts/http-interactions.md deleted file mode 100644 index 44d0a677a..000000000 --- a/Documentation/guides/basic-concepts/http-interactions.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -omitAppTitle: true -title: Handling Discord HTTP Interactions with C# and ASP.NET Core -description: Learn how to make your Discord bot receive interactions from Discord via HTTP requests using the NetCord.Hosting.AspNetCore package in C# and ASP.NET Core. ---- - -# Handling HTTP Interactions with ASP.NET Core - -This guide will show you how to receive and handle Discord interactions, like slash commands and button clicks, through HTTP requests using the [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) package. This ASP.NET Core package can be effortlessly integrated with [NetCord.Hosting.Services](https://www.nuget.org/packages/NetCord.Hosting.Services) to handle these HTTP interactions in C# easily. Additionally, you can implement your own @NetCord.Hosting.IHttpInteractionHandler to manually handle HTTP interactions received from Discord, giving you full control over your bot's behavior. - -## Required Dependencies - -Before you get started, ensure that you've installed the necessary native dependencies. Follow the [installation guide](../installing-native-dependencies.md) to set them up. - -## Setting Up HTTP Interactions in C# - -To handle HTTP interactions from Discord in your bot, you need to use @NetCord.Hosting.Rest.RestClientServiceCollectionExtensions.AddDiscordRest* to add the @NetCord.Rest.RestClient and then call @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions* to map the HTTP interactions route. You can also use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceServiceCollectionExtensions.AddHttpApplicationCommands* to add the application command service with preconfigured HTTP contexts to your host builder. -[!code-cs[Program.cs](HttpInteractions/Program.cs?highlight=8,16)] - -### Receiving HTTP Interactions via HTTP Interaction Handler - -You can also create your own @NetCord.Hosting.IHttpInteractionHandler to handle HTTP interactions manually. This allows you to have full control over the behavior of your bot when receiving HTTP interactions. You register them using @"NetCord.Hosting.HttpInteractionHandlerServiceCollectionExtensions.AddHttpInteractionHandler``1(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.DependencyInjection.ServiceLifetime)?text=AddHttpInteractionHandler". - -[!code-cs[HttpInteractionHandler.cs](HttpInteractions/HttpInteractionHandler.cs#L6-L14)] - -## Configuring Your Discord Bot for HTTP Interactions - -To make your bot receive HTTP interactions from Discord, you need to store the public key in the configuration and specify the endpoint URL in the [Discord Developer Portal](https://discord.com/developers/applications). - -![Shows 'Public Key' section in 'General Information' section](../../images/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp){width=850px} - -### Specifying the Public Key in the Configuration - -You can for example use `appsettings.json` file for configuration. It should look like this: -[!code-json[appsettings.json](HttpInteractions/appsettings.json?highlight=4)] - -### Specifying the Interactions Endpoint URL - -If your bot is hosted at `https://example.com` and you have specified `/interactions` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions*, the endpoint URL will be `https://example.com/interactions`. Also note that Discord sends validation requests to the endpoint URL, so your bot must be running while updating it. - -For local testing, you can use [ngrok](https://ngrok.com), a tool that exposes your local server to the internet, providing a public URL to receive interactions. Use the following command to start ngrok with a correct port specified: -```bash -ngrok http http://localhost:port -``` - -It will generate a URL that you can use to receive HTTP interactions from Discord. For example, if the URL is `https://random-subdomain.ngrok-free.app` and you have specified `/interactions` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions*, the endpoint URL will be `https://random-subdomain.ngrok-free.app/interactions`. - -## Extending Your HTTP Interactions Bot - -Now, as you have your bot up and running, you can start adding more features to it. You can use the following guides to learn more about the features you can add to your HTTP interactions based bot: - -- **@"application-commands?text=Application Commands":** Learn how to make complex commands with parameters and subcommands with ease. -- **@"component-interactions?text=Component Interactions":** Make your HTTP interactions bot interactive with buttons, select menus, and other components easily. diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj b/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj new file mode 100644 index 000000000..f1a9b9e95 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj @@ -0,0 +1,27 @@ + + + net10.0 + enable + enable + true + Lambda + + + true + + + + + true + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj.diff b/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj.diff new file mode 100644 index 000000000..8318b3677 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj.diff @@ -0,0 +1,9 @@ + + + ... ++ true +- +- true + + ... + diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/Program.cs b/Documentation/guides/http-events/AWSLambda.HttpInteractions/Program.cs new file mode 100644 index 000000000..3d474f850 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/Program.cs @@ -0,0 +1,41 @@ +using NetCord.Hosting.Services.ApplicationCommands; +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; + +using Amazon.Lambda.Serialization.SystemTextJson; +using Amazon.Lambda.APIGatewayEvents; + +using System.Text.Json.Serialization; + +var registerCommands = args.Contains("--register-commands"); + +var builder = WebApplication.CreateSlimBuilder(args); + +builder.Services + .AddDiscordRest() + .AddHttpApplicationCommands(o => o.AutoRegisterCommands = registerCommands) + .AddAWSLambdaHosting(LambdaEventSource.HttpApi, + // That is only required when using Native AOT, + // otherwise that parameter can be omitted + new SourceGeneratorLambdaJsonSerializer()); + +var app = builder.Build(); + +app.AddSlashCommand("ping", "Ping AWS Lambda", () => "Pong from AWS Lambda!"); + +if (registerCommands) +{ + await app.StartAsync(); + await app.StopAsync(); + return; +} + +app.UseHttpInteractions("/"); + +await app.RunAsync(); + +// That is passed to AddAWSLambdaHosting for Native AOT, +// otherwise that class can be omitted +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] +public partial class APIGatewaySerializerContext : JsonSerializerContext; diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/Readme.md b/Documentation/guides/http-events/AWSLambda.HttpInteractions/Readme.md new file mode 100644 index 000000000..7d69335bd --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/Readme.md @@ -0,0 +1,51 @@ +# ASP.NET Core Minimal API Serverless Application + +This project shows how to run an ASP.NET Core Web API project as an AWS Lambda exposed through Amazon API Gateway. The NuGet package [Amazon.Lambda.AspNetCoreServer](https://www.nuget.org/packages/Amazon.Lambda.AspNetCoreServer) contains a Lambda function that is used to translate requests from API Gateway into the ASP.NET Core framework and then the responses from ASP.NET Core back to API Gateway. + + +For more information about how the Amazon.Lambda.AspNetCoreServer package works and how to extend its behavior view its [README](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.AspNetCoreServer/README.md) file in GitHub. + +## Executable Assembly ## + +.NET Lambda projects that use C# top level statements like this project must be deployed as an executable assembly instead of a class library. To indicate to Lambda that the .NET function is an executable assembly the +Lambda function handler value is set to the .NET Assembly name. This is different then deploying as a class library where the function handler string includes the assembly, type and method name. + +To deploy as an executable assembly the Lambda runtime client must be started to listen for incoming events to process. For an ASP.NET Core application the Lambda runtime client is started by included the +`Amazon.Lambda.AspNetCoreServer.Hosting` NuGet package and calling `AddAWSLambdaHosting(LambdaEventSource.HttpApi)` passing in the event source while configuring the services of the application. The +event source can be API Gateway REST API and HTTP API or Application Load Balancer. + +### Project Files ### + +* serverless.template - an AWS CloudFormation Serverless Application Model template file for declaring your Serverless functions and other AWS resources +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS +* Program.cs - entry point to the application that contains all of the top level statements initializing the ASP.NET Core application. +The call to `AddAWSLambdaHosting` configures the application to work in Lambda when it detects Lambda is the executing environment. +* Controllers\CalculatorController - example Web API controller + +You may also have a test project depending on the options selected. + +## Here are some steps to follow from Visual Studio: + +To deploy your Serverless application, right click the project in Solution Explorer and select *Publish to AWS Lambda*. + +To view your deployed application open the Stack View window by double-clicking the stack name shown beneath the AWS CloudFormation node in the AWS Explorer tree. The Stack View also displays the root URL to your published application. + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Deploy application +``` + cd "AWSLambda/src/AWSLambda" + dotnet lambda deploy-serverless +``` diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.Development.json b/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.json b/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/aws-lambda-tools-defaults.json b/Documentation/guides/http-events/AWSLambda.HttpInteractions/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..38996c34c --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/aws-lambda-tools-defaults.json @@ -0,0 +1,14 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "s3-prefix": "AWSLambda/", + "template": "serverless.template", + "template-parameters": "" +} diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template b/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template new file mode 100644 index 000000000..ebf037346 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template @@ -0,0 +1,47 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", + "Parameters": {}, + "Conditions": {}, + "Resources": { + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "MyBot", + "Runtime": "dotnet10", + "CodeUri": "", + "MemorySize": 512, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambda_FullAccess" + ], + "Events": { + "ProxyResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/{proxy+}", + "Method": "ANY" + } + }, + "RootResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/", + "Method": "ANY" + } + } + } + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { + "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + } + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template.diff b/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template.diff new file mode 100644 index 000000000..af9a37ed2 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.HttpInteractions/serverless.template.diff @@ -0,0 +1,32 @@ +{ + ... + "Resources": { + "AspNetCoreFunction": { + ... + "Properties": { + ... + "Events": { + "ProxyResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + }, + "RootResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + } + } + } + } + }, + "Outputs": { + "ApiURL": { + ... + "Value": { ++ "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" +- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" + } + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj b/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj new file mode 100644 index 000000000..f1a9b9e95 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj @@ -0,0 +1,27 @@ + + + net10.0 + enable + enable + true + Lambda + + + true + + + + + true + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj.diff b/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj.diff new file mode 100644 index 000000000..8318b3677 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/AWSLambda.WebhookEvents.csproj.diff @@ -0,0 +1,9 @@ + + + ... ++ true +- +- true + + ... + diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/Program.cs b/Documentation/guides/http-events/AWSLambda.WebhookEvents/Program.cs new file mode 100644 index 000000000..40ef6144d --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/Program.cs @@ -0,0 +1,36 @@ +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; +using NetCord.Rest; + +using Amazon.Lambda.Serialization.SystemTextJson; +using Amazon.Lambda.APIGatewayEvents; + +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateSlimBuilder(args); + +builder.Services + .AddDiscordRest() + .AddWebhookHandler(WebhookEvent.ApplicationAuthorized, (ApplicationAuthorizedWebhookEventArgs args, + ILogger logger) => + { + logger.LogInformation("User '{Username}' authorized with scopes: {Scopes}", + args.User.Username, + args.Scopes); + }) + .AddAWSLambdaHosting(LambdaEventSource.HttpApi, + // That is only required when using Native AOT, + // otherwise that parameter can be omitted + new SourceGeneratorLambdaJsonSerializer()); + +var app = builder.Build(); + +app.UseWebhookEvents("/"); + +await app.RunAsync(); + +// That is passed to AddAWSLambdaHosting for Native AOT, +// otherwise that class can be omitted +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] +public partial class APIGatewaySerializerContext : JsonSerializerContext; diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/Readme.md b/Documentation/guides/http-events/AWSLambda.WebhookEvents/Readme.md new file mode 100644 index 000000000..7d69335bd --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/Readme.md @@ -0,0 +1,51 @@ +# ASP.NET Core Minimal API Serverless Application + +This project shows how to run an ASP.NET Core Web API project as an AWS Lambda exposed through Amazon API Gateway. The NuGet package [Amazon.Lambda.AspNetCoreServer](https://www.nuget.org/packages/Amazon.Lambda.AspNetCoreServer) contains a Lambda function that is used to translate requests from API Gateway into the ASP.NET Core framework and then the responses from ASP.NET Core back to API Gateway. + + +For more information about how the Amazon.Lambda.AspNetCoreServer package works and how to extend its behavior view its [README](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.AspNetCoreServer/README.md) file in GitHub. + +## Executable Assembly ## + +.NET Lambda projects that use C# top level statements like this project must be deployed as an executable assembly instead of a class library. To indicate to Lambda that the .NET function is an executable assembly the +Lambda function handler value is set to the .NET Assembly name. This is different then deploying as a class library where the function handler string includes the assembly, type and method name. + +To deploy as an executable assembly the Lambda runtime client must be started to listen for incoming events to process. For an ASP.NET Core application the Lambda runtime client is started by included the +`Amazon.Lambda.AspNetCoreServer.Hosting` NuGet package and calling `AddAWSLambdaHosting(LambdaEventSource.HttpApi)` passing in the event source while configuring the services of the application. The +event source can be API Gateway REST API and HTTP API or Application Load Balancer. + +### Project Files ### + +* serverless.template - an AWS CloudFormation Serverless Application Model template file for declaring your Serverless functions and other AWS resources +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS +* Program.cs - entry point to the application that contains all of the top level statements initializing the ASP.NET Core application. +The call to `AddAWSLambdaHosting` configures the application to work in Lambda when it detects Lambda is the executing environment. +* Controllers\CalculatorController - example Web API controller + +You may also have a test project depending on the options selected. + +## Here are some steps to follow from Visual Studio: + +To deploy your Serverless application, right click the project in Solution Explorer and select *Publish to AWS Lambda*. + +To view your deployed application open the Stack View window by double-clicking the stack name shown beneath the AWS CloudFormation node in the AWS Explorer tree. The Stack View also displays the root URL to your published application. + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Deploy application +``` + cd "AWSLambda/src/AWSLambda" + dotnet lambda deploy-serverless +``` diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.Development.json b/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.json b/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/aws-lambda-tools-defaults.json b/Documentation/guides/http-events/AWSLambda.WebhookEvents/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..38996c34c --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/aws-lambda-tools-defaults.json @@ -0,0 +1,14 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "s3-prefix": "AWSLambda/", + "template": "serverless.template", + "template-parameters": "" +} diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template b/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template new file mode 100644 index 000000000..ebf037346 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template @@ -0,0 +1,47 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", + "Parameters": {}, + "Conditions": {}, + "Resources": { + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "MyBot", + "Runtime": "dotnet10", + "CodeUri": "", + "MemorySize": 512, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambda_FullAccess" + ], + "Events": { + "ProxyResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/{proxy+}", + "Method": "ANY" + } + }, + "RootResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/", + "Method": "ANY" + } + } + } + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { + "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + } + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template.diff b/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template.diff new file mode 100644 index 000000000..af9a37ed2 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda.WebhookEvents/serverless.template.diff @@ -0,0 +1,32 @@ +{ + ... + "Resources": { + "AspNetCoreFunction": { + ... + "Properties": { + ... + "Events": { + "ProxyResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + }, + "RootResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + } + } + } + } + }, + "Outputs": { + "ApiURL": { + ... + "Value": { ++ "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" +- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" + } + } + } +} diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/.gitignore b/Documentation/guides/http-events/AzureFunction.HttpInteractions/.gitignore new file mode 100644 index 000000000..33579ca65 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/.gitignore @@ -0,0 +1,265 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Commented out because it is used by a guide to show a sample local.settings.json file +# Azure Functions localsettings file +# local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/.vscode/extensions.json b/Documentation/guides/http-events/AzureFunction.HttpInteractions/.vscode/extensions.json new file mode 100644 index 000000000..dde673dcd --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/AzureFunction.HttpInteractions.csproj b/Documentation/guides/http-events/AzureFunction.HttpInteractions/AzureFunction.HttpInteractions.csproj new file mode 100644 index 000000000..8c8c48da6 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/AzureFunction.HttpInteractions.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/Program.cs b/Documentation/guides/http-events/AzureFunction.HttpInteractions/Program.cs new file mode 100644 index 000000000..4bd91239a --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/Program.cs @@ -0,0 +1,37 @@ +using NetCord.Hosting.Services.ApplicationCommands; +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; + +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; + +var registerCommands = Environment.GetEnvironmentVariable("REGISTER_COMMANDS") is "1"; + +var builder = FunctionsApplication.CreateBuilder(args); + +builder.Logging.AddConsole(); + +builder.ConfigureFunctionsWebApplication(); + +builder.Services + .AddDiscordRest() + .AddHttpApplicationCommands(o => o.AutoRegisterCommands = registerCommands) + .AddHttpInteractionProcessor(); + +var host = builder.Build(); + +host.AddSlashCommand("ping", "Ping!", () => "Pong from Azure Function!"); + +await host.RunAsync(); + +public class Interaction(IHttpInteractionProcessor processor) +{ + [Function("interaction")] + public ValueTask RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest request) + { + return processor.ProcessAsync(request.HttpContext); + } +} diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/Properties/launchSettings.json b/Documentation/guides/http-events/AzureFunction.HttpInteractions/Properties/launchSettings.json new file mode 100644 index 000000000..5823e7efd --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "AzureFunction": { + "commandName": "Project", + "commandLineArgs": "--port 7078", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/host.json b/Documentation/guides/http-events/AzureFunction.HttpInteractions/host.json new file mode 100644 index 000000000..bb804e04f --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/host.json @@ -0,0 +1,4 @@ +{ + "version": "2.0", + "telemetryMode": "OpenTelemetry" +} diff --git a/Documentation/guides/http-events/AzureFunction.HttpInteractions/local.settings.json b/Documentation/guides/http-events/AzureFunction.HttpInteractions/local.settings.json new file mode 100644 index 000000000..cc7908ba6 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.HttpInteractions/local.settings.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "REGISTER_COMMANDS": "1" + } +} diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/.gitignore b/Documentation/guides/http-events/AzureFunction.WebhookEvents/.gitignore new file mode 100644 index 000000000..33579ca65 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/.gitignore @@ -0,0 +1,265 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Commented out because it is used by a guide to show a sample local.settings.json file +# Azure Functions localsettings file +# local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/.vscode/extensions.json b/Documentation/guides/http-events/AzureFunction.WebhookEvents/.vscode/extensions.json new file mode 100644 index 000000000..dde673dcd --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/AzureFunction.WebhookEvents.csproj b/Documentation/guides/http-events/AzureFunction.WebhookEvents/AzureFunction.WebhookEvents.csproj new file mode 100644 index 000000000..8c8c48da6 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/AzureFunction.WebhookEvents.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/Program.cs b/Documentation/guides/http-events/AzureFunction.WebhookEvents/Program.cs new file mode 100644 index 000000000..13b6d371f --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/Program.cs @@ -0,0 +1,39 @@ +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; +using NetCord.Rest; + +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; + +var builder = FunctionsApplication.CreateBuilder(args); + +builder.Logging.AddConsole(); + +builder.ConfigureFunctionsWebApplication(); + +builder.Services + .AddDiscordRest() + .AddWebhookHandler(WebhookEvent.ApplicationAuthorized, (ApplicationAuthorizedWebhookEventArgs args, + ILogger logger) => + { + logger.LogInformation("User '{Username}' authorized with scopes: {Scopes}", + args.User.Username, + args.Scopes); + }) + .AddWebhookEventProcessor(); + +var host = builder.Build(); + +await host.RunAsync(); + +public class Webhook(IWebhookEventProcessor processor) +{ + [Function("webhook")] + public ValueTask RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest request) + { + return processor.ProcessAsync(request.HttpContext); + } +} diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/Properties/launchSettings.json b/Documentation/guides/http-events/AzureFunction.WebhookEvents/Properties/launchSettings.json new file mode 100644 index 000000000..5823e7efd --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "AzureFunction": { + "commandName": "Project", + "commandLineArgs": "--port 7078", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/host.json b/Documentation/guides/http-events/AzureFunction.WebhookEvents/host.json new file mode 100644 index 000000000..bb804e04f --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/host.json @@ -0,0 +1,4 @@ +{ + "version": "2.0", + "telemetryMode": "OpenTelemetry" +} diff --git a/Documentation/guides/http-events/AzureFunction.WebhookEvents/local.settings.json b/Documentation/guides/http-events/AzureFunction.WebhookEvents/local.settings.json new file mode 100644 index 000000000..cc7908ba6 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction.WebhookEvents/local.settings.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "REGISTER_COMMANDS": "1" + } +} diff --git a/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs b/Documentation/guides/http-events/Introduction.HttpInteractions/HttpInteractionHandler.cs similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs rename to Documentation/guides/http-events/Introduction.HttpInteractions/HttpInteractionHandler.cs diff --git a/Documentation/guides/http-events/Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs b/Documentation/guides/http-events/Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs new file mode 100644 index 000000000..9401a7dd1 --- /dev/null +++ b/Documentation/guides/http-events/Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs @@ -0,0 +1,45 @@ +using NetCord; +using NetCord.Hosting; + +namespace MyBot; + +internal static class HttpInteractionHandlerExamples +{ + public static void AddDelegateBasedHandler(IServiceCollection services) + { + services.AddHttpInteractionHandler((Interaction interaction, ILogger logger) => + { + logger.LogInformation("User '{Username}' triggered an interaction", interaction.User.Username); + }); + } + + private sealed class MyDbContext; + + public static void AddDelegateBasedHandlerScoped(IServiceCollection services) + { + services.AddHttpInteractionHandler((Interaction interaction, MyDbContext dbContext) => + { + // 'dbContext' is scoped to the handler + }, ServiceLifetime.Scoped); + } + + public static void AddClassBasedHandler(IServiceCollection services) + { + services.AddHttpInteractionHandler(); + } + + public static void AddClassBasedHandlerScoped(IServiceCollection services) + { + services.AddHttpInteractionHandler(ServiceLifetime.Scoped); + } + + public static void AddAllClassBasedHandlers(IServiceCollection services) + { + services.AddHttpInteractionHandlers(typeof(Program).Assembly); + } + + public static void AddAllClassBasedHandlersScoped(IServiceCollection services) + { + services.AddHttpInteractionHandlers(typeof(Program).Assembly, ServiceLifetime.Scoped); + } +} diff --git a/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj b/Documentation/guides/http-events/Introduction.HttpInteractions/Introduction.HttpInteractions.csproj similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj rename to Documentation/guides/http-events/Introduction.HttpInteractions/Introduction.HttpInteractions.csproj diff --git a/Documentation/guides/basic-concepts/HttpInteractions/Program.cs b/Documentation/guides/http-events/Introduction.HttpInteractions/Program.cs similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/Program.cs rename to Documentation/guides/http-events/Introduction.HttpInteractions/Program.cs diff --git a/Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json b/Documentation/guides/http-events/Introduction.HttpInteractions/Properties/launchSettings.json similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json rename to Documentation/guides/http-events/Introduction.HttpInteractions/Properties/launchSettings.json diff --git a/Documentation/guides/basic-concepts/HttpInteractions/appsettings.json b/Documentation/guides/http-events/Introduction.HttpInteractions/appsettings.json similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/appsettings.json rename to Documentation/guides/http-events/Introduction.HttpInteractions/appsettings.json diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/ApplicationDeauthorizedWebhookHandler.cs b/Documentation/guides/http-events/Introduction.WebhookEvents/ApplicationDeauthorizedWebhookHandler.cs new file mode 100644 index 000000000..4beafec9a --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/ApplicationDeauthorizedWebhookHandler.cs @@ -0,0 +1,15 @@ +using NetCord.Hosting.AspNetCore; +using NetCord.Rest; + +namespace MyBot; + +public class ApplicationDeauthorizedWebhookHandler( + ILogger logger) : IApplicationDeauthorizedWebhookHandler +{ + public ValueTask HandleAsync(ApplicationDeauthorizedWebhookEventArgs args) + { + logger.LogInformation("User '{Username}' deauthorized", args.User.Username); + + return default; + } +} diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/Introduction.WebhookEvents.csproj b/Documentation/guides/http-events/Introduction.WebhookEvents/Introduction.WebhookEvents.csproj new file mode 100644 index 000000000..b5e9bb73c --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/Introduction.WebhookEvents.csproj @@ -0,0 +1,13 @@ + + + + Exe + + + + + + + + + diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/Program.cs b/Documentation/guides/http-events/Introduction.WebhookEvents/Program.cs new file mode 100644 index 000000000..69be1dffa --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/Program.cs @@ -0,0 +1,22 @@ +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; +using NetCord.Rest; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddDiscordRest() + .AddWebhookHandler(WebhookEvent.ApplicationAuthorized, (ApplicationAuthorizedWebhookEventArgs args, + ILogger logger) => + { + logger.LogInformation("User '{Username}' authorized with scopes: {Scopes}", + args.User.Username, + args.Scopes); + }); + +var app = builder.Build(); + +// You can specify any pattern here, but remember to update it in the Discord Developer Portal +app.UseWebhookEvents("/webhooks"); + +await app.RunAsync(); diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/Properties/launchSettings.json b/Documentation/guides/http-events/Introduction.WebhookEvents/Properties/launchSettings.json new file mode 100644 index 000000000..e996b6a69 --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "HttpInteractions": { + "commandName": "Project", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:56793" + } + } +} diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/WebhookHandlerExamples.cs b/Documentation/guides/http-events/Introduction.WebhookEvents/WebhookHandlerExamples.cs new file mode 100644 index 000000000..0710dbf7f --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/WebhookHandlerExamples.cs @@ -0,0 +1,38 @@ +using NetCord.Hosting.AspNetCore; +using NetCord.Rest; + +namespace MyBot; + +internal static class WebhookHandlerExamples +{ + private sealed class MyDbContext; + + public static void AddDelegateBasedHandlerScoped(IServiceCollection services) + { + services.AddWebhookHandler(WebhookEvent.ApplicationDeauthorized, (ApplicationDeauthorizedWebhookEventArgs args, + MyDbContext dbContext) => + { + // 'dbContext' is scoped to the handler + }, ServiceLifetime.Scoped); + } + + public static void AddClassBasedHandler(IServiceCollection services) + { + services.AddWebhookHandler(); + } + + public static void AddClassBasedHandlerScoped(IServiceCollection services) + { + services.AddWebhookHandler(ServiceLifetime.Scoped); + } + + public static void AddAllClassBasedHandlers(IServiceCollection services) + { + services.AddWebhookHandlers(typeof(Program).Assembly); + } + + public static void AddAllClassBasedHandlersScoped(IServiceCollection services) + { + services.AddWebhookHandlers(typeof(Program).Assembly, ServiceLifetime.Scoped); + } +} diff --git a/Documentation/guides/http-events/Introduction.WebhookEvents/appsettings.json b/Documentation/guides/http-events/Introduction.WebhookEvents/appsettings.json new file mode 100644 index 000000000..f07a18f1b --- /dev/null +++ b/Documentation/guides/http-events/Introduction.WebhookEvents/appsettings.json @@ -0,0 +1,6 @@ +{ + "Discord": { + "Token": "Token from Discord Developer Portal", + "PublicKey": "Public Key from Discord Developer Portal" + } +} diff --git a/Documentation/guides/http-events/aws-lambda.md b/Documentation/guides/http-events/aws-lambda.md new file mode 100644 index 000000000..f234f8e1f --- /dev/null +++ b/Documentation/guides/http-events/aws-lambda.md @@ -0,0 +1,84 @@ +# Running Serverless C# Discord Bots on AWS Lambda + +AWS Lambda is a great serverless hosting option for HTTP-based C# Discord bots. It lets you run your code without provisioning or managing underlying servers, which makes it a scalable and cost-effective hosting choice. + +Whether you are building an interactive bot using HTTP Interactions or a background integration that listens for Webhook Events, this guide will walk you through the steps to deploy your C# Discord application to AWS Lambda. It will also cover how to enable Native AOT to drastically reduce your cold start times. + +> [!NOTE] +> This guide assumes you have a basic understanding of AWS Lambda. It specifically uses the AWS Lambda ASP.NET Core integration for seamless setup. See [Deploy ASP.NET applications](https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-asp.html) for more information. + +## 1. Project Setup and Optimization + +To get started, create a new project using the `serverless.AspNetCoreMinimalAPI` template. If you aren't familiar with this template, refer to the AWS documentation linked in the note above. + +Once generated, clean up and optimize the configuration. + +### Upgrading to the HTTP API +By default, the template configures a REST API. You can optimize costs and performance by switching to the newer HTTP API. Make the following changes to your `serverless.template` file: + +[!code-diff[serverless.template](AWSLambda.HttpInteractions/serverless.template.diff)] + +### Removing Unnecessary Files +Remove the `Controllers` directory and its contents - they are not needed for this setup. + +### Enabling Native AOT +To reduce your app's cold start times in a serverless environment, we highly recommend enabling Native AOT compilation. Update your project file as follows: + +[!code-diff[AWSLambda.csproj](AWSLambda.HttpInteractions/AWSLambda.HttpInteractions.csproj.diff)] + +### Adding Required Dependencies +Add the following NuGet packages: +* [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) +* [libsodium](https://www.nuget.org/packages/libsodium) + +## 2. Writing the Application + +Update your `Program.cs` file to match the implementation below. Choose the section for your preferred request type. + +### [Http Interactions](#tab/http-interactions) + +This sets up a simple HTTP interaction bot featuring a basic `/ping` command. + +[!code-cs[Program.cs](AWSLambda.HttpInteractions/Program.cs)] + +Note the `--register-commands` flag. Because Lambda functions start and stop frequently, registering commands on every cold start wastes resources and adds latency. + +Before deploying, register the commands by running your bot locally with the flag: `dotnet run -- --register-commands`. (This requires the bot token - see [Configuring Secrets](#4-configuring-secrets).) + +The `registerCommands` variable gates the `AutoRegisterCommands` option - it is `true` only when you explicitly pass `--register-commands` locally, and `false` when the app is running in Lambda. + +### [Webhook Events](#tab/webhook-events) + +This sets up an application-authorized webhook event handler, allowing you to detect when a user authorizes with your application. + +[!code-cs[Program.cs](AWSLambda.WebhookEvents/Program.cs)] + +> [!NOTE] +> Unlike application commands, webhook events do not require any command registration. + +*** + +## 3. Deploying to AWS Lambda + +With your code ready, you can deploy the project. Use the following .NET CLI command to package your application and publish it directly to AWS Lambda: + +```bash +dotnet lambda deploy-serverless +``` + +Once the deployment completes, the AWS endpoint URL will be printed in your console. + +## 4. Configuring Secrets + +Before your app can receive and verify requests from Discord at your new endpoint, you must provide the Public Key to your Lambda function. We will use environment variables for this purpose. + +1. In the AWS Management Console, navigate to your Lambda function. +2. Click on **Configuration**, then select **Environment variables**. +3. Add a new environment variable with the key `Discord__PublicKey` and set its value to your app's Public Key from the Discord Developer Portal. + +If your app also makes authenticated @NetCord.Rest.RestClient calls, add a `Discord__Token` environment variable the same way. + +For HTTP interactions, the bot token is also required for local command registration via `--register-commands`. + +> [!NOTE] +> If you want maximum security for your sensitive credentials, consider using AWS Secrets Manager instead. See [AWS .NET Configuration Extension for Systems Manager](https://github.com/aws/aws-dotnet-extensions-configuration) for more information. diff --git a/Documentation/guides/http-events/azure-function.md b/Documentation/guides/http-events/azure-function.md new file mode 100644 index 000000000..4deb7614b --- /dev/null +++ b/Documentation/guides/http-events/azure-function.md @@ -0,0 +1,77 @@ +# Running Serverless C# Discord Bots on Azure Functions + +Azure Functions is a great serverless hosting option for HTTP-based C# Discord bots. It lets you run your code without provisioning or managing underlying servers, which makes it a scalable and cost-effective hosting choice. + +Whether you are building an interactive bot using HTTP Interactions or a background integration that listens for Webhook Events, this guide will walk you through the steps to deploy your C# Discord application to Azure Functions. + +> [!NOTE] +> This guide assumes you have a basic understanding of Azure Functions. It specifically uses the Azure Functions in the isolated worker model with ASP.NET Core integration. See [Guide for running C# Azure Functions in the isolated worker model](https://learn.microsoft.com/azure/azure-functions/dotnet-isolated-process-guide) for more information. + +## 1. Project Setup and Optimization + +To get started, create a new project using the command below. This will set up an Azure Functions project using the isolated worker model with .NET 10.0 as the target framework. + +```bash +func init --worker-runtime dotnet-isolated --target-framework net10.0 +``` + +### Adding Required Dependencies +Add the following NuGet packages: +* [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) +* [libsodium](https://www.nuget.org/packages/libsodium) + +## 2. Writing the Application + +Update your `Program.cs` file to match the implementation below. Choose the section for your preferred request type. + +### [Http Interactions](#tab/http-interactions) + +This sets up a simple HTTP interaction bot featuring a basic `/ping` command. + +[!code-cs[Program.cs](AzureFunction.HttpInteractions/Program.cs)] + +Note the `REGISTER_COMMANDS` environment variable. + +Because Azure Functions start and stop frequently, registering commands on every cold start wastes resources and adds latency. + +Before deploying, register the commands by running your bot locally (e.g., `dotnet run`) with the `REGISTER_COMMANDS` environment variable set to `1`. (This requires the bot token - see [Configuring Secrets](#4-configuring-secrets).) You can pass that environment variable via `local.settings.json`: + +[!code-json[local.settings.json](AzureFunction.HttpInteractions/local.settings.json)] + +The `registerCommands` variable gates the `AutoRegisterCommands` option - it is `true` only when `REGISTER_COMMANDS` is set to `1` locally, and `false` when the app is running in Azure Functions. + +### [Webhook Events](#tab/webhook-events) + +This sets up an application-authorized webhook event handler, allowing you to detect when a user authorizes with your application. + +[!code-cs[Program.cs](AzureFunction.WebhookEvents/Program.cs)] + +> [!NOTE] +> Unlike application commands, webhook events do not require any command registration. + +*** + +## 3. Deploying to Azure Functions + +With your code ready, you can deploy the project. Use the following Azure CLI command to package your application and publish it directly to Azure Functions: + +```bash +func azure functionapp publish +``` + +Once the deployment completes, the Azure endpoint URL will be printed in your console. + +## 4. Configuring Secrets + +Before your app can receive and verify requests from Discord at your new endpoint, you must provide the Public Key to your Function App. We will use environment variables for this purpose. + +1. In the Azure Portal, navigate to your Function App. +2. Expand the "Settings" section and click on "Environment variables". +3. Add a new environment variable with the name `Discord__PublicKey` and set its value to your app's Public Key from the Discord Developer Portal. + +If your app also makes authenticated @NetCord.Rest.RestClient calls, add a `Discord__Token` environment variable the same way. + +For HTTP interactions, the bot token is also required for local command registration via `REGISTER_COMMANDS`. + +> [!NOTE] +> If you want maximum security for your sensitive credentials, consider using Azure Key Vault instead. See [Use Azure Key Vault configuration provider in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-10.0) for more information. diff --git a/Documentation/guides/http-events/http-interactions.md b/Documentation/guides/http-events/http-interactions.md new file mode 100644 index 000000000..3c47449ea --- /dev/null +++ b/Documentation/guides/http-events/http-interactions.md @@ -0,0 +1,94 @@ +--- +omitAppTitle: true +title: Handling Discord HTTP Interactions with C# and ASP.NET Core +description: Learn how to make your Discord bot receive interactions from Discord via HTTP requests using the NetCord.Hosting.AspNetCore package in C# and ASP.NET Core. +--- + +# Handling HTTP Interactions with ASP.NET Core + +This guide shows you how to receive and handle Discord interactions, such as slash commands and button clicks, through HTTP requests using the [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) package. This package integrates with [NetCord.Hosting.Services](https://www.nuget.org/packages/NetCord.Hosting.Services) to easily handle these HTTP interactions in C#. You can also implement your own @NetCord.Hosting.IHttpInteractionHandler to intercept every incoming interaction, giving you full control over your bot's behavior. + +## Required Dependencies + +Before you get started, make sure you've installed the required native dependencies. Follow the [installation guide](../installing-native-dependencies.md) to set them up. + +## Setting Up + +To handle HTTP interactions from Discord, do the following: + +1. Add the @NetCord.Rest.RestClient using @NetCord.Hosting.Rest.RestClientServiceCollectionExtensions.AddDiscordRest*. +2. Map the HTTP interactions route by calling @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions*. + +Optionally, add the application command service with preconfigured HTTP contexts using @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceServiceCollectionExtensions.AddHttpApplicationCommands*. + +[!code-cs[Program.cs](Introduction.HttpInteractions/Program.cs?highlight=8,16)] + +You can register your own HTTP interaction handler - either as a delegate or as a class that implements @NetCord.Hosting.IHttpInteractionHandler. This gives you full control over how every incoming interaction is processed. + +### Delegate-based Handlers + +Register a delegate-based HTTP interaction handler using @NetCord.Hosting.HttpInteractionHandlerServiceCollectionExtensions.AddHttpInteractionHandler*. + +[!code-cs[Delegate-based HTTP Interaction handler](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L10-L13)] + +You can inject any DI services your handler needs. To control the lifetime of those injected services, specify a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Delegate-based HTTP Interaction handler registration with lifetime](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L20-L23)] + +### Class-based Handlers + +To use a class-based handler, implement @NetCord.Hosting.IHttpInteractionHandler and register it using @NetCord.Hosting.HttpInteractionHandlerServiceCollectionExtensions.AddHttpInteractionHandler*. + +[!code-cs[HttpInteractionHandler.cs](Introduction.HttpInteractions/HttpInteractionHandler.cs#L6-L13)] + +Register it as follows: +[!code-cs[Class-based HTTP Interaction handler registration](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L28)] + +To control the lifetime of a single class handler, specify a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Class-based HTTP Interaction handler registration with lifetime](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L33)] + +#### Registering from an Assembly + +> [!WARNING] +> Registering all handlers from an assembly is not supported when publishing your application with Native AOT or with trimming enabled. In that case, register your handlers explicitly instead. + +To register every public class-based handler in an assembly at once, use @NetCord.Hosting.HttpInteractionHandlerServiceCollectionExtensions.AddHttpInteractionHandlers*. + +[!code-cs[Registering all public class-based HTTP Interaction handlers in an assembly](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L38)] + +For assembly-wide registration, you can set the lifetime of the discovered handlers by specifying a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Registering all public class-based HTTP Interaction handlers in an assembly with lifetime](Introduction.HttpInteractions/HttpInteractionHandlerExamples.cs#L43)] + +### Configuring Your Discord Bot for HTTP Interactions + +To make your bot receive HTTP interactions from Discord, store the public key in the configuration and specify the endpoint URL in the [Discord Developer Portal](https://discord.com/developers/applications). + +![Shows 'Public Key' and 'Interaction Endpoint URL' sections in 'General Information' section](../../images/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp){width=850px} + +#### Specifying the Public Key in the Configuration + +For example, you can use an `appsettings.json` file for configuration. It should look like this: + +[!code-json[appsettings.json](Introduction.HttpInteractions/appsettings.json?highlight=4)] + +#### Specifying the Interactions Endpoint URL + +If your bot is hosted at `https://example.com` and you specified the `/interactions` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions*, the endpoint URL will be `https://example.com/interactions`. Note that Discord sends validation requests to the endpoint URL, so your bot must be running while updating it. + +For local testing, you can use [ngrok](https://ngrok.com), a tool that exposes your local server to the internet by providing a public URL to receive interactions. Use the following command to start ngrok on the correct port: +```bash +ngrok http http://localhost:port +``` + +After it starts, ngrok prints a public URL you can use to receive HTTP interactions from Discord. For example, if the URL is `https://random-subdomain.ngrok-free.app` and you specified the `/interactions` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseHttpInteractions*, the endpoint URL will be `https://random-subdomain.ngrok-free.app/interactions`. + +## Next Steps + +Now that your application is set up to receive Discord interactions, you can expand its features or deploy it to a cloud environment: + +- **[Application Commands](../services/application-commands/introduction.md):** Learn how to build complex commands with parameters and subcommands. +- **[Component Interactions](../services/component-interactions/introduction.md):** Create interactive experiences with buttons, select menus, and other components. +- **[AWS Lambda Deployment](aws-lambda.md):** Deploy your application to AWS Lambda for serverless hosting. +- **[Azure Functions Deployment](azure-function.md):** Deploy your application to Azure Functions for serverless hosting. diff --git a/Documentation/guides/http-events/webhook-events.md b/Documentation/guides/http-events/webhook-events.md new file mode 100644 index 000000000..001fd6cae --- /dev/null +++ b/Documentation/guides/http-events/webhook-events.md @@ -0,0 +1,96 @@ +--- +omitAppTitle: true +title: Handling Discord Webhook Events with C# and ASP.NET Core +description: Learn how to handle Discord Webhook Events using the NetCord.Hosting.AspNetCore package in C# and ASP.NET Core. +--- + +# Handling Webhook Events with ASP.NET Core + +This guide walks you through receiving and handling Webhook Events from Discord in your application using the [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) package. + +## Required Dependencies + +Before you get started, make sure you've installed the required native dependencies. Follow the [installation guide](../installing-native-dependencies.md) to set them up. + +## Setting Up + +To receive Webhook Events from Discord, do the following: + +1. Add the @NetCord.Rest.RestClient using @NetCord.Hosting.Rest.RestClientServiceCollectionExtensions.AddDiscordRest*. +2. Map the webhook events route by calling @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseWebhookEvents*. +3. Register handlers for specific webhook events using @NetCord.Hosting.AspNetCore.WebhookHandlerServiceCollectionExtensions.AddWebhookHandler*. + +[!code-cs[Program.cs](Introduction.WebhookEvents/Program.cs?highlight=8,20)] + +The full example above registers a delegate-based handler. Alternatively, you can register a class that implements one of the @NetCord.Hosting.AspNetCore.IWebhookHandler interfaces. + +### Delegate-based Handlers + +As shown in the example above, register a delegate-based handler using @NetCord.Hosting.AspNetCore.WebhookHandlerServiceCollectionExtensions.AddWebhookHandler*. + +You can inject any DI services your handler needs. To control the lifetime of those injected services, specify a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Delegate-based Webhook Event handler registration with lifetime](Introduction.WebhookEvents/WebhookHandlerExamples.cs#L12-L16)] + +### Class-based Handlers + +To use a class-based handler, implement the @NetCord.Hosting.AspNetCore.IWebhookHandler interface of your choice and register it with @NetCord.Hosting.AspNetCore.WebhookHandlerServiceCollectionExtensions.AddWebhookHandler*. + +[!code-cs[ApplicationDeauthorizedWebhookHandler.cs](Introduction.WebhookEvents/ApplicationDeauthorizedWebhookHandler.cs#L6-L15)] + +Register it as follows: +[!code-cs[Class-based Webhook Handler Registration](Introduction.WebhookEvents/WebhookHandlerExamples.cs#L21)] + +To control the lifetime of a single class handler, specify a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Class-based Webhook Handler Registration with lifetime](Introduction.WebhookEvents/WebhookHandlerExamples.cs#L26)] + +#### Registering from an Assembly + +> [!WARNING] +> Registering all handlers from an assembly is not supported when publishing your application with Native AOT or with trimming enabled. In that case, register your handlers explicitly instead. + +To register every public class-based handler in an assembly at once, use @NetCord.Hosting.AspNetCore.WebhookHandlerServiceCollectionExtensions.AddWebhookHandlers*. + +[!code-cs[Registering all public class-based Webhook Handlers in an assembly](Introduction.WebhookEvents/WebhookHandlerExamples.cs#L31)] + +For assembly-wide registration, you can set the lifetime of the discovered handlers by specifying a @Microsoft.Extensions.DependencyInjection.ServiceLifetime in the registration method; the default is @Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton. See an example below: + +[!code-cs[Registering all public class-based Webhook Handlers in an assembly with lifetime](Introduction.WebhookEvents/WebhookHandlerExamples.cs#L36)] + +### Configuring Webhook Events + +To make your app receive Webhook Events from Discord, store the public key in the configuration (you can find it in the [Discord Developer Portal](https://discord.com/developers/applications)), then enable Webhook Events and specify the endpoint URL there. + +![Shows 'Public Key' section in 'General Information' section](../../images/http-events_FindingPublicKey.webp){width=850px} + +#### Specifying the Public Key in the Configuration + +For example, you can use an `appsettings.json` file for configuration. It should look like this: + +[!code-json[appsettings.json](Introduction.WebhookEvents/appsettings.json?highlight=4)] + +#### Enabling Webhook Events + +In the [Discord Developer Portal](https://discord.com/developers/applications), enable Webhook Events to make your app receive them. +![Shows 'Endpoint' and 'Events' sections in 'Webhooks' section](../../images/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.webp){width=850px} + +Enable the events you want to receive in the Events section. + +### Specifying the Endpoint + +The Discord Developer Portal's **Endpoint** field expects the full public URL that Discord will use to reach your app. If your app is hosted at `https://example.com` and you specified the `/webhooks` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseWebhookEvents*, that URL is `https://example.com/webhooks`. Discord sends validation requests to the endpoint URL, so your app must be running while you save the change. + +For local testing, you can use [ngrok](https://ngrok.com), a tool that exposes your local server to the internet by providing a public URL to receive webhook events. Use the following command to start ngrok on the correct port: +```bash +ngrok http http://localhost:port +``` + +After it starts, ngrok prints a public URL you can use to receive Webhook Events from Discord. For example, if the URL is `https://random-subdomain.ngrok-free.app` and you specified the `/webhooks` pattern in @NetCord.Hosting.AspNetCore.HttpEventEndpointRouteBuilderExtensions.UseWebhookEvents*, the endpoint URL will be `https://random-subdomain.ngrok-free.app/webhooks`. + +## Next Steps + +Now that your application is set up to receive Webhook Events, you can expand its features or deploy it to a cloud environment: + +- **[AWS Lambda Deployment](aws-lambda.md):** Deploy your application to AWS Lambda for serverless hosting. +- **[Azure Functions Deployment](azure-function.md):** Deploy your application to Azure Functions for serverless hosting. diff --git a/Documentation/guides/toc.yml b/Documentation/guides/toc.yml index 1111241ba..ee6450628 100644 --- a/Documentation/guides/toc.yml +++ b/Documentation/guides/toc.yml @@ -16,8 +16,6 @@ href: basic-concepts/responding-to-interactions.md - name: Sending Messages href: basic-concepts/sending-messages.md - - name: HTTP Interactions - href: basic-concepts/http-interactions.md - name: Sharding href: basic-concepts/sharding.md - name: Voice @@ -32,6 +30,16 @@ href: voice/receiving-voice.md - name: Troubleshooting href: voice/troubleshooting.md +- name: HTTP Events + items: + - name: HTTP Interactions + href: http-events/http-interactions.md + - name: Webhook Events + href: http-events/webhook-events.md + - name: AWS Lambda + href: http-events/aws-lambda.md + - name: Azure Function + href: http-events/azure-function.md - name: Services items: - name: Introduction diff --git a/Documentation/image_sources/http-events_FindingPublicKey.pdn b/Documentation/image_sources/http-events_FindingPublicKey.pdn new file mode 100644 index 000000000..6ff407959 Binary files /dev/null and b/Documentation/image_sources/http-events_FindingPublicKey.pdn differ diff --git a/Documentation/image_sources/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn b/Documentation/image_sources/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn new file mode 100644 index 000000000..a50c7ee19 Binary files /dev/null and b/Documentation/image_sources/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn differ diff --git a/Documentation/image_sources/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.pdn b/Documentation/image_sources/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.pdn new file mode 100644 index 000000000..32c5afe89 Binary files /dev/null and b/Documentation/image_sources/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.pdn differ diff --git a/Documentation/image_sources/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn b/Documentation/image_sources/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn deleted file mode 100644 index 446b8faea..000000000 Binary files a/Documentation/image_sources/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.pdn and /dev/null differ diff --git a/Documentation/images/http-events_FindingPublicKey.webp b/Documentation/images/http-events_FindingPublicKey.webp new file mode 100644 index 000000000..0d43d528d Binary files /dev/null and b/Documentation/images/http-events_FindingPublicKey.webp differ diff --git a/Documentation/images/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp b/Documentation/images/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp new file mode 100644 index 000000000..a76819177 Binary files /dev/null and b/Documentation/images/http-events_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp differ diff --git a/Documentation/images/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.webp b/Documentation/images/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.webp new file mode 100644 index 000000000..0cf2c95e3 Binary files /dev/null and b/Documentation/images/http-events_SpecifyingWebhookEndpointAndEnablingWebhookEvents.webp differ diff --git a/Documentation/images/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp b/Documentation/images/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp deleted file mode 100644 index 0155002bc..000000000 Binary files a/Documentation/images/http-interactions_FindingPublicKeyAndSpecifyingInteractionEndpointUrl.webp and /dev/null differ diff --git a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionHandlerInvoker.cs b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionHandlerInvoker.cs index 510bb9cf7..8120dfcab 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionHandlerInvoker.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionHandlerInvoker.cs @@ -12,7 +12,9 @@ public interface IHttpInteractionHandlerInvoker public ValueTask InvokeAsync(Interaction interaction); } -internal sealed partial class HttpInteractionHandlerInvoker(ILogger logger, IEnumerable handlerMetadata, IServiceProvider services) : HttpEventHandlerInvoker, IHttpInteractionHandlerInvoker +internal sealed partial class HttpInteractionHandlerInvoker(ILogger logger, + IEnumerable handlerMetadata, + IServiceProvider services) : HttpEventHandlerInvoker, IHttpInteractionHandlerInvoker { private readonly Func[] _handlers = [.. handlerMetadata.Select(m => CreateInvokeDelegate(m, services))]; diff --git a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionParser.cs b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionParser.cs index e8e2fcfc1..c2df73409 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionParser.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionParser.cs @@ -15,7 +15,8 @@ public interface IHttpInteractionParser public ValueTask ParseAsync(HttpContext context); } -internal sealed class HttpInteractionParser(RestClient client, IOptions options) : HttpEventParser(options), IHttpInteractionParser +internal sealed class HttpInteractionParser(RestClient client, + IOptions options) : HttpEventParser(options), IHttpInteractionParser { protected override IInteraction GetData(HttpContext context, ReadOnlySpan body) { diff --git a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionProcessor.cs b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionProcessor.cs index 9464fe18c..eb6bf6a35 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionProcessor.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionProcessor.cs @@ -20,10 +20,13 @@ public interface IHttpInteractionProcessor internal sealed class HttpInteractionProcessor(IServiceProvider services) : IHttpInteractionProcessor { private readonly IHttpInteractionParser _parser = services.GetService() - ?? new HttpInteractionParser(services.GetRequiredService(), services.GetRequiredService>()); + ?? new HttpInteractionParser(services.GetRequiredService(), + services.GetRequiredService>()); private readonly IHttpInteractionHandlerInvoker _invoker = services.GetService() - ?? new HttpInteractionHandlerInvoker(services.GetRequiredService>(), services.GetServices(), services); + ?? new HttpInteractionHandlerInvoker(services.GetRequiredService>(), + services.GetServices(), + services); public async ValueTask ProcessAsync(HttpContext context) { diff --git a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventHandlerInvoker.cs b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventHandlerInvoker.cs index 8955d391d..864f8b2f7 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventHandlerInvoker.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventHandlerInvoker.cs @@ -24,7 +24,9 @@ internal sealed partial class WebhookEventHandlerInvoker : HttpEventHandlerInvok private readonly Storage _storage; - public WebhookEventHandlerInvoker(ILogger logger, IEnumerable handlersMetadata, IServiceProvider services) + public WebhookEventHandlerInvoker(ILogger logger, + IEnumerable handlersMetadata, + IServiceProvider services) { StorageBuilder builder = new(); diff --git a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventParser.cs b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventParser.cs index 3bf21db8f..8aac20a84 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventParser.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventParser.cs @@ -15,7 +15,8 @@ public interface IWebhookEventParser public ValueTask ParseAsync(HttpContext context); } -internal sealed class WebhookEventParser(RestClient client, IOptions options) : HttpEventParser(options), IWebhookEventParser +internal sealed class WebhookEventParser(RestClient client, + IOptions options) : HttpEventParser(options), IWebhookEventParser { protected override IWebhookEventArgs GetData(HttpContext context, ReadOnlySpan body) { diff --git a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventProcessor.cs b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventProcessor.cs index e77546f59..18555e827 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventProcessor.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/IWebhookEventProcessor.cs @@ -20,10 +20,13 @@ public interface IWebhookEventProcessor internal sealed class WebhookEventProcessor(IServiceProvider services) : IWebhookEventProcessor { private readonly IWebhookEventParser _parser = services.GetService() - ?? new WebhookEventParser(services.GetRequiredService(), services.GetRequiredService>()); + ?? new WebhookEventParser(services.GetRequiredService(), + services.GetRequiredService>()); private readonly IWebhookEventHandlerInvoker _invoker = services.GetService() - ?? new WebhookEventHandlerInvoker(services.GetRequiredService>(), services.GetServices(), services); + ?? new WebhookEventHandlerInvoker(services.GetRequiredService>(), + services.GetServices(), + services); public async ValueTask ProcessAsync(HttpContext context) { diff --git a/NetCord.slnx b/NetCord.slnx index 7c0c8d0a1..e0af1a419 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -6,13 +6,20 @@ - + + + + + + + +