init commit

This commit is contained in:
Garret Patti
2026-07-04 20:43:38 -04:00
commit 3064e93011
12 changed files with 607 additions and 0 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
# Copy this file to .env and set your real values.
DISCORD_BOT_TOKEN=YOUR_DISCORD_BOT_TOKEN_HERE

45
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,45 @@
# TentacleBot - Discord .NET Bot
A Discord bot project built with Discord.Net and .NET 8+, featuring dependency injection and slash commands.
## Setup Instructions
1. Verify .NET SDK is installed (version 8.0 or higher)
2. Install project dependencies: `dotnet restore`
3. Configure bot token in `appsettings.json`
4. Build the project: `dotnet build`
5. Run the bot: `dotnet run`
## Project Structure
- **Program.cs**: Bot initialization and dependency injection setup
- **Services/BotService.cs**: Core bot service managing connection and event handlers
- **Commands/GeneralCommands.cs**: Example slash command module
- **appsettings.json**: Configuration file for bot token and prefix
## Key Features
- Dependency injection for services
- Slash command support
- Event handling for bot lifecycle
- Discord.Net integration
## Configuration
Before running, update `appsettings.json` with your Discord bot token:
```json
{
"Discord": {
"BotToken": "YOUR_BOT_TOKEN_HERE"
}
}
```
## Running the Bot
```bash
dotnet run
```
The bot will connect to Discord and respond to slash commands.

50
.gitignore vendored Normal file
View File

@@ -0,0 +1,50 @@
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio cache/options
.vs/
# Visual Studio Code
.vscode/settings.json
.vscode/launch.json
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# Configuration files with sensitive data
appsettings.json
!appsettings.*.json
# NuGet
.nuget/
*.nupkg
*.snupkg
# dotnet
project.assets.json
project.lock.json
# secrets
.env
# OS generated files
*.swp
*.swo
*.DS_Store

44
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"--configuration",
"Debug"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "run",
"command": "dotnet",
"type": "shell",
"args": [
"run",
"--no-build"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "test",
"isDefault": true
},
"isBackground": false
},
{
"label": "clean",
"command": "dotnet",
"type": "shell",
"args": [
"clean"
],
"problemMatcher": "$msCompile"
}
]
}

32
AGENTS.md Normal file
View File

@@ -0,0 +1,32 @@
# TentacleBot - Agent Instructions
## Quick Commands
```bash
dotnet restore && dotnet run # Run the bot locally
docker compose up --build -d # Run with Docker
docker compose logs -f # View Docker logs
```
## Setup
1. Set `DISCORD_BOT_TOKEN` env var (preferred) or edit `appsettings.json`
2. Copy `.env.example` to `.env` for Docker: `cp .env.example .env`
3. `appsettings.json` is gitignored — never commit it
## Architecture
- **`Program.cs`** — Entry point, DI setup, registers command modules from the assembly
- **`Services/BotService.cs`** — Core bot lifecycle (login, ready handler, interaction routing)
- **`Commands/GeneralCommands.cs`** — Slash commands (`/general ping`, `/general hello`, `/botinfo`)
Add new commands by creating a class in `Commands/` that extends `InteractionModuleBase<SocketInteractionContext>`. Commands are auto-discovered via `AddModulesAsync(typeof(Program).Assembly)`.
## Gotchas
- **Two `DiscordSocketClient` instances** are created in `Program.cs` — one for the bot client and one passed into `InteractionService`. They are separate instances sharing the same config.
- **Token priority**: `DISCORD_BOT_TOKEN` env var is checked first, then `appsettings.json``Discord:BotToken`.
## No tests, no lint config
This project has no test framework, no linting, and no CI. `dotnet build` is the only verification step.

View File

