Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 25 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# TheSexy6BotWorker
# DSharpPlusBot

A Discord bot built as a .NET 9.0 Worker Service that integrates multiple AI models (Google Gemini and X.AI Grok) with Microsoft Semantic Kernel for function calling capabilities. Features a modular bot architecture with engagement mode for autonomous conversation participation.

Expand Down Expand Up @@ -112,19 +112,17 @@ cd DSharpPlusBot
This project uses .NET User Secrets to store sensitive configuration. Set up your secrets with:

```bash
cd TheSexy6BotWorker

# Set Discord bot token
dotnet user-secrets set "DiscordToken" "your-discord-bot-token"
dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj set "DiscordToken" "your-discord-bot-token"

# Set Google AI Gemini API key
dotnet user-secrets set "GeminiKey" "your-gemini-api-key"
dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj set "GeminiKey" "your-gemini-api-key"

# Set X.AI Grok API key
dotnet user-secrets set "GrokKey" "your-grok-api-key"
dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj set "GrokKey" "your-grok-api-key"

# Set Perplexity API key
dotnet user-secrets set "PerplexityApiKey" "your-perplexity-api-key"
dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj set "PerplexityApiKey" "your-perplexity-api-key"
```

**User Secrets ID**: `dotnet-TheSexy6BotWorker-d23e68fa-7622-4b43-ac67-735c9cf191f4`
Expand All @@ -136,22 +134,23 @@ dotnet user-secrets set "PerplexityApiKey" "your-perplexity-api-key"
List your configured secrets:

```bash
dotnet user-secrets list
dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj list
```

### 4. Restore Dependencies

```bash
cd ..
dotnet restore
dotnet restore TheSexy6BotWorker.slnx
```

## Running Locally

For remote staging secret wiring and runbook steps, see `src/terraform/README.md`.

### Standard Mode (Production Commands)

```bash
dotnet run --project TheSexy6BotWorker/TheSexy6BotWorker.csproj
dotnet run --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj
```

In Discord:
Expand All @@ -169,14 +168,14 @@ In Discord:

### Local Development Mode (Test Commands)

Set the `LOCAL_DEV` environment variable to add "test-" prefix:
Set `DOTNET_ENVIRONMENT=Development` to add the `test-` command prefix:

```bash
# PowerShell
$env:LOCAL_DEV="true"; dotnet run --project TheSexy6BotWorker/TheSexy6BotWorker.csproj
$env:DOTNET_ENVIRONMENT="Development"; dotnet run --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj

# Bash/Linux
LOCAL_DEV=true dotnet run --project TheSexy6BotWorker/TheSexy6BotWorker.csproj
DOTNET_ENVIRONMENT=Development dotnet run --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj
```

Commands become: `test-gemini`, `test-grok`, `test-ping`
Expand Down Expand Up @@ -230,41 +229,15 @@ docker buildx build --platform linux/amd64 \
## Project Structure

```
TheSexy6BotWorker/
├── Commands/ # DSharpPlus command definitions
│ └── PingCommand.cs
├── Configuration/ # Bot configuration implementations
│ ├── BotConfigurationExtensions.cs
│ ├── GeminiBotConfiguration.cs
│ └── GrokBotConfiguration.cs
├── Contracts/ # Interfaces
│ ├── IBotConfiguration.cs
│ └── IConversationSessionManager.cs
├── DTOs/ # Data transfer objects
├── Handlers/ # Discord event handlers
│ └── MessageCreatedHandler.cs
├── Markdown/ # Fluent markdown generation library
│ ├── MarkdownBuilder.cs
│ ├── MarkdownGenerator.cs # ObjectMarkdownBuilder<T>
│ └── *Attribute.cs # MarkdownProperty, MarkdownIgnore, etc.
├── Models/ # Domain models
│ ├── BotConfigurationModel.cs
│ ├── ConversationSession.cs
│ └── EngagementDecision.cs # Structured output for engagement mode
├── Services/ # Application services
│ ├── BotRegistry.cs # Bot lookup by prefix
│ ├── ConversationSessionManager.cs
│ ├── DynamicStatusService.cs
│ ├── PerplexitySearchService.cs
│ └── WeatherService.cs
├── Program.cs
└── DiscordWorker.cs

TheSexy6BotWorker.Tests/ # xUnit test project
├── Configuration/
├── Integration/
├── Markdown/
└── Services/
DSharpPlusBot/
├── src/
│ ├── dotnet/
│ │ ├── TheSexy6BotWorker/ # Main worker project
│ │ └── TheSexy6BotWorker.Tests/ # xUnit test project
│ └── terraform/ # Azure infra + secret contract
├── Dockerfile
├── TheSexy6BotWorker.slnx
└── README.md
```

## Core Components
Expand Down Expand Up @@ -308,7 +281,7 @@ var md = new ObjectMarkdownBuilder<Config>(config)
| `GeminiKey` | Google AI Gemini API key | Yes |
| `GrokKey` | X.AI Grok API key | Yes |
| `PerplexityApiKey` | Perplexity API key | Yes |
| `LOCAL_DEV` | Enable test command prefixes | No |
| `DOTNET_ENVIRONMENT` | Set to `Development` to enable test command prefixes | No |

## Key Dependencies

Expand All @@ -320,7 +293,7 @@ var md = new ObjectMarkdownBuilder<Config>(config)
## Troubleshooting

### Bot Not Responding
- Verify all user secrets are set: `dotnet user-secrets list`
- Verify all user secrets are set: `dotnet user-secrets --project src/dotnet/TheSexy6BotWorker/TheSexy6BotWorker.csproj list`
- Check Discord bot has Message Content intent enabled
- Ensure bot has appropriate permissions in your Discord server

