A general-purpose Discord bot written with Discord.Net in C#
  • C# 99.7%
  • Shell 0.2%
  • PowerShell 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-05-12 18:57:29 +12:00
src/Pyria Added donate and guide commands and moved the info commands to a new utility module. 2026-05-12 18:57:29 +12:00
.gitignore Worked on the config system a little bit. 2026-05-08 01:22:47 +12:00
LICENSE Added license to the readme with a alpha disclaimer. 2026-05-07 03:19:39 +12:00
Pyria.sln Added v1.0.0 bot files 2026-05-06 02:35:33 +12:00
README.md Fixed wording 2026-05-10 01:18:59 +12:00

Pyria

A general-purpose Discord bot written in C#.

Caution

This is an alpha system so please use at your own risk the EllieBotDevs team accepts no responsibility for any losses caused by using this project in its current state. Please see the LICENSE section for more details.

Modules

Module Commands
⚙️ Administration /admin settings, /admin levelup-channel, /admin ticket-category, /admin ticket-log, /admin ticket-role
🛡️ Moderation /mod kick, /mod ban, /mod unban, /mod mute, /mod unmute, /mod warn, /mod warnings, /mod delwarn, /mod purge, /mod slowmode
🎵 Music /music play, /music skip, /music stop, /music queue, /music volume, /music loop, /music nowplaying, /music remove
🎮 Games /game roll, /game coinflip, /game rps, /game 8ball, /game trivia, /game answer, /game hangman, /game guess
📖 Help /help commands, /help ping, /help botinfo, /help serverinfo, /help userinfo
🎫 Tickets /ticket open, /ticket close, /ticket adduser, /ticket removeuser, /ticket list
📊 Xp /xp rank, /xp leaderboard
💬 Custom Responses /cr add, /cr addglobal, /cr remove, /cr removeid, /cr list, /cr info

Quick Start

1. Prerequisites

Optional

  • EF Core CLI tools (for managing migrations):
    dotnet tool install --global dotnet-ef
    

2. Configure

Edit config.yml and set your bot token:

bot:
  token: "YOUR_ACTUAL_BOT_TOKEN"

3. Discord Developer Portal

In your application's Bot tab, enable these privileged gateway intents:

  • Server Members Intent
  • Message Content Intent

4. Run

dotnet run

Or build a release binary:

dotnet build -c Release
dotnet ./bin/Release/net9.0/DiscordBot.dll

On first run the bot will:

  1. Check and migrate config.yml to the latest version (writing a timestamped backup first)
  2. Apply any pending EF Core database migrations
  3. Register slash commands globally with Discord

Slash command propagation: Global registration can take up to an hour to appear in all servers. For instant updates during development, replace RegisterCommandsGloballyAsync() with RegisterCommandsToGuildAsync(yourGuildId) in BotService.cs.


Project Structure

Pyria/
├── config.yml
├── Program.cs                                  # Entry point + DI container
├── Configuration/
│   ├── BotConfig.cs                            # Typed config models + YAML loader
│   └── ConfigMigrator.cs                       # Automatic config version migrations
├── Models/
│   ├── UserLevel.cs
│   ├── Warning.cs
│   ├── ModLog.cs
│   ├── Ticket.cs
│   ├── CustomResponse.cs
│   ├── GuildSettings.cs
│   └── MusicTrack.cs                           # In-memory only — not persisted
├── Database/
│   ├── BotDbContext.cs                         # EF Core DbContext + entity configuration
│   ├── BotDbContextFactory.cs                  # Design-time factory for dotnet ef tooling
│   ├── DatabaseService.cs                      # LINQ query methods over BotDbContext
│   └── Migrations/                             # Generated by: dotnet ef migrations add
│       ├── XXXXXXXXXXXXXX_init.cs
│       ├── BotDbContextModelSnapshot.cs
│       └── README.md
├── Services/
│   ├── BotService.cs                           # Gateway events + interaction dispatch
│   ├── LevelingService.cs                      # XP grant + level-up logic
│   ├── MusicService.cs                         # Per-guild in-memory queue
│   ├── CustomResponseService.cs                # Trigger matching + CRUD
│   └── GuildSettingsService.cs                 # Per-guild settings with YAML fallback
└── Commands/
    ├── Administration/AdministrationModule.cs
    ├── Moderation/ModerationModule.cs
    ├── Music/MusicModule.cs
    ├── Games/GamesModule.cs
    ├── Help/HelpModule.cs
    ├── Tickets/TicketsModule.cs
    ├── Xp/XpModule.cs
    └── CustomResponses/CustomResponseModule.cs

Configuration (config.yml)

The config file is versioned. On startup the bot compares config_version against the current version and automatically applies any pending migrations, writing a timestamped backup first (e.g. config.v1.20260509_120000.bak.yml). Do not edit config_version manually.

config_version: 2

bot:
  token: "YOUR_BOT_TOKEN_HERE"
  # {guild_count} is replaced at runtime
  status: "/help commands | Serving {guild_count} servers"

database:
  path: "bot.db"

