diff --git a/.gitignore b/.gitignore index 44c3b430e..060c1385e 100644 --- a/.gitignore +++ b/.gitignore @@ -364,4 +364,8 @@ FodyWeavers.xsd # Settings file appsettings.json -!Documentation/**/appsettings.json \ No newline at end of file +!Documentation/**/appsettings.json +!ProjectTemplates/**/appsettings.json + +# Rider settings folder +.idea/ \ No newline at end of file diff --git a/NetCord.slnx b/NetCord.slnx index c9a17d42b..2af6b471e 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -93,4 +93,5 @@ + diff --git a/ProjectTemplates/NetCordTemplates.csproj b/ProjectTemplates/NetCordTemplates.csproj new file mode 100644 index 000000000..73a2b1eee --- /dev/null +++ b/ProjectTemplates/NetCordTemplates.csproj @@ -0,0 +1,29 @@ + + + + Template + NetCord.Templates + + $(NoWarn);NU5128 + + true + false + content + + + + false + + + + NetCord Project Templates + NetCord + Starting templates to use with NetCord - The modern and fully customizable C# Discord library. + + + + + + + + \ No newline at end of file diff --git a/ProjectTemplates/Templates.md b/ProjectTemplates/Templates.md new file mode 100644 index 000000000..2ef250b98 --- /dev/null +++ b/ProjectTemplates/Templates.md @@ -0,0 +1,62 @@ +### Convenient TL;DR one-liner + +> [!WARNING] +> TODO: Add actual command to install from nuget + +Minimal template +```bash +dotnet new install NetCord.Templates && \ +dotnet new netcord --name MyNewBot --framework net9.0 +``` +Or if you want complete template with all features +```bash +dotnet new install NetCord.Templates && \ +dotnet new netcord --add-text-commands --add-application-commands --add-component-interactions --name MyNewBot --framework net9.0 +``` + + +> [!IMPORTANT] +> In case of local testing, +> all commands should be run from the root of the repository. + +### Packing +```bash +dotnet pack ./ProjectTemplates/ -c Release -o ./nupkgs +``` + +### Installing +Nuget + +> [!WARNING] +> TODO: Add actual command to install from nuget + +```bash +dotnet new install NetCord.Templates +``` +Local +```bash +dotnet new install ./nupkgs/* +``` + +### Uninstalling +```bash +dotnet new uninstall NetCord.Templates +``` + +### Updating +```bash +dotnet new update +``` + +### Usage +`dotnet new netcord [options] [template options]` +Use `dotnet new netcord --help` to see all available options. +Example command: +```bash +dotnet new netcord \ + --add-text-commands \ + --add-application-commands \ + --add-component-interactions \ + --name MyNewBot \ + --framework net9.0 +``` \ No newline at end of file diff --git a/ProjectTemplates/templates/NetCord.GenericHost/.template.config/dotnetcli.host.json b/ProjectTemplates/templates/NetCord.GenericHost/.template.config/dotnetcli.host.json new file mode 100644 index 000000000..41a9c5a99 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/.template.config/dotnetcli.host.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "kestrelHttpPort": { + "isHidden": true + }, + "kestrelHttpsPort": { + "isHidden": true + }, + "framework": { + "longName": "framework" + }, + "skipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "excludeLaunchSettings": { + "longName": "exclude-launch-settings", + "shortName": "" + }, + "addApplicationCommands": { + "longName": "add-application-commands", + "shortName": "" + }, + "addTextCommands": { + "longName": "add-text-commands", + "shortName": "" + }, + "addComponentInteractions": { + "longName": "add-component-interactions", + "shortName": "" + } + }, + "usageExamples": [ + "--add-text-commands --add-interactions --add-application-commands --name MyNewBot --framework net9.0" + ] +} \ No newline at end of file diff --git a/ProjectTemplates/templates/NetCord.GenericHost/.template.config/template.json b/ProjectTemplates/templates/NetCord.GenericHost/.template.config/template.json new file mode 100644 index 000000000..feca32e28 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/.template.config/template.json @@ -0,0 +1,180 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "NetCord", + "classifications": [ + "Web", + "NetCord", + "AspNetCore" + ], + "identity": "NetCord.Hosting.AspNetCore", + "groupIdentity": "NetCord", + "name": "NetCord Project", + "defaultName": "NetCordBot", + "sourceName": "NetCord.Template.Bot", + "shortName": "netcord", + "description": "NetCord Bot with module-based approach", + "preferNameDirectory": true, + "tags": { + "language": "C#", + "type": "project" + }, + "primaryOutputs": [ + { + "path": "NetCord.Template.Bot.csproj" + } + ], + "sources": [ + { + "source": "./", + "target": "./", + "exclude": [ + ".template.config/**", + "**/*.idea/**" + ], + "modifiers": [ + { + "condition": "(excludeLaunchSettings)", + "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!addApplicationCommands)", + "exclude": [ + "SlashCommands/HelloSlashModule.cs" + ] + }, + { + "condition": "(!addTextCommands)", + "exclude": [ + "TextCommands/HelloTextModule.cs" + ] + }, + { + "condition": "(!addComponentInteractions)", + "exclude": [ + "Interactions/ButtonInteractionModule.cs", + "SlashCommands/ButtonSlashModule.cs" + ] + } + ] + } + ], + "symbols": { + "framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net8.0", + "description": "Targets .NET 8" + }, + { + "choice": "net9.0", + "description": "Targets .NET 9" + } + ], + "defaultValue": "net8.0", + "replaces": "net8.0" + }, + "addApplicationCommands": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Add Application Commands", + "description": "Whether to add application commands with example module", + "isRequired": false + }, + "addTextCommands": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Add Text Commands", + "description": "Whether to add text commands with example module", + "isRequired": false + }, + "addComponentInteractions": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Add Interactions", + "description": "Whether to add interactions with example module", + "isRequired": false + }, + "hostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "Whether to skip automatic restore of the project on create.", + "defaultValue": "false", + "displayName": "Skip restore", + "isRequired": false + }, + "excludeLaunchSettings": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to exclude launchSettings.json from the generated template." + }, + "kestrelHttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json." + }, + "kestrelHttpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 5000, + "high": 5300 + } + }, + "kestrelHttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "kestrelHttpPort", + "fallbackVariableName": "kestrelHttpPortGenerated" + }, + "replaces": "5500" + }, + "kestrelHttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if Individual auth is used)." + }, + "kestrelHttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 7000, + "high": 7300 + } + }, + "kestrelHttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "kestrelHttpsPort", + "fallbackVariableName": "kestrelHttpsPortGenerated" + }, + "replaces": "5501" + } + }, + "postActions": [ + { + "id": "restore", + "condition": "(!skipRestore)", + "description": "Restore NuGet packages.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } + ] +} \ No newline at end of file diff --git a/ProjectTemplates/templates/NetCord.GenericHost/Interactions/ButtonInteractionModule.cs b/ProjectTemplates/templates/NetCord.GenericHost/Interactions/ButtonInteractionModule.cs new file mode 100644 index 000000000..deb9a6724 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/Interactions/ButtonInteractionModule.cs @@ -0,0 +1,46 @@ +using NetCord.Rest; +using NetCord.Services.ComponentInteractions; + +namespace NetCord.Template.Bot.Interactions; + +/// +/// Example Button interaction module. +/// To learn more about interactions, check out docs. +/// +/// Logger injected via DI +public class ButtonModule(ILogger logger) : ComponentInteractionModule +{ + /// + /// Replies with deferred message to the button click. + /// Don't forget that CustomId must match the one in the button. + /// + /// + [ComponentInteraction("pong-interaction")] + public async Task PingButton() + { + logger.LogInformation("Received {Interaction} from {User}", Context.Interaction.Data.CustomId, Context.User.Username); + var received = DateTime.Now; + var callback = InteractionCallback.DeferredModifyMessage; + await Context.Interaction.SendResponseAsync(callback).ConfigureAwait(false); + + await Task.Delay(10 * 1_000).ConfigureAwait(true); // Simulate some work + + await Context.Interaction.ModifyResponseAsync(m => + { + m.Components = + [ + new ComponentContainerProperties + { + AccentColor = new Color(0, 255, 0), + Components = + [ + new TextDisplayProperties("# Pong!"), + new TextDisplayProperties($"I've received your interaction at `{received:T}` and replied at `{DateTime.Now:T}`"), + new TextDisplayProperties("-# This was example of deferred component interaction response"), + new TextDisplayProperties("-# Which also used [ComponentsV2](https://discord.com/developers/docs/components/overview) over regular embeds."), + ] + } + ]; + }).ConfigureAwait(false); + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/NetCord.Template.Bot.csproj b/ProjectTemplates/templates/NetCord.GenericHost/NetCord.Template.Bot.csproj new file mode 100644 index 000000000..90bb7ecdf --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/NetCord.Template.Bot.csproj @@ -0,0 +1,26 @@ + + + + + net9.0 + + net8.0 + + enable + enable + + + + + + + + + + + + + + + + diff --git a/ProjectTemplates/templates/NetCord.GenericHost/Program.cs b/ProjectTemplates/templates/NetCord.GenericHost/Program.cs new file mode 100644 index 000000000..12177307b --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/Program.cs @@ -0,0 +1,54 @@ +using NetCord.Gateway; +using NetCord.Hosting.Gateway; +using NetCord.Hosting.Services; +#if (addApplicationCommands) +using NetCord.Hosting.Services.ApplicationCommands; +#endif +#if (addTextCommands) +using NetCord.Hosting.Services.Commands; +#endif +#if (addComponentInteractions) +using NetCord.Hosting.Services.ComponentInteractions; +#endif +#if (addComponentInteractions) +using NetCord.Services.ComponentInteractions; +#endif + +namespace NetCord.Template.Bot; + +public static class Program +{ + public static async Task Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + builder.Services + // Use intents suitable for your needs. + // See: https://netcord.dev/guides/events/intents.html?tabs=generic-host + .AddDiscordGateway(op => op.Intents = GatewayIntents.All) +#if (addTextCommands) + .AddApplicationCommands() +#endif +#if (addTextCommands) + .AddCommands(options => options.IgnoreCase = true) // IgnoreCase makes commands case-insensitive. "!hello" and "!Hello" will be treated the same. +#endif +#if (addComponentInteractions) + .AddComponentInteractions() + .AddComponentInteractions() + .AddComponentInteractions() + .AddComponentInteractions() + .AddComponentInteractions() + .AddComponentInteractions() + .AddComponentInteractions() +#endif + .AddGatewayEventHandlers(typeof(Program).Assembly); + + var host = builder + .Build() + // Adds application commands, text commands and interactions from the assembly. + // All "Modules" must be public in order to be discovered and registered properly. + .AddModules(typeof(Program).Assembly) + .UseGatewayEventHandlers(); + + await host.RunAsync().ConfigureAwait(false); + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/Properties/launchSettings.json b/ProjectTemplates/templates/NetCord.GenericHost/Properties/launchSettings.json new file mode 100644 index 000000000..aa7b214f6 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5500", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5501;http://localhost:5500", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/ButtonSlashModule.cs b/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/ButtonSlashModule.cs new file mode 100644 index 000000000..7f3d73484 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/ButtonSlashModule.cs @@ -0,0 +1,45 @@ +using NetCord.Rest; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Template.Bot.SlashCommands; + +/// +/// Example App (Slash) command module with buttons and componentsV2. +/// To learn more about slash commands, check out docs. +/// +/// Logger injected via DI +[RequireBotPermissions(Permissions.SendMessages)] +public class ButtonSlashModule(ILogger logger) : ApplicationCommandModule +{ + [SlashCommand(name: "generate-button", description: "Generates message with button", DefaultGuildUserPermissions = Permissions.UseApplicationCommands)] + public InteractionMessageProperties GenerateButton() + { + logger.LogInformation("Received {Command} slash command from {User}", Context.Interaction.Data.Name, Context.User.Username); + return new InteractionMessageProperties + { + Components = + [ + new ComponentContainerProperties + { + AccentColor = new Color(255, 0, 0), + Spoiler = false, + Components = + [ + new ComponentSectionProperties(new ComponentSectionThumbnailProperties(new ComponentMediaProperties("https://netcord.dev/images/SmallSquare.png"))) + { + new TextDisplayProperties($"<@{Context.Interaction.User.Id}> Click the button to trigger HTTP Interaction"), new TextDisplayProperties("-# Or don't. It's up to you 🤖"), + }, + new ComponentSeparatorProperties { Divider = true, Spacing = ComponentSeparatorSpacingSize.Small }, + new ActionRowProperties + { + new LinkButtonProperties("https://github.com/NetCordDev/NetCord", "Check out NetCord!"), new ButtonProperties("pong-interaction", "Click for pong!", ButtonStyle.Primary) + } + ] + } + ], + Flags = MessageFlags.IsComponentsV2, + AllowedMentions = AllowedMentionsProperties.All + }; + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/HelloSlashModule.cs b/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/HelloSlashModule.cs new file mode 100644 index 000000000..1be94b950 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/SlashCommands/HelloSlashModule.cs @@ -0,0 +1,23 @@ +using NetCord.Rest; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Template.Bot.SlashCommands; + +/// +/// Example App (Slash) command module. +/// Usable only within guilds where bot has '' permission. +/// To learn more about slash commands, check out docs. +/// +/// Logger injected via DI +[RequireBotPermissions(Permissions.SendMessages)] +[RequireContext(RequiredContext.Guild)] +public class HelloSlashModule(ILogger logger) : ApplicationCommandModule +{ + [SlashCommand(name: "hello", description: "Hello, NetCord!", DefaultGuildUserPermissions = Permissions.UseApplicationCommands)] + public InteractionMessageProperties Hello() + { + logger.LogInformation("Received {Command} slash command from {User}", Context.Interaction.Data.Name, Context.User.Username); + return new InteractionMessageProperties { Content = $"<@{Context.User.Id}> Hello from NetCord Slash commands!", AllowedMentions = AllowedMentionsProperties.All }; + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/TextCommands/HelloTextModule.cs b/ProjectTemplates/templates/NetCord.GenericHost/TextCommands/HelloTextModule.cs new file mode 100644 index 000000000..998e154af --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/TextCommands/HelloTextModule.cs @@ -0,0 +1,18 @@ +using NetCord.Services.Commands; + +namespace NetCord.Template.Bot.TextCommands; + +/// +/// Example Text command module. +/// To learn more about text commands, check out docs. +/// +/// Logger injected via DI +public class HelloTextModule(ILogger logger) : CommandModule +{ + [Command("hello")] + public string Hello() + { + logger.LogInformation("Received hello command from {User}", Context.User.Username); + return "Hello from NetCord text commands!"; + } +} diff --git a/ProjectTemplates/templates/NetCord.GenericHost/appsettings.json b/ProjectTemplates/templates/NetCord.GenericHost/appsettings.json new file mode 100644 index 000000000..9e1d75de9 --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Discord": { + "Token": "Your_Discord_Token_Here", + "Prefixes": [ + "Your_First_Prefix_Here (eg. !)" + ] + } +} \ No newline at end of file diff --git a/ProjectTemplates/templates/NetCord.GenericHost/example.appsettings.jsonc b/ProjectTemplates/templates/NetCord.GenericHost/example.appsettings.jsonc new file mode 100644 index 000000000..b18d6c8db --- /dev/null +++ b/ProjectTemplates/templates/NetCord.GenericHost/example.appsettings.jsonc @@ -0,0 +1,20 @@ +// Example "appsettings.json" for a NetCord Generic Host project. +// All changes should be made in the "appsettings.json" file, or it's corresponding environment file. +// This file can be removed. It's purpose is purely to show the user what the default settings are. +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Discord": { + // See: https://netcord.dev/guides/getting-started/making-a-bot.html?tabs=generic-host + "Token": "Your_Discord_Token_Here", + // See https://netcord.dev/guides/services/text-commands/introduction.html?tabs=generic-host#specifying-a-prefix + "Prefixes": [ + "Your_First_Prefix_Here (eg. !)", + "Your_Second_Another_Prefix_Here (eg. ?)" + ] + } +} \ No newline at end of file