@@ -0,0 +1,71 @@
using Discord;
using Discord.Interactions;
namespace TentacleBot.Commands;
[Group("general", "General commands")]
public class GeneralCommands : InteractionModuleBase<SocketInteractionContext>
{
/// <summary>
/// Example ping command that responds with pong
/// </summary>
[SlashCommand("ping", "Responds with pong and latency")]
public async Task HandlePingAsync()
{
await DeferAsync();
var latency = Context.Client.Latency;
var embed = new EmbedBuilder()
.WithTitle("Pong!")
.WithDescription($"Latency: {latency}ms")
.WithColor(Color.Green)
.WithTimestamp(DateTimeOffset.UtcNow)
.Build();
await FollowupAsync(embed: embed);
}
/// <summary>
/// Example hello command with a user parameter
/// </summary>
[SlashCommand("hello", "Greet a user")]
public async Task HandleHelloAsync(
[Summary("user", "The user to greet")] IUser? user = null)
{
await DeferAsync();
var targetUser = user ?? Context.User;
var embed = new EmbedBuilder()
.WithTitle($"Hello, {targetUser.Username}!")
.WithDescription($"Nice to meet you! ?")
.WithThumbnailUrl(targetUser.GetAvatarUrl() ?? targetUser.GetDefaultAvatarUrl())
.WithColor(Color.Blue)
.WithTimestamp(DateTimeOffset.UtcNow)
.Build();
await FollowupAsync(embed: embed);
}
/// <summary>
/// Example command that shows bot information
/// </summary>
[SlashCommand("botinfo", "Display bot information")]
public async Task HandleBotInfoAsync()
{
await DeferAsync();
var bot = Context.Client.CurrentUser;
var embed = new EmbedBuilder()
.WithTitle("Bot Information")
.AddField("Name", bot?.Username, true)
.AddField("ID", bot?.Id, true)
.AddField("Created", bot?.CreatedAt.ToString("g"), true)
.AddField("Guilds", Context.Client.Guilds.Count, true)
.WithColor(Color.Purple)
.WithThumbnailUrl(bot?.GetAvatarUrl())
.WithTimestamp(DateTimeOffset.UtcNow)
.Build();
await FollowupAsync(embed: embed);
}
}

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY TentacleBot.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish ./
ENV DOTNET_RUNNING_IN_CONTAINER=true
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
ENTRYPOINT ["dotnet", "TentacleBot.dll"]

50
Program.cs Normal file
View File

@@ -0,0 +1,50 @@
using Discord;
using Discord.WebSocket;
using Discord.Interactions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TentacleBot.Services;
using TentacleBot.Commands;
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var discordConfig = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.All,
LogLevel = LogSeverity.Info,
UseInteractionSnowflakeDate = false,
};
var client = new DiscordSocketClient(discordConfig);
var services = new ServiceCollection()
.AddLogging(builder => builder.AddConsole())
.AddSingleton<IConfiguration>(configuration)
.AddSingleton(client)
.AddSingleton(new InteractionService(client, new InteractionServiceConfig
{
LogLevel = LogSeverity.Info,
UseCompiledLambda = true,
}))
.AddSingleton<BotService>();
var serviceProvider = services.BuildServiceProvider();
var botService = serviceProvider.GetRequiredService<BotService>();
var interactionService = serviceProvider.GetRequiredService<InteractionService>();
// Register command modules
await interactionService.AddModulesAsync(
typeof(Program).Assembly,
serviceProvider);
// Initialize the bot
await botService.InitializeAsync();
// Keep the bot running
await Task.Delay(Timeout.Infinite);

157
README.md Normal file
View File