leveling:
  xp_per_message_min: 15
  xp_per_message_max: 25
  xp_cooldown_seconds: 60
  # Global fallback channel name used when no per-guild override exists in the DB.
  # Prefer using /admin levelup-channel in each server instead.
  default_level_up_channel_name: ""

moderation:
  # Name of the role applied by /mod mute
  mute_role: "Muted"
  # Global fallback mod log channel name
  default_log_channel_name: ""

music:
  max_queue_size: 100
  volume_default: 50

Ticket settings (category, log channel, support role) and per-guild level-up channels are configured at runtime via /admin and stored in the database — they no longer live in config.yml.

Adding a config version migration

  1. Bump CurrentVersion in ConfigMigrator.cs
  2. Add a new entry to the _migrations dictionary
  3. Write the migration method — it receives the raw YAML as a Dictionary<object, object?> so you can rename, remove, or add keys freely
public const int CurrentVersion = 3;

private static readonly Dictionary<int, Action<...>> _migrations = new()
{
    [1] = MigrateV2,
    [2] = MigrateV3,   // ← new
};

private static void MigrateV3(Dictionary<object, object?> raw, string path)
{
    if (raw.TryGetValue("music", out var m) && m is Dictionary<object, object?> music)
        music.TryAdd("announce_channel_name", "");
}

Database & Migrations

The bot uses Entity Framework Core 9 with the SQLite provider. All schema changes live in Database/Migrations/ as individual migration files. Migrations are applied automatically on startup.

Workflow for schema changes

# 1. Edit your model(s) in Models/
# 2. Generate the migration
dotnet ef migrations add DescriptiveMigrationName

# 3. Optional — apply manually (the bot does this on startup anyway)
dotnet ef database update

Rolling back

dotnet ef database update PreviousMigrationName
dotnet ef migrations remove

SQLite and ulong

SQLite has no native unsigned integer type. All Discord snowflake IDs (ulong) are stored as signed long and converted transparently via EF Core value converters defined in BotDbContext.OnModelCreating. No casting is needed in query code — EF Core handles it automatically.

EF Core 9 pending model warning

EF Core 9 raises PendingModelChangesWarning if the model snapshot doesn't exactly match the live PyriaDbContext. This is suppressed in BotDbContext.OnConfiguring to prevent a startup crash when the snapshot is slightly out of sync after a migration. If you see it as a console warning, run dotnet ef migrations add to bring the snapshot up to date.


Per-Server Administration

All server-specific settings are managed via /admin and stored in the database, so they persist across restarts and are fully independent per server. All /admin commands require the Administrator permission.

Command Description
/admin settings Shows the current configuration for this server
/admin levelup-channel [channel] Sets or clears the level-up notification channel
/admin ticket-category [category] Sets the category new ticket channels are created under
/admin ticket-log [channel] Sets or clears the ticket event log channel
/admin ticket-role [role] Sets the support role that can see all ticket channels

Custom Responses

Custom responses let moderators make the bot reply to trigger phrases without any code changes. Server-specific responses take priority over globals.

Command Permission Description
/cr add <trigger> <response> [match_type] Manage Messages Adds a server-scoped response
/cr addglobal <trigger> <response> [match_type] Administrator Adds a response across all servers
/cr remove <trigger> Manage Messages Removes a server-scoped response by trigger
/cr removeid <id> Manage Messages Removes any response by ID
/cr list [page] Manage Messages Lists all responses for this server
/cr info <id> Manage Messages Shows full details of a response

match_type is either contains (trigger appears anywhere in the message) or exact (full message must equal the trigger).


Xp

XP is granted once per message after the configured cooldown. The amount is randomised between xp_per_message_min and xp_per_message_max.

XP required to advance from level n to n+1:

XP(n) = 5n² + 50n + 100
Transition XP needed
0 → 1 100
1 → 2 155
5 → 6 475
10 → 11 1,100

Adding Real Audio Playback

MusicModule and MusicService fully manage the per-guild queue, volume, loop state, and skip logic. Actual voice audio requires additional setup:

  1. Install FFmpeg and add it to your PATH
  2. Install yt-dlp for YouTube/URL resolution
  3. In BotService, connect to the user's voice channel via await voiceChannel.ConnectAsync()
  4. Stream PCM audio through the returned IAudioClient

Required Bot Permissions

Permission Used by
View Channels All modules
Send Messages All modules
Embed Links All modules
Read Message History /mod purge, Tickets
Manage Messages /mod purge, /mod slowmode
Manage Channels Tickets
Manage Roles /mod mute, /mod unmute
Kick Members /mod kick
Ban Members /mod ban, /mod unban
Connect / Speak Music (when audio playback is wired up)

Invite URL scopes: bot + applications.commands

applications.commands is required for slash commands to register in servers.


Gateway Intents

The bot requests only the intents it actually uses:

Intent Purpose
Guilds Guild/channel/role resolution
GuildMembers Welcome messages, member lookups
GuildMessages XP grants, custom response matching
GuildMessageReactions Available for future use
GuildVoiceStates Detecting the user's voice channel for music
MessageContent Reading message text for XP and custom responses

LICENSE

Copyright 2026 EllieBotDevs

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

To read the entire license look in the LICENSE file.