Expand All @@ -344,4 +317,4 @@ var md = new ObjectMarkdownBuilder<Config>(config)

## Contributing

[Your Contributing Guidelines Here]
[Your Contributing Guidelines Here]
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,16 @@ public void GenerateConfigurationDescription_HandlesComplexSettings()
Assert.Contains("0.7", description);
}

[Theory]
[InlineData("")]
[InlineData("test-")]
[InlineData("dev-")]
public void GenerateConfigurationDescription_IncludesEnvironmentPrefix(string prefix)
[Fact]
public void GenerateConfigurationDescription_IncludesCanonicalPrefix()
{
// Arrange
var config = new GeminiBotConfiguration(prefix);
var config = new GeminiBotConfiguration();

// Act
var description = config.GenerateConfigurationDescription();

// Assert
Assert.Contains($"{prefix}gemini", description);
Assert.Contains("gemini", description);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ public void Registry_WithMultipleBots_CanDistinguishByPrefix()
}

[Fact]
public void Registry_WithEnvironmentPrefixes_HandlesCollisions()
public void Registry_WithDuplicateCanonicalPrefixes_HandlesCollisions()
{
// Arrange
var registry = new BotRegistry();
registry.Register(new GeminiBotConfiguration("test-"));
registry.Register(new GeminiBotConfiguration());

// Act - trying to register same bot with same prefix should throw
var exception = Assert.Throws<InvalidOperationException>(() =>
registry.Register(new GeminiBotConfiguration("test-")));
registry.Register(new GeminiBotConfiguration()));

// Assert
Assert.Contains("already registered", exception.Message);
Expand Down Expand Up @@ -92,14 +92,12 @@ public void BotSettings_CanBeModifiedAtRuntime()
[InlineData("gemini what is AI?", "gemini", "what is AI?")]
[InlineData("grok tell me a joke", "grok", "tell me a joke")]
[InlineData("GEMINI case test", "gemini", "case test")]
[InlineData("test-grok hello", "test-grok", "hello")]
public void Registry_MessageParsing_HandlesVariousFormats(string message, string expectedPrefix, string expectedStripped)
{
// Arrange
var registry = new BotRegistry();
registry.Register(new GeminiBotConfiguration());
registry.Register(new GrokBotConfiguration());
registry.Register(new GrokBotConfiguration("test-"));

// Act
var found = registry.TryGetBot(message, out var bot, out var strippedMessage);
Expand All @@ -110,6 +108,23 @@ public void Registry_MessageParsing_HandlesVariousFormats(string message, string
Assert.Equal(expectedStripped, strippedMessage);
}

[Fact]
public void Registry_MessageParsing_WithMessagePrefix_HandlesDevFormat()
{
// Arrange
var registry = new BotRegistry("test-");
registry.Register(new GeminiBotConfiguration());
registry.Register(new GrokBotConfiguration());

// Act
var found = registry.TryGetBot("test-grok hello", out var bot, out var strippedMessage);

// Assert
Assert.True(found);
Assert.Equal("grok", bot!.Prefix);
Assert.Equal("hello", strippedMessage);
}

[Fact]
public void Registry_GetAllBots_PreservesCapabilityInformation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ public void Constructor_WithoutEnvironmentPrefix_SetsDefaultPrefix()
Assert.Equal("gemini", config.Prefix);
}

[Fact]
public void Constructor_WithEnvironmentPrefix_SetsCustomPrefix()
{
// Arrange & Act
var config = new GeminiBotConfiguration("test-");

// Assert
Assert.Equal("test-gemini", config.Prefix);
}

[Fact]
public void ServiceId_ReturnsCorrectValue()
{
Expand Down Expand Up @@ -119,20 +109,6 @@ public void SupportsImages_ReturnsFalse()
Assert.False(config.SupportsImages);
}

[Theory]
[InlineData("")]
[InlineData("test-")]
[InlineData("dev-")]
[InlineData("prod-")]
public void Constructor_WithVariousPrefixes_SetsCorrectPrefix(string environmentPrefix)
{
// Arrange & Act
var config = new GeminiBotConfiguration(environmentPrefix);

// Assert
Assert.Equal($"{environmentPrefix}gemini", config.Prefix);
}

[Fact]
public void GetConfigurationDescription_ReturnsNonEmptyString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@ public void Constructor_WithoutEnvironmentPrefix_SetsDefaultPrefix()
Assert.Equal("grok", config.Prefix);
}

[Fact]
public void Constructor_WithEnvironmentPrefix_SetsCustomPrefix()
{
// Arrange & Act
var config = new GrokBotConfiguration("test-");

// Assert
Assert.Equal("test-grok", config.Prefix);
}

[Fact]
public void ServiceId_ReturnsCorrectValue()
{
Expand Down Expand Up @@ -173,20 +163,6 @@ public void SupportsImages_ReturnsTrue()
Assert.True(config.SupportsImages);
}

[Theory]
[InlineData("")]
[InlineData("test-")]
[InlineData("dev-")]
[InlineData("prod-")]
public void Constructor_WithVariousPrefixes_SetsCorrectPrefix(string environmentPrefix)
{
// Arrange & Act
var config = new GrokBotConfiguration(environmentPrefix);

// Assert
Assert.Equal($"{environmentPrefix}grok", config.Prefix);
}

[Fact]
public void AllCapabilities_AreEnabled()
{
Expand Down
Loading
Loading