@@ -0,0 +1,157 @@
# TentacleBot - Discord .NET Bot
A Discord bot project built with Discord.Net and .NET 8+, featuring dependency injection and slash commands.
## Features
- ? Built with Discord.Net 3.14+
- ? Dependency Injection for services
- ? Slash commands support
- ? Rich embed messages
- ? Example commands included
- ? .NET 8 or higher
## Prerequisites
- .NET 8.0 or higher ([download here](https://dotnet.microsoft.com/download))
- A Discord Bot Token (create one at [Discord Developer Portal](https://discord.com/developers/applications))
## Quick Start
### 1. Clone and Setup
```bash
dotnet restore
```
### 2. Configure Your Bot Token
Edit `appsettings.json` and replace `YOUR_BOT_TOKEN_HERE` with your actual Discord bot token:
```json
{
"Discord": {
"BotToken": "YOUR_ACTUAL_TOKEN_HERE"
}
}
```
### 3. Run the Bot
```bash
dotnet run
```
The bot will connect to Discord and register its slash commands globally.
## Run With Docker Compose
1. Copy the example env file:
```bash
cp .env.example .env
```
2. Edit `.env` and set your Discord token:
```dotenv
DISCORD_BOT_TOKEN=YOUR_ACTUAL_TOKEN_HERE
```
3. Build and run with Docker Compose:
```bash
docker compose up --build -d
```
4. View logs:
```bash
docker compose logs -f
```
5. Stop the bot:
```bash
docker compose down
```
## Project Structure
```
TentacleBot/
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Program.cs # Entry point and DI setup
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Services/
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> BotService.cs # Core bot service
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Commands/
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> GeneralCommands.cs # Example slash commands
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> appsettings.json # Configuration
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Dockerfile # Container image definition
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> docker-compose.yml # Local compose setup
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .env.example # Environment variable template
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> TentacleBot.csproj # Project file
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> README.md # This file
```
## Example Commands
The bot includes example slash commands:
- `/general ping` - Shows bot latency
- `/general hello [user]` - Greet a user
- `/general botinfo` - Display bot information
## Adding New Commands
Create a new class in the `Commands` folder that inherits from `InteractionModuleBase<SocketInteractionContext>`:
```csharp
using Discord.Interactions;
namespace TentacleBot.Commands;
public class MyCommands : InteractionModuleBase<SocketInteractionContext>
{
[SlashCommand("mycommand", "My custom command")]
public async Task HandleMyCommandAsync()
{
await RespondAsync("Hello from my command!");
}
}
```
Commands are automatically discovered and registered when the bot starts.
## Configuration
### appsettings.json
```json
{
"Discord": {
"BotToken": "YOUR_BOT_TOKEN_HERE"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}
```
## Troubleshooting
- **"Bot token not found"**: Set `DISCORD_BOT_TOKEN` in `.env` (Docker) or in your shell. The app falls back to `appsettings.json` if the env var is not set
- **"Insufficient permissions"**: Ensure your bot has the necessary permissions in Discord Developer Portal
- **Commands not appearing**: The bot needs to connect first. Wait a few seconds after starting, then try `/` in Discord
## Resources
- [Discord.Net Documentation](https://discordnet.dev/)
- [Discord Developer Portal](https://discord.com/developers/applications)
- [.NET Documentation](https://docs.microsoft.com/dotnet)
## License
MIT

102
Services/BotService.cs Normal file
View File

@@ -0,0 +1,102 @@
using Discord;
using Discord.WebSocket;
using Discord.Interactions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
namespace TentacleBot.Services;
public class BotService
{
private readonly DiscordSocketClient _client;
private readonly InteractionService _interactionService;
private readonly ILogger<BotService> _logger;
private readonly IConfiguration _configuration;
private readonly IServiceProvider _serviceProvider;
public BotService(
DiscordSocketClient client,
InteractionService interactionService,
ILogger<BotService> logger,
IConfiguration configuration,
IServiceProvider serviceProvider)
{
_client = client;
_interactionService = interactionService;
_logger = logger;
_configuration = configuration;
_serviceProvider = serviceProvider;
}
public async Task InitializeAsync()
{
_client.Log += LogAsync;
_interactionService.Log += LogAsync;
_client.Ready += ClientReadyAsync;
_client.InteractionCreated += InteractionCreatedAsync;
var token = Environment.GetEnvironmentVariable("DISCORD_BOT_TOKEN");
if (string.IsNullOrWhiteSpace(token))
{
token = _configuration["Discord:BotToken"];
}
if (string.IsNullOrEmpty(token))
{
throw new InvalidOperationException("Discord bot token not found. Set DISCORD_BOT_TOKEN or Discord:BotToken in appsettings.json");
}
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
_logger.LogInformation("Bot service initialized");
}
private async Task ClientReadyAsync()
{
_logger.LogInformation("Bot connected as {BotName}#{Discriminator}", _client.CurrentUser?.Username, _client.CurrentUser?.Discriminator);
// Register slash commands
await _interactionService.RegisterCommandsGloballyAsync();
_logger.LogInformation("Slash commands registered");
}
private async Task InteractionCreatedAsync(SocketInteraction interaction)
{
try
{
var ctx = new SocketInteractionContext(_client, interaction);
await _interactionService.ExecuteCommandAsync(ctx, _serviceProvider);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling interaction");
if (interaction.Type == InteractionType.ApplicationCommand)
{
await interaction.GetOriginalResponseAsync().ContinueWith(async msg =>
{
if (msg.IsCompletedSuccessfully)
{
await interaction.FollowupAsync("An error occurred while executing the command.");
}
});
}
}
}
private static Task LogAsync(LogMessage message)
{
Console.WriteLine($"[{message.Severity}] {message.Source}: {message.Message}");
return Task.CompletedTask;
}
public async Task StopAsync()
{
await _client.StopAsync();
_client.Dispose();
_logger.LogInformation("Bot service stopped");
}
}

27
TentacleBot.csproj Normal file
View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>TentacleBot</RootNamespace>
<AssemblyName>TentacleBot</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.14.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

9
docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
tentaclebot:
build:
context: .
dockerfile: Dockerfile
container_name: tentaclebot
restart: unless-stopped
env_file:
- .env