0.3.4.0 - Blacklist system, Ping/Uptime commands & fixes
This commit is contained in:
@@ -18,7 +18,9 @@ All commands are prefixed with your configured prefix (e.g. `sd/`). Arguments in
|
||||
|---|---|---|
|
||||
| `help` | `cmds` | Shows all commands organised by category with an interactive embed |
|
||||
| `info <planet\|user> [@user]` | — | Shows detailed info about the current planet or a user |
|
||||
| `ping` | — | Shows the bot's response time |
|
||||
| `source` | `src` | Link to the bot's source code |
|
||||
| `uptime` | — | Shows how long the bot has been running |
|
||||
| `version` | `ver` | Shows the bot version, Valour server version, and Valour SDK version (current and latest) |
|
||||
|
||||
---
|
||||
@@ -75,6 +77,7 @@ All commands are prefixed with your configured prefix (e.g. `sd/`). Arguments in
|
||||
|
||||
| Command | Aliases | Description |
|
||||
|---|---|---|
|
||||
| `blacklist <add\|remove> <@user\|userid>` | — | Add or remove a user from the bot blacklist |
|
||||
| `delete` | — | Deletes a replied-to message. Deletes bot messages directly; requires `ManageMessages` for other members' messages. Reply to a message to use. |
|
||||
| `planet <join\|leave\|list>` | — | Manage the planets the bot is a member of |
|
||||
| `react <emoji> [amount]` | — | Add a reaction to a replied-to message a given number of times |
|
||||
|
||||
@@ -24,6 +24,7 @@ The Bot collects only the minimum data necessary to provide its intended functio
|
||||
The following data is written to a local SQLite database (`database.db`) and is retained across restarts:
|
||||
|
||||
1. Marriage records — the Valour user IDs of both partners and the timestamp the marriage was created
|
||||
2. Blacklist entries — the Valour user ID of each blacklisted user
|
||||
|
||||
### Information Never Stored
|
||||
|
||||
@@ -41,6 +42,7 @@ Temporarily held information is used exclusively to:
|
||||
2. Enable core bot functionality during the current session
|
||||
3. Track echo message pairs to support automatic cleanup when the original command message is deleted
|
||||
4. Operate the marriage system (tracking active marriages and pending proposals/divorces)
|
||||
5. Enforce the blacklist (preventing blacklisted users from using bot commands)
|
||||
|
||||
The Bot does not use any information for profiling, marketing, analytics, or tracking purposes.
|
||||
|
||||
@@ -48,7 +50,7 @@ The Bot does not use any information for profiling, marketing, analytics, or tra
|
||||
|
||||
## 3. Data Storage and Security
|
||||
|
||||
In-memory data is automatically cleared when the Bot restarts. Marriage records are written to a local SQLite database file (`database.db`) on the machine running the bot. No data is sent to any external storage or cloud service.
|
||||
In-memory data is automatically cleared when the Bot restarts. Marriage records and blacklist entries are written to a local SQLite database file (`database.db`) on the machine running the bot. No data is sent to any external storage or cloud service.
|
||||
|
||||
The Bot does not sell, rent, trade, or otherwise share any data with third parties.
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -1,8 +1,3 @@
|
||||
Currently in the process of remaking the bot, for current official bot information goto: [v2 branch](../v2/)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,10 +22,11 @@ SkyBot is a Valour.gg bot built with .NET 10.
|
||||
### Command Categories
|
||||
|
||||
- **Fun** — echo and similar utilities
|
||||
- **Info** — bot info, command listing, planet/user info
|
||||
- **Info** — bot info, ping, uptime, command listing, planet/user info
|
||||
- **Moderation** — kick and ban with reason and duration support
|
||||
- **RP** — emotes (35 actions via nekos.best) and a marriage system
|
||||
- **Utils** — accept, decline, confirm, cancel for pending actions
|
||||
- **Dev** — owner-only tools including blacklist management, planet control, and message utilities
|
||||
|
||||
Full command list: [COMMANDS.md](COMMANDS.md)
|
||||
|
||||
@@ -38,13 +34,13 @@ Full command list: [COMMANDS.md](COMMANDS.md)
|
||||
|
||||
## Data & Privacy
|
||||
|
||||
SkyBot stores only the minimum data required for operation. Marriage data is persisted to a local SQLite database. All other data is stored in-memory and is lost on restart.
|
||||
SkyBot stores only the minimum data required for operation. Marriage records and blacklist entries are persisted to a local SQLite database. All other data is stored in-memory and is lost on restart.
|
||||
|
||||
SkyBot does **not** store:
|
||||
|
||||
- Message content
|
||||
- Direct messages
|
||||
- Personal user data beyond what is needed for the marriage system
|
||||
- Personal user data beyond what is needed for the marriage and blacklist systems
|
||||
|
||||
Full privacy policy: [PRIVACY.md](PRIVACY.md)
|
||||
|
||||
|
||||
91
SkyBot/Commands/Dev/Blacklist.cs
Normal file
91
SkyBot/Commands/Dev/Blacklist.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Runtime.Serialization;
|
||||
using SkyBot.Helpers;
|
||||
using SkyBot.Models;
|
||||
using SkyBot.Services;
|
||||
using Valour.Sdk.Models;
|
||||
|
||||
namespace SkyBot.Commands
|
||||
{
|
||||
public class Blacklist : ICommand
|
||||
{
|
||||
public string Name => "blacklist";
|
||||
public string[] Aliases => [];
|
||||
public string Description => "adds or removes a user from the blacklist";
|
||||
public string Category => "Dev";
|
||||
public string Usage => "blacklist <add|remove> <@user|userid>";
|
||||
public string[] SubCommands => ["add", "remove"];
|
||||
|
||||
public async Task Execute(CommandContext ctx)
|
||||
{
|
||||
if (!PermissionHelper.IsOwner(ctx.Member))
|
||||
{
|
||||
await MessageHelper.ReplyAsync(ctx, "You do not have permission to execute this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
long? userId = null;
|
||||
|
||||
if (ctx.Message.Mentions?.Any() == true)
|
||||
{
|
||||
var member = await ctx.Planet.FetchMemberAsync(ctx.Message.Mentions.First().TargetId);
|
||||
if (member is null) { await MessageHelper.ReplyAsync(ctx, "Could not find that member."); return; }
|
||||
userId = member.UserId;
|
||||
}
|
||||
else if (long.TryParse(ctx.Args.ElementAtOrDefault(1), out long parsed))
|
||||
{
|
||||
var member = await ctx.Planet.FetchMemberAsync(parsed);
|
||||
userId = member?.UserId ?? parsed;
|
||||
}
|
||||
|
||||
if (userId is null)
|
||||
{
|
||||
await MessageHelper.ReplyAsync(ctx, "Mention a user or provide their user ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
User user = await ctx.Client.UserService.FetchUserAsync(userId.Value);
|
||||
if (user is null) { await MessageHelper.ReplyAsync(ctx, "Could not find that user."); return; }
|
||||
|
||||
switch (ctx.Args[0])
|
||||
{
|
||||
case "add":
|
||||
await handleAdd(ctx, user);
|
||||
break;
|
||||
case "remove":
|
||||
await handleRemove(ctx, user);
|
||||
break;
|
||||
default:
|
||||
await MessageHelper.ReplyAsync(ctx, "Usage: blacklist <add|remove> <@user|userid>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task handleAdd(CommandContext ctx, User user)
|
||||
{
|
||||
var result = await BlacklistService.Blacklist(user.Id);
|
||||
switch (result)
|
||||
{
|
||||
case BlacklistService.BlacklistResult.AlreadyBlacklisted:
|
||||
await MessageHelper.ReplyAsync(ctx, "User is already blacklisted");
|
||||
break;
|
||||
case BlacklistService.BlacklistResult.Ok:
|
||||
await MessageHelper.ReplyAsync(ctx, $"Blacklisted {user.Name} successfully.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task handleRemove(CommandContext ctx, User user)
|
||||
{
|
||||
var result = await BlacklistService.UnBlacklist(user.Id);
|
||||
switch (result)
|
||||
{
|
||||
case BlacklistService.UnBlacklistResult.NotBlacklisted:
|
||||
await MessageHelper.ReplyAsync(ctx, "User is not blacklisted.");
|
||||
break;
|
||||
case BlacklistService.UnBlacklistResult.Ok:
|
||||
await MessageHelper.ReplyAsync(ctx, $"Removed {user.Name} from the blacklist.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace SkyBot.Commands
|
||||
public string[] Aliases => [];
|
||||
public string Description => "Adds a reaction to a replied message.";
|
||||
public string Category => "Dev";
|
||||
public string Usage => "dev <emoji> [amount]";
|
||||
public string Usage => "react <emoji> [amount]";
|
||||
public string[] SubCommands => [];
|
||||
|
||||
public async Task Execute(CommandContext ctx)
|
||||
|
||||
26
SkyBot/Commands/Info/Ping.cs
Normal file
26
SkyBot/Commands/Info/Ping.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using SkyBot.Helpers;
|
||||
using SkyBot.Models;
|
||||
using Valour.Sdk.Models;
|
||||
using Valour.Shared;
|
||||
|
||||
namespace SkyBot.Commands
|
||||
{
|
||||
public class Ping : ICommand
|
||||
{
|
||||
public string Name => "ping";
|
||||
public string[] Aliases => [];
|
||||
public string Description => "Displays the bots response time.";
|
||||
public string Category => "Info";
|
||||
public string Usage => "ping";
|
||||
public string[] SubCommands => [];
|
||||
|
||||
public async Task Execute(CommandContext ctx)
|
||||
{
|
||||
DateTime start = DateTime.UtcNow;
|
||||
TaskResult<Message> message = await MessageHelper.ReplyAsync(ctx, "🏓 Pinging...");
|
||||
double elapsed = (DateTime.UtcNow - start).TotalMilliseconds;
|
||||
|
||||
await MessageHelper.EditAsync(ctx.Channel, message.Data, $"🏓 Ping! `{elapsed:F0}ms`");
|
||||
}
|
||||
}
|
||||
}
|
||||
28
SkyBot/Commands/Info/Uptime.cs
Normal file
28
SkyBot/Commands/Info/Uptime.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using SkyBot.Helpers;
|
||||
using SkyBot.Models;
|
||||
|
||||
namespace SkyBot.Commands
|
||||
{
|
||||
public class Uptime : ICommand
|
||||
{
|
||||
public string Name => "uptime";
|
||||
public string[] Aliases => [];
|
||||
public string Description => "Displays the uptime of the bot";
|
||||
public string Category => "Info";
|
||||
public string Usage => "uptime";
|
||||
public string[] SubCommands => [];
|
||||
|
||||
public async Task Execute(CommandContext ctx)
|
||||
{
|
||||
TimeSpan uptime = DateTime.UtcNow - Program.StartTime;
|
||||
string formatted = uptime.TotalDays >= 1
|
||||
? $"{(int)uptime.TotalDays}d {uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s"
|
||||
: uptime.TotalHours >= 1
|
||||
? $"{uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s"
|
||||
: uptime.TotalMinutes >= 1
|
||||
? $"{uptime.Minutes}m {uptime.Seconds}s"
|
||||
: $"{uptime.Seconds}s";
|
||||
await MessageHelper.ReplyAsync(ctx, $"⏱️ SkyBot Uptime: {formatted}");
|
||||
}
|
||||
}
|
||||
}
|
||||
71
SkyBot/Services/BlacklistService.cs
Normal file
71
SkyBot/Services/BlacklistService.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace SkyBot.Services
|
||||
{
|
||||
public static class BlacklistService
|
||||
{
|
||||
private static readonly ConcurrentDictionary<long, bool> blacklist = new();
|
||||
public static async Task InitialiseAsync()
|
||||
{
|
||||
using SqliteConnection connection = DatabaseService.GetConnection();
|
||||
using SqliteCommand cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT UserId FROM Blacklist";
|
||||
using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
while ( await reader.ReadAsync())
|
||||
{
|
||||
long userId = (long)reader["UserId"];
|
||||
blacklist[userId] = true;
|
||||
}
|
||||
Console.WriteLine($"BlacklistService initialised. Loaded {blacklist.Count} blacklisted users");
|
||||
}
|
||||
|
||||
public static bool GetBlacklisted(long userId)
|
||||
{
|
||||
return blacklist.TryGetValue(userId, out var blacklisted) ? blacklisted : false;
|
||||
}
|
||||
|
||||
public enum BlacklistResult
|
||||
{
|
||||
Ok,
|
||||
AlreadyBlacklisted
|
||||
}
|
||||
|
||||
public static async Task<BlacklistResult> Blacklist(long userId)
|
||||
{
|
||||
if (GetBlacklisted(userId)) return BlacklistResult.AlreadyBlacklisted;
|
||||
|
||||
using SqliteConnection connection = DatabaseService.GetConnection();
|
||||
using SqliteCommand cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "INSERT OR IGNORE INTO Blacklist (UserId, Bool) VALUES ($u, $b)";
|
||||
cmd.Parameters.AddWithValue("$u", userId);
|
||||
cmd.Parameters.AddWithValue("$b", true);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
blacklist[userId] = true;
|
||||
|
||||
return BlacklistResult.Ok;
|
||||
}
|
||||
|
||||
public enum UnBlacklistResult
|
||||
{
|
||||
Ok,
|
||||
NotBlacklisted
|
||||
}
|
||||
|
||||
public static async Task<UnBlacklistResult> UnBlacklist(long userId)
|
||||
{
|
||||
if (!GetBlacklisted(userId)) return UnBlacklistResult.NotBlacklisted;
|
||||
|
||||
using SqliteConnection connection = DatabaseService.GetConnection();
|
||||
using SqliteCommand cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "DELETE FROM Blacklist WHERE UserId = $u";
|
||||
cmd.Parameters.AddWithValue("$u", userId);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
blacklist.TryRemove(userId, out _);
|
||||
|
||||
return UnBlacklistResult.Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,18 @@ namespace SkyBot.Services
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
using (SqliteCommand cmd = connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS Blacklist (
|
||||
UserId INTEGER NOT NULL,
|
||||
Bool BOOLEAN NOT NULL,
|
||||
PRIMARY KEY (UserId, Bool)
|
||||
);
|
||||
";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
Console.WriteLine("Database initialised.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,13 @@ namespace SkyBot.Services
|
||||
|
||||
string prefix = Config.Prefix;
|
||||
string content = message.Content;
|
||||
PlanetMember member = await message.FetchAuthorMemberAsync();
|
||||
|
||||
if (!content.ToLower().StartsWith(prefix)) return;
|
||||
if (!channelCache.TryGetValue(message.ChannelId, out var channel)) return;
|
||||
if (string.IsNullOrWhiteSpace(content)) return;
|
||||
|
||||
if (BlacklistService.GetBlacklisted(message.AuthorUserId)) return;
|
||||
|
||||
string[] parts = content.Substring(prefix.Length).Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 0) return;
|
||||
@@ -34,6 +35,8 @@ namespace SkyBot.Services
|
||||
string command = parts[0].ToLower();
|
||||
string[] args = parts[1..];
|
||||
|
||||
PlanetMember member = await message.FetchAuthorMemberAsync();
|
||||
|
||||
CommandContext ctx = new CommandContext
|
||||
{
|
||||
Client = client,
|
||||
|
||||
@@ -10,14 +10,17 @@ namespace SkyBot
|
||||
private static readonly ValourClient client = new("https://api.valour.gg/");
|
||||
private static readonly ConcurrentDictionary<long, Channel> channelCache = new();
|
||||
private static readonly ConcurrentDictionary<long, bool> initialisedPlanets = new();
|
||||
public static DateTime StartTime;
|
||||
|
||||
public static async Task Main()
|
||||
{
|
||||
client.SetupHttpClient();
|
||||
try
|
||||
{
|
||||
StartTime = DateTime.UtcNow;
|
||||
await DatabaseService.InitialiseAsync();
|
||||
await MarriageService.InitialiseAsync();
|
||||
await BlacklistService.InitialiseAsync();
|
||||
await BotService.InitialiseBotAsync(client, channelCache, initialisedPlanets);
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.3.3.2</Version>
|
||||
<Version>0.3.4.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user