Skip to content

Commit

Permalink
Improve configuration in hosting (#44)
Browse files Browse the repository at this point in the history
* Improve configuration in hosting

* Refactor `RoutePatternHelper.ParseLiteral` and a test project

* Update documentation
  • Loading branch information
KubaZ2 authored Sep 14, 2024
1 parent 59d17b2 commit b90816f
Show file tree
Hide file tree
Showing 46 changed files with 1,043 additions and 162 deletions.
13 changes: 5 additions & 8 deletions Documentation/guides/events/FirstEventsHosting/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
builder.Services
.AddDiscordGateway(options =>
{
options.Configuration = new()
{
Intents = GatewayIntents.GuildMessages
| GatewayIntents.DirectMessages
| GatewayIntents.MessageContent
| GatewayIntents.DirectMessageReactions
| GatewayIntents.GuildMessageReactions,
};
options.Intents = GatewayIntents.GuildMessages
| GatewayIntents.DirectMessages
| GatewayIntents.MessageContent
| GatewayIntents.DirectMessageReactions
| GatewayIntents.GuildMessageReactions;
})
.AddGatewayEventHandlers(typeof(Program).Assembly);

Expand Down
5 changes: 1 addition & 4 deletions Documentation/guides/events/IntentsHosting/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,5 @@
builder.Services
.AddDiscordGateway(options =>
{
options.Configuration = new()
{
Intents = GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.MessageContent,
};
options.Intents = GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.MessageContent;
});
2 changes: 1 addition & 1 deletion Documentation/guides/events/first-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
The preferred way to receive events with the generic host is by implementing @NetCord.Hosting.Gateway.IGatewayEventHandler or @NetCord.Hosting.Gateway.IGatewayEventHandler`1.

First, use @NetCord.Hosting.Gateway.GatewayEventHandlerServiceCollectionExtensions.AddGatewayEventHandlers(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Reflection.Assembly) to add all event handlers in an assembly. You also need to call @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the handlers to the client.
[!code-cs[Program.cs](FirstEventsHosting/Program.cs?highlight=20,23)]
[!code-cs[Program.cs](FirstEventsHosting/Program.cs?highlight=17,20)]

> [!NOTE]
> All gateway event handlers require @NetCord.Hosting.Gateway.GatewayEventAttribute that specifies the event to bind to. The event argument must match the type of the handler, otherwise an exception will be thrown.
Expand Down
2 changes: 1 addition & 1 deletion Documentation/guides/events/intents.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Intents in NetCord are handled by @NetCord.Gateway.GatewayIntents.
You specify them like this:

## [Generic Host](#tab/generic-host)
[!code-cs[Program.cs](IntentsHosting/Program.cs?highlight=6#L8-L15)]
[!code-cs[Program.cs](IntentsHosting/Program.cs?highlight=4#L8-L12)]

## [Bare Bones](#tab/bare-bones)
[!code-cs[Program.cs](Intents/Program.cs?highlight=3#L4-L7)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

builder.Services
.AddSingleton<IDataProvider, DataProvider>()
.AddDiscordGateway(o => o.Configuration = new() { Intents = GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.MessageContent })
.AddDiscordGateway(o => o.Intents = GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.MessageContent)
.AddCommands<CommandContext>()
.AddApplicationCommands<SlashCommandInteraction, SlashCommandContext>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
builder.Services
.AddApplicationCommands<SlashCommandInteraction, SlashCommandContext>(options =>
{
options.Configuration = ApplicationCommandServiceConfiguration<SlashCommandContext>.Default with
{
LocalizationsProvider = new JsonLocalizationsProvider(),
};
options.LocalizationsProvider = new JsonLocalizationsProvider();
})
.AddDiscordGateway();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ To localize application commands, you need to use @NetCord.Services.ApplicationC
The samples below show how to specify the @NetCord.Services.ApplicationCommands.JsonLocalizationsProvider.

## [Generic Host](#tab/generic-host)
[!code-cs[Program.cs](LocalizationsHosting/Program.cs?highlight=6#L11-L18)]
[!code-cs[Program.cs](LocalizationsHosting/Program.cs?highlight=4#L11-L15)]

## [Bare Bones](#tab/bare-bones)
[!code-cs[Program.cs](Localizations/Program.cs?highlight=3#L12-L15)]
Expand Down
8 changes: 6 additions & 2 deletions Hosting/NetCord.Hosting.AspNetCore/RoutePatternHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ public static RoutePattern ParseLiteral(string pattern)
{
var trimmedPattern = TrimPrefix(pattern);

var routePattern = RoutePatternFactory.Pattern(pattern, trimmedPattern.Length == 0 ? [] : trimmedPattern.Split('/').Select(p => RoutePatternFactory.Segment(RoutePatternFactory.LiteralPart(p))));
return routePattern;
var segments = trimmedPattern.Length is 0
? []
: trimmedPattern.Split('/')
.Select(p => RoutePatternFactory.Segment(RoutePatternFactory.LiteralPart(p)));

return RoutePatternFactory.Pattern(pattern, segments);
}

private static string TrimPrefix(string routePattern)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ApplicationCommandInteractionHandler(IServiceProvider services,

var optionsValue = options.Value;

if (optionsValue.UseScopes)
if (optionsValue.UseScopes.GetValueOrDefault(true))
{
_scopeFactory = services.GetService<IServiceScopeFactory>() ?? throw new InvalidOperationException($"'{nameof(IServiceScopeFactory)}' is not registered in the '{nameof(IServiceProvider)}', but it is required for using scopes.");
_handleAsync = &HandleInteractionWithScopeAsync;
Expand All @@ -43,7 +43,7 @@ public ApplicationCommandInteractionHandler(IServiceProvider services,
_handleAsync = &HandleInteractionAsync;

_createContext = optionsValue.CreateContext ?? ContextHelper.CreateContextDelegate<TInteraction, GatewayClient?, TContext>();
_resultHandler = optionsValue.ResultHandler;
_resultHandler = optionsValue.ResultHandler ?? new ApplicationCommandResultHandler<TContext>();
_client = client;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,56 @@ namespace NetCord.Hosting.Services.ApplicationCommands;

public static class ApplicationCommandServiceHostBuilderExtensions
{
// Configure

public static IHostBuilder ConfigureApplicationCommands(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions> configureOptions)
{
return builder.ConfigureApplicationCommands((options, _) => configureOptions(options));
}

public static IHostBuilder ConfigureApplicationCommands(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions, IServiceProvider> configureOptions)
{
return builder.ConfigureServices((context, services) => services.ConfigureApplicationCommands(configureOptions));
}

public static IHostBuilder ConfigureApplicationCommands<TInteraction, TContext>(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions<TInteraction, TContext>> configureOptions)
where TInteraction : ApplicationCommandInteraction
where TContext : IApplicationCommandContext
{
return builder.ConfigureApplicationCommands<TInteraction, TContext>((options, _) => configureOptions(options));
}

public static IHostBuilder ConfigureApplicationCommands<TInteraction, TContext>(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions<TInteraction, TContext>, IServiceProvider> configureOptions)
where TInteraction : ApplicationCommandInteraction
where TContext : IApplicationCommandContext
{
return builder.ConfigureServices((context, services) => services.ConfigureApplicationCommands(configureOptions));
}

public static IHostBuilder ConfigureApplicationCommands<TInteraction, TContext, TAutocompleteContext>(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions<TInteraction, TContext, TAutocompleteContext>> configureOptions)
where TInteraction : ApplicationCommandInteraction
where TContext : IApplicationCommandContext
where TAutocompleteContext : IAutocompleteInteractionContext
{
return builder.ConfigureApplicationCommands<TInteraction, TContext, TAutocompleteContext>((options, _) => configureOptions(options));
}

public static IHostBuilder ConfigureApplicationCommands<TInteraction, TContext, TAutocompleteContext>(this IHostBuilder builder,
Action<ApplicationCommandServiceOptions<TInteraction, TContext, TAutocompleteContext>, IServiceProvider> configureOptions)
where TInteraction : ApplicationCommandInteraction
where TContext : IApplicationCommandContext
where TAutocompleteContext : IAutocompleteInteractionContext
{
return builder.ConfigureServices((context, services) => services.ConfigureApplicationCommands(configureOptions));
}

// Use

public static IHostBuilder UseApplicationCommands<TInteraction, TContext>(this IHostBuilder builder)
where TInteraction : ApplicationCommandInteraction
where TContext : IApplicationCommandContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,114 @@
using NetCord.Gateway;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;

using Microsoft.Extensions.Options;

using NetCord.Gateway;
using NetCord.Services;
using NetCord.Services.ApplicationCommands;

namespace NetCord.Hosting.Services.ApplicationCommands;

public class ApplicationCommandServiceOptions
{
public bool? DefaultDMPermission { get; set; }

public IEnumerable<ApplicationIntegrationType>? DefaultIntegrationTypes { get; set; }

public IEnumerable<InteractionContextType>? DefaultContexts { get; set; }

/// <summary>
/// {0} - parameter name
/// </summary>
[StringSyntax(StringSyntaxAttribute.CompositeFormat)]
public string? DefaultParameterDescriptionFormat { get; set; }

public ILocalizationsProvider? LocalizationsProvider { get; set; }

public bool? UseScopes { get; set; }
}

public class ApplicationCommandServiceOptions<TInteraction, TContext> where TInteraction : ApplicationCommandInteraction where TContext : IApplicationCommandContext
{
public ApplicationCommandServiceConfiguration<TContext> Configuration { get; set; } = ApplicationCommandServiceConfiguration<TContext>.Default;
public Dictionary<Type, SlashCommandTypeReader<TContext>> TypeReaders { get; set; } = ApplicationCommandServiceConfiguration<TContext>.Default.TypeReaders.ToDictionary();

public SlashCommandTypeReader<TContext>? EnumTypeReader { get; set; }

public bool? DefaultDMPermission { get; set; }

public IEnumerable<ApplicationIntegrationType>? DefaultIntegrationTypes { get; set; }

public IEnumerable<InteractionContextType>? DefaultContexts { get; set; }

public bool UseScopes { get; set; } = true;
public ISlashCommandParameterNameProcessor<TContext>? ParameterNameProcessor { get; set; }

/// <summary>
/// {0} - parameter name
/// </summary>
[StringSyntax(StringSyntaxAttribute.CompositeFormat)]
public string? DefaultParameterDescriptionFormat { get; set; }

public IResultResolverProvider<TContext>? ResultResolverProvider { get; set; }

public ILocalizationsProvider? LocalizationsProvider { get; set; }

public bool? UseScopes { get; set; }

public Func<TInteraction, GatewayClient?, IServiceProvider, TContext>? CreateContext { get; set; }

public IApplicationCommandResultHandler<TContext> ResultHandler { get; set; } = new ApplicationCommandResultHandler<TContext>();
public IApplicationCommandResultHandler<TContext>? ResultHandler { get; set; }

internal void Apply(IOptions<ApplicationCommandServiceOptions> options)
{
var value = options.Value;

var defaultDMPermission = value.DefaultDMPermission;
if (defaultDMPermission.HasValue)
DefaultDMPermission = defaultDMPermission.GetValueOrDefault();

var defaultIntegrationTypes = value.DefaultIntegrationTypes;
if (defaultIntegrationTypes is not null)
DefaultIntegrationTypes = defaultIntegrationTypes;

var defaultContexts = value.DefaultContexts;
if (defaultContexts is not null)
DefaultContexts = defaultContexts;

var defaultParameterDescriptionFormat = value.DefaultParameterDescriptionFormat;
if (defaultParameterDescriptionFormat is not null)
DefaultParameterDescriptionFormat = defaultParameterDescriptionFormat;

var localizationsProvider = value.LocalizationsProvider;
if (localizationsProvider is not null)
LocalizationsProvider = localizationsProvider;

var useScopes = value.UseScopes;
if (useScopes.HasValue)
UseScopes = useScopes;
}

internal ApplicationCommandServiceConfiguration<TContext> CreateConfiguration()
{
var configuration = ApplicationCommandServiceConfiguration<TContext>.Default;

return configuration with
{
TypeReaders = TypeReaders.ToImmutableDictionary(),
EnumTypeReader = EnumTypeReader ?? configuration.EnumTypeReader,
DefaultDMPermission = DefaultDMPermission ?? configuration.DefaultDMPermission,
DefaultIntegrationTypes = DefaultIntegrationTypes ?? configuration.DefaultIntegrationTypes,
DefaultContexts = DefaultContexts ?? configuration.DefaultContexts,
ParameterNameProcessor = ParameterNameProcessor ?? configuration.ParameterNameProcessor,
DefaultParameterDescriptionFormat = DefaultParameterDescriptionFormat ?? configuration.DefaultParameterDescriptionFormat,
ResultResolverProvider = ResultResolverProvider ?? configuration.ResultResolverProvider,
LocalizationsProvider = LocalizationsProvider ?? configuration.LocalizationsProvider,
};
}
}

public class ApplicationCommandServiceOptions<TInteraction, TContext, TAutocompleteContext> : ApplicationCommandServiceOptions<TInteraction, TContext> where TInteraction : ApplicationCommandInteraction where TContext : IApplicationCommandContext where TAutocompleteContext : IAutocompleteInteractionContext
{
public Func<AutocompleteInteraction, GatewayClient?, IServiceProvider, TAutocompleteContext>? CreateAutocompleteContext { get; set; }

public IAutocompleteInteractionResultHandler<TAutocompleteContext> AutocompleteResultHandler { get; set; } = new AutocompleteInteractionResultHandler<TAutocompleteContext>();
public IAutocompleteInteractionResultHandler<TAutocompleteContext>? AutocompleteResultHandler { get; set; }
}
Loading

0 comments on commit b90816f

Please sign in to comment.