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 |
|
| `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 |
|
| `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 |
|
| `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) |
|
| `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 |
|
| 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. |
|
| `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 |
|
| `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 |
|
| `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:
|
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
|
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
|
### Information Never Stored
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ Temporarily held information is used exclusively to:
|
|||||||
2. Enable core bot functionality during the current session
|
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
|
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)
|
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.
|
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
|
## 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.
|
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
|
### Command Categories
|
||||||
|
|
||||||
- **Fun** — echo and similar utilities
|
- **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
|
- **Moderation** — kick and ban with reason and duration support
|
||||||
- **RP** — emotes (35 actions via nekos.best) and a marriage system
|
- **RP** — emotes (35 actions via nekos.best) and a marriage system
|
||||||
- **Utils** — accept, decline, confirm, cancel for pending actions
|
- **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)
|
Full command list: [COMMANDS.md](COMMANDS.md)
|
||||||
|
|
||||||
@@ -38,13 +34,13 @@ Full command list: [COMMANDS.md](COMMANDS.md)
|
|||||||
|
|
||||||
## Data & Privacy
|
## 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:
|
SkyBot does **not** store:
|
||||||
|
|
||||||
- Message content
|
- Message content
|
||||||
- Direct messages
|
- 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)
|
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[] Aliases => [];
|
||||||
public string Description => "Adds a reaction to a replied message.";
|
public string Description => "Adds a reaction to a replied message.";
|
||||||
public string Category => "Dev";
|
public string Category => "Dev";
|
||||||
public string Usage => "dev <emoji> [amount]";
|
public string Usage => "react <emoji> [amount]";
|
||||||
public string[] SubCommands => [];
|
public string[] SubCommands => [];
|
||||||
|
|
||||||
public async Task Execute(CommandContext ctx)
|
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();
|
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.");
|
Console.WriteLine("Database initialised.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ namespace SkyBot.Services
|
|||||||
|
|
||||||
string prefix = Config.Prefix;
|
string prefix = Config.Prefix;
|
||||||
string content = message.Content;
|
string content = message.Content;
|
||||||
PlanetMember member = await message.FetchAuthorMemberAsync();
|
|
||||||
|
|
||||||
if (!content.ToLower().StartsWith(prefix)) return;
|
if (!content.ToLower().StartsWith(prefix)) return;
|
||||||
if (!channelCache.TryGetValue(message.ChannelId, out var channel)) return;
|
if (!channelCache.TryGetValue(message.ChannelId, out var channel)) return;
|
||||||
if (string.IsNullOrWhiteSpace(content)) return;
|
if (string.IsNullOrWhiteSpace(content)) return;
|
||||||
|
|
||||||
|
if (BlacklistService.GetBlacklisted(message.AuthorUserId)) return;
|
||||||
|
|
||||||
string[] parts = content.Substring(prefix.Length).Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
string[] parts = content.Substring(prefix.Length).Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
@@ -34,6 +35,8 @@ namespace SkyBot.Services
|
|||||||
string command = parts[0].ToLower();
|
string command = parts[0].ToLower();
|
||||||
string[] args = parts[1..];
|
string[] args = parts[1..];
|
||||||
|
|
||||||
|
PlanetMember member = await message.FetchAuthorMemberAsync();
|
||||||
|
|
||||||
CommandContext ctx = new CommandContext
|
CommandContext ctx = new CommandContext
|
||||||
{
|
{
|
||||||
Client = client,
|
Client = client,
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ namespace SkyBot
|
|||||||
private static readonly ValourClient client = new("https://api.valour.gg/");
|
private static readonly ValourClient client = new("https://api.valour.gg/");
|
||||||
private static readonly ConcurrentDictionary<long, Channel> channelCache = new();
|
private static readonly ConcurrentDictionary<long, Channel> channelCache = new();
|
||||||
private static readonly ConcurrentDictionary<long, bool> initialisedPlanets = new();
|
private static readonly ConcurrentDictionary<long, bool> initialisedPlanets = new();
|
||||||
|
public static DateTime StartTime;
|
||||||
|
|
||||||
public static async Task Main()
|
public static async Task Main()
|
||||||
{
|
{
|
||||||
client.SetupHttpClient();
|
client.SetupHttpClient();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StartTime = DateTime.UtcNow;
|
||||||
await DatabaseService.InitialiseAsync();
|
await DatabaseService.InitialiseAsync();
|
||||||
await MarriageService.InitialiseAsync();
|
await MarriageService.InitialiseAsync();
|
||||||
|
await BlacklistService.InitialiseAsync();
|
||||||
await BotService.InitialiseBotAsync(client, channelCache, initialisedPlanets);
|
await BotService.InitialiseBotAsync(client, channelCache, initialisedPlanets);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.3.3.2</Version>
|
<Version>0.3.4.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user