60
PRIVACY.md
60
PRIVACY.md
@@ -2,61 +2,53 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>Privacy Policy</h1>
|
<h1>Privacy Policy</h1>
|
||||||
<p><span class="bold">Effective Date:</span> February 26, 2026</p>
|
<p><strong>Effective Date:</strong> March 16, 2026</p>
|
||||||
<p>This Privacy Policy describes how the bot (“the Bot”) collects, uses, and stores information when used within a server.</p>
|
<p>This Privacy Policy describes how SkyBot ("the Bot") collects, uses, and stores information when used within a Valour planet.</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>1. Information Collected</h2>
|
<h2>1. Information Collected</h2>
|
||||||
<p>The Bot collects and stores only the minimum data necessary to provide its intended functionality.</p>
|
<p>The Bot collects only the minimum data necessary to provide its intended functionality. All data is stored in-memory and is lost when the Bot restarts. The Bot does not persist any data to disk.</p>
|
||||||
|
<h3>Information Temporarily Held in Memory:</h3>
|
||||||
<h3>Information Stored:</h3>
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Message IDs</li>
|
<li>Channel IDs (for routing messages and commands)</li>
|
||||||
<li>Channel IDs</li>
|
<li>Planet IDs (for planet-specific operations)</li>
|
||||||
<li>Server (“Planet”) IDs</li>
|
<li>Member IDs (for moderation commands)</li>
|
||||||
<li>Planet Configuration data associated with those channels</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
<h3>Information Never Stored:</h3>
|
||||||
<h3>Information Not Stored:</h3>
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Message content</li>
|
<li>Message content</li>
|
||||||
<li>User-generated message content</li>
|
<li>Direct Messages ("DMs")</li>
|
||||||
<li>Direct Messages (“DMs”)</li>
|
|
||||||
<li>Personal account information (including usernames, email addresses, or other personally identifiable information)</li>
|
<li>Personal account information (including usernames, email addresses, or other personally identifiable information)</li>
|
||||||
|
<li>Any data that persists beyond the Bot's current session</li>
|
||||||
</ol>
|
</ol>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>2. Purpose of Data Collection</h2>
|
<h2>2. Purpose of Data Collection</h2>
|
||||||
<p>Stored information is used exclusively to:</p>
|
<p>Temporarily held information is used exclusively to:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Maintain server-specific configuration settings</li>
|
<li>Route commands to the correct channels and planets</li>
|
||||||
<li>Associate Planets with designated channels</li>
|
<li>Enable moderation commands such as ban, unban, and kick</li>
|
||||||
<li>Enable and maintain core bot functionality</li>
|
<li>Enable core bot functionality during the current session</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>The Bot does not use stored information for profiling, marketing, analytics, or tracking purposes.</p>
|
<p>The Bot does not use any information for profiling, marketing, analytics, or tracking purposes.</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>3. Data Storage and Security</h2>
|
<h2>3. Data Storage and Security</h2>
|
||||||
<p>All stored data is maintained securely on the Bot’s hosting server. Reasonable technical measures are implemented to protect stored information against unauthorized access, alteration, or disclosure.</p>
|
<p>Since all data is stored in-memory only, no data is written to disk, databases, or any external storage. All temporarily held data is automatically cleared when the Bot restarts.</p>
|
||||||
<p>The Bot does not sell, rent, trade, or otherwise share stored data with third parties.</p>
|
<p>The Bot does not sell, rent, trade, or otherwise share any data with third parties.</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>4. Data Retention</h2>
|
<h2>4. Data Retention</h2>
|
||||||
<p>Configuration data is retained only while the Bot remains active within a server.</p>
|
<p>All data is held only for the duration of the Bot's current session. No data is retained beyond a restart. There is no mechanism for long-term data storage in this Bot.</p>
|
||||||
<p>If the Bot is removed from a server, associated configuration data may be deleted within a reasonable timeframe.</p>
|
|
||||||
<hr>
|
<hr>
|
||||||
|
<h2>5. Self-Hosting</h2>
|
||||||
<h2>5. Future Changes to Logging or Data Practices</h2>
|
<p>SkyBot is designed for self-hosting. If you choose to host your own instance of SkyBot, you are responsible for the privacy and security of any data processed by your instance. This policy applies to the official instance of SkyBot only.</p>
|
||||||
|
<hr>
|
||||||
|
<h2>6. Future Changes to Logging or Data Practices</h2>
|
||||||
<p>If additional operational logging or data collection practices are introduced in the future, this Privacy Policy will be updated to reflect those changes prior to implementation.</p>
|
<p>If additional operational logging or data collection practices are introduced in the future, this Privacy Policy will be updated to reflect those changes prior to implementation.</p>
|
||||||
<p>Continued use of the Bot after updates to this policy constitutes acceptance of the revised policy.</p>
|
<p>Continued use of the Bot after updates to this policy constitutes acceptance of the revised policy.</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
<h2>7. Contact Information</h2>
|
||||||
<h2>6. Contact Information</h2>
|
|
||||||
<p>For privacy-related inquiries, requests, or concerns, please contact:</p>
|
<p>For privacy-related inquiries, requests, or concerns, please contact:</p>
|
||||||
<p><span class="bold">Email:</span> contact@skyjoshua.xyz</p>
|
<p><strong>Email:</strong> contact@skyjoshua.xyz</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
67
README.md
67
README.md
@@ -1,106 +1,91 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>SkyBot</h1>
|
<h1>SkyBot</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
SkyBot is a Valour.gg bot.
|
SkyBot is a Valour.gg bot built with .NET 10.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Features</h2>
|
<h2>Features</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Designed for self-hosting</li>
|
<li>Designed for self-hosting</li>
|
||||||
<li>Open-source under AGPL-3.0</li>
|
<li>Open-source under AGPL-3.0</li>
|
||||||
<li>Built with .NET</li>
|
<li>Built with .NET 10</li>
|
||||||
|
<li>Command system with automatic registration</li>
|
||||||
|
<li>Moderation commands (ban, unban, kick)</li>
|
||||||
|
<li>Fun commands (8ball, coinflip, dice, rock paper scissors, and more)</li>
|
||||||
|
<li>Info commands (user info, planet info, ping, uptime)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Data & Privacy</h2>
|
<h2>Data & Privacy</h2>
|
||||||
<p>SkyBot stores only the minimum data required for operation:</p>
|
<p>SkyBot stores only the minimum data required for operation. All data is stored in-memory and is lost on restart. SkyBot does <strong>not</strong> persist any data to disk.</p>
|
||||||
<ul>
|
|
||||||
<li>Message IDs</li>
|
|
||||||
<li>Server (Planet) IDs</li>
|
|
||||||
<li>Channel IDs</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>SkyBot does <strong>not</strong> store:</p>
|
<p>SkyBot does <strong>not</strong> store:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Message content</li>
|
<li>Message content</li>
|
||||||
<li>Direct messages</li>
|
<li>Direct messages</li>
|
||||||
<li>Personal user data</li>
|
<li>Personal user data</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Full privacy policy:<br>
|
Full privacy policy:<br>
|
||||||
<a href="https://github.com/SkyJoshua/SkyBot/blob/main/PRIVACY.md">
|
<a href="https://github.com/SkyJoshua/SkyBot/blob/main/PRIVACY.md">
|
||||||
https://github.com/SkyJoshua/SkyBot/blob/main/PRIVACY.md
|
https://github.com/SkyJoshua/SkyBot/blob/main/PRIVACY.md
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>License</h2>
|
<h2>License</h2>
|
||||||
<p>
|
<p>
|
||||||
This project is licensed under the
|
This project is licensed under the
|
||||||
<strong>GNU Affero General Public License v3.0 (AGPL-3.0)</strong>.
|
<strong>GNU Affero General Public License v3.0 (AGPL-3.0)</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
See the LICENSE file for details:<br>
|
See the LICENSE file for details:<br>
|
||||||
<a href="https://github.com/SkyJoshua/SkyBot/blob/main/LICENSE">
|
<a href="https://github.com/SkyJoshua/SkyBot/blob/main/LICENSE">
|
||||||
https://github.com/SkyJoshua/SkyBot/blob/main/LICENSE
|
https://github.com/SkyJoshua/SkyBot/blob/main/LICENSE
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Because this project is licensed under AGPL-3.0, if you modify and deploy it
|
Because this project is licensed under AGPL-3.0, if you modify and deploy it
|
||||||
publicly (including as a hosted service), you must make your source code
|
publicly (including as a hosted service), you must make your source code
|
||||||
available under the same license.
|
available under the same license.
|
||||||
</p>
|
</p>
|
||||||
|
<h2>Requirements</h2>
|
||||||
|
<ul>
|
||||||
|
<li>.NET 10</li>
|
||||||
|
<li>A Valour bot token</li>
|
||||||
|
</ul>
|
||||||
<h2>Installation</h2>
|
<h2>Installation</h2>
|
||||||
Fork this Repository
|
<p>Fork this repository, then:</p>
|
||||||
<pre><code>git clone https://github.com/YOUR_USERNAME/SkyBot.git
|
<pre><code>git clone https://github.com/YOUR_USERNAME/SkyBot.git
|
||||||
cd SkyBot
|
cd SkyBot/SkyBot
|
||||||
dotnet restore
|
dotnet restore
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
All required NuGet packages will be installed automatically using the
|
All required NuGet packages will be installed automatically using the
|
||||||
provided <code>SkyBot.csproj</code> file.
|
provided <code>SkyBot.csproj</code> file.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
<p>Before running the bot, create a <code>.env</code> file in the root directory of the project with the following content:</p>
|
<p>Create a <code>.env</code> file in the root directory of the project with your bot token:</p>
|
||||||
|
|
||||||
<pre><code>TOKEN=your-bot-token-here
|
<pre><code>TOKEN=your-bot-token-here
|
||||||
PREFIX=your-prefix-here
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
<p>Then open <code>Config.cs</code> and update the following values:</p>
|
||||||
|
<pre><code>public static readonly long OwnerId = your-owner-id-here;
|
||||||
|
public static readonly string Prefix = "your-prefix-here";
|
||||||
|
public static readonly string SourceLink = "your-source-link-here";
|
||||||
|
</code></pre>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Replace <code>your-bot-token-here</code> with your actual bot token.</li>
|
<li>Replace <code>your-owner-id-here</code> with your Valour user ID.</li>
|
||||||
<li>Ensure the bot has proper permissions in the target server.</li>
|
<li>Replace <code>your-prefix-here</code> with your desired command prefix (e.g. <code>s/</code>).</li>
|
||||||
|
<li>Replace <code>your-source-link-here</code> with a link to your fork of the repository.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p>Never commit your <code>.env</code> file to the repository. Ensure it is listed in your <code>.gitignore</code>.</p>
|
||||||
<p>
|
|
||||||
Sensitive data such as bot tokens should never be committed to the repository.
|
|
||||||
Use environment variables or secure configuration methods.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Running the Bot</h2>
|
<h2>Running the Bot</h2>
|
||||||
|
<pre><code>dotnet run</code></pre>
|
||||||
<pre><code>dotnet run
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h2>Contributing</h2>
|
<h2>Contributing</h2>
|
||||||
<p>
|
<p>
|
||||||
Contributions are welcome. By submitting a contribution, you agree that your
|
Contributions are welcome. By submitting a contribution, you agree that your
|
||||||
contributions will be licensed under AGPL-3.0.
|
contributions will be licensed under AGPL-3.0.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Fork the repository</li>
|
<li>Fork the repository</li>
|
||||||
<li>Create a feature branch</li>
|
<li>Create a feature branch</li>
|
||||||
<li>Submit a pull request</li>
|
<li>Submit a pull request</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -7,7 +7,7 @@ using Valour.Shared;
|
|||||||
|
|
||||||
namespace SkyBot.Commands
|
namespace SkyBot.Commands
|
||||||
{
|
{
|
||||||
public class Test : ICommand
|
public class Edit : ICommand
|
||||||
{
|
{
|
||||||
public string Name => "edit";
|
public string Name => "edit";
|
||||||
public string[] Aliases => [];
|
public string[] Aliases => [];
|
||||||
@@ -29,6 +29,7 @@ namespace SkyBot.Commands
|
|||||||
if(!PermissionHelper.IsOwner(member))
|
if(!PermissionHelper.IsOwner(member))
|
||||||
{
|
{
|
||||||
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.ReplyToId == null)
|
if (message.ReplyToId == null)
|
||||||
103
SkyBot/Commands/Mod/SetWelcome.cs
Normal file
103
SkyBot/Commands/Mod/SetWelcome.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using SkyBot.Models;
|
||||||
|
using SkyBot.Services;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
using Valour.Shared.Authorization;
|
||||||
|
using Valour.Shared.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Commands
|
||||||
|
{
|
||||||
|
public class SetWelcome : ICommand
|
||||||
|
{
|
||||||
|
public string Name => "setwelcome";
|
||||||
|
public string[] Aliases => [];
|
||||||
|
public string Description => "Sets the welcome channel, message or active.";
|
||||||
|
public string Section => "Mod";
|
||||||
|
public string Usage => "set <channel|message|active [value]";
|
||||||
|
|
||||||
|
public async Task Execute(CommandContext ctx)
|
||||||
|
{
|
||||||
|
ConcurrentDictionary<long, Channel> channelCache = ctx.ChannelCache;
|
||||||
|
long channelId = ctx.ChannelId;
|
||||||
|
Message message = ctx.Message;
|
||||||
|
PlanetMember member = ctx.Member;
|
||||||
|
Planet planet = ctx.Planet;
|
||||||
|
string[] args = ctx.Args;
|
||||||
|
|
||||||
|
if (!channelCache.TryGetValue(channelId, out var channel)) return;
|
||||||
|
|
||||||
|
if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Manage]) && !PermissionHelper.IsOwner(member))
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "Please specify `channel` or `message`.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].ToLower())
|
||||||
|
{
|
||||||
|
case "channel":
|
||||||
|
case "c":
|
||||||
|
long targetChannelId;
|
||||||
|
if (message.Mentions != null && message.Mentions.Any(m => m.Type == MentionType.Channel)) {targetChannelId = message.Mentions.First(m => m.Type == MentionType.Channel).TargetId;}
|
||||||
|
else if (args.Length > 1 && long.TryParse(args[1], out long parsedChannelId)) {targetChannelId = parsedChannelId;}
|
||||||
|
else {targetChannelId = channelId;}
|
||||||
|
|
||||||
|
if (!channelCache.ContainsKey(targetChannelId)) {await MessageHelper.ReplyAsync(ctx, channel, "Could not find that channel."); return;}
|
||||||
|
|
||||||
|
await WelcomeService.SetWelcomeChannel(planet.Id, targetChannelId);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, $"Welcome channel set to «@c-{targetChannelId}».");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "message":
|
||||||
|
case "m":
|
||||||
|
if (args.Length < 2)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a message. Valid variables: {username} {nickname} {fulluser} {mention} {id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string msg = string.Join(" ", args[1..]);
|
||||||
|
await WelcomeService.SetWelcomeMessage( planet.Id, msg);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, $"Welcome message set to: `{msg}`");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "active":
|
||||||
|
case "a":
|
||||||
|
if (args.Length < 2)
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "Please provide a value. Use `true`, `false`, or `toggle`.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string value = args[1].ToLower();
|
||||||
|
if (value != "toggle" && value != "true" && value != "false")
|
||||||
|
{
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "Invalid value. Use `true`, `false`, `toggle`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == "toggle")
|
||||||
|
{
|
||||||
|
var toggle = await WelcomeService.SetActive(planet.Id);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, toggle.Value ? "Welcome messages enabled." : "Welcome messages disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool.TryParse(value, out var active);
|
||||||
|
|
||||||
|
await WelcomeService.SetActive(planet.Id, active);
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, active ? "Welcome messages enabled." : "Welcome messages disabled.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await MessageHelper.ReplyAsync(ctx, channel, "Invalid option. Use `channel`, `message` or `active`.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
SkyBot/Models/DatabaseHelper.cs
Normal file
32
SkyBot/Models/DatabaseHelper.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace SkyBot.Helpers
|
||||||
|
{
|
||||||
|
public static class DatabaseHelper
|
||||||
|
{
|
||||||
|
private const string ConnectionString = "Data Source=database.db";
|
||||||
|
|
||||||
|
public static SqliteConnection GetConnection()
|
||||||
|
{
|
||||||
|
SqliteConnection connection = new SqliteConnection(ConnectionString);
|
||||||
|
connection.Open();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = GetConnection();
|
||||||
|
using SqliteCommand cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS WelcomeConfigs (
|
||||||
|
PlanetId INTEGER PRIMARY KEY,
|
||||||
|
ChannelId INTEGER NOT NULL DEFAULT 0,
|
||||||
|
Message TEXT NOT NULL DEFAULT 'Welcome to the planet, {username}!',
|
||||||
|
Active INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
";
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
Console.WriteLine("Database initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
SkyBot/Models/WelcomeConfig.cs
Normal file
7
SkyBot/Models/WelcomeConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
public class WelcomeConfig
|
||||||
|
{
|
||||||
|
public long PlanetId { get; set; }
|
||||||
|
public long ChannelId { get; set; }
|
||||||
|
public string Message { get; set; } = "Welcome to the planet, {username}!";
|
||||||
|
public bool Active { get; set; } = false;
|
||||||
|
}
|
||||||
@@ -5,10 +5,14 @@ using SkyBot.Models;
|
|||||||
using Valour.Sdk.Client;
|
using Valour.Sdk.Client;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace SkyBot.Services.Messages
|
namespace SkyBot.Services.Messages
|
||||||
{
|
{
|
||||||
public static class Create
|
public static class Create
|
||||||
{
|
{
|
||||||
|
private static readonly ConcurrentDictionary<long, DateTime> _cooldowns = new();
|
||||||
|
private static readonly TimeSpan _cooldown = TimeSpan.FromSeconds(2);
|
||||||
public static async Task MessageAsync(
|
public static async Task MessageAsync(
|
||||||
ValourClient client,
|
ValourClient client,
|
||||||
ConcurrentDictionary<long, Channel> channelCache,
|
ConcurrentDictionary<long, Channel> channelCache,
|
||||||
@@ -40,6 +44,11 @@ namespace SkyBot.Services.Messages
|
|||||||
Client = client
|
Client = client
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_cooldowns.TryGetValue(message.AuthorUserId, out var lastUsed) && DateTime.UtcNow - lastUsed < _cooldown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cooldowns[message.AuthorUserId] = DateTime.UtcNow;
|
||||||
|
|
||||||
if (CommandRegistry.Commands.TryGetValue(command, out var handler))
|
if (CommandRegistry.Commands.TryGetValue(command, out var handler))
|
||||||
{
|
{
|
||||||
await handler.Execute(ctx);
|
await handler.Execute(ctx);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using SkyBot.Services;
|
|
||||||
using Valour.Sdk.Client;
|
using Valour.Sdk.Client;
|
||||||
|
using Valour.Sdk.ModelLogic;
|
||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
using Valour.Sdk.Models.Messages.Embeds;
|
|
||||||
|
|
||||||
|
|
||||||
namespace SkyBot.Services
|
namespace SkyBot.Services
|
||||||
{
|
{
|
||||||
public static class PlanetService
|
public static class PlanetService
|
||||||
{
|
{
|
||||||
|
private static readonly DateTime _startTime = DateTime.UtcNow;
|
||||||
public static async Task InitializePlanetsAsync(
|
public static async Task InitializePlanetsAsync(
|
||||||
ValourClient client,
|
ValourClient client,
|
||||||
ConcurrentDictionary<long, Channel> channelCache,
|
ConcurrentDictionary<long, Channel> channelCache,
|
||||||
@@ -19,15 +18,27 @@ namespace SkyBot.Services
|
|||||||
.Select(async planet =>
|
.Select(async planet =>
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Initializing Planet: {planet.Name}");
|
Console.WriteLine($"Initializing Planet: {planet.Name}");
|
||||||
|
|
||||||
await planet.EnsureReadyAsync();
|
await planet.EnsureReadyAsync();
|
||||||
await planet.FetchInitialDataAsync();
|
await planet.FetchInitialDataAsync();
|
||||||
await ChannelService.InitializeChannelsAsync(channelCache, planet);
|
await ChannelService.InitializeChannelsAsync(channelCache, planet);
|
||||||
|
|
||||||
planet.Channels.Changed += async (channelEvent) => {
|
planet.Channels.Changed += async _ =>
|
||||||
|
{
|
||||||
await ChannelService.InitializeChannelsAsync(channelCache, planet);
|
await ChannelService.InitializeChannelsAsync(channelCache, planet);
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
|
planet.Members.Changed += async memberEvent =>
|
||||||
|
{
|
||||||
|
if ((DateTime.UtcNow - _startTime).TotalSeconds < 10) return;
|
||||||
|
if (memberEvent is ModelAddedEvent<PlanetMember> addedEvent)
|
||||||
|
{
|
||||||
|
await WelcomeService.OnMemberJoin(addedEvent.Model, channelCache);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializedPlanets.TryAdd(planet.Id, true);
|
||||||
|
});
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
137
SkyBot/Services/WelcomeService.cs
Normal file
137
SkyBot/Services/WelcomeService.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
using Valour.Sdk.Models;
|
||||||
|
|
||||||
|
namespace SkyBot.Services
|
||||||
|
{
|
||||||
|
public static class WelcomeService
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<long, WelcomeConfig> _cache = new();
|
||||||
|
|
||||||
|
public static async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = DatabaseHelper.GetConnection();
|
||||||
|
using SqliteCommand cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = "SELECT * FROM WelcomeConfigs";
|
||||||
|
using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
var config = new WelcomeConfig
|
||||||
|
{
|
||||||
|
PlanetId = (long)reader["PlanetId"],
|
||||||
|
ChannelId = (long)reader["ChannelId"],
|
||||||
|
Message = (string)reader["Message"],
|
||||||
|
Active = (long)reader["Active"] == 1
|
||||||
|
};
|
||||||
|
_cache[config.PlanetId] = config;
|
||||||
|
}
|
||||||
|
Console.WriteLine("WelcomeService initialized.");
|
||||||
|
Console.WriteLine($"Loaded {_cache.Count} welcome configs from database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task OnMemberJoin(PlanetMember member, ConcurrentDictionary<long, Channel> channelCache)
|
||||||
|
{
|
||||||
|
if (!_cache.TryGetValue(member.PlanetId, out var config)) { Console.WriteLine("No config found"); return; }
|
||||||
|
if (!config.Active) { Console.WriteLine("Not active"); return; }
|
||||||
|
|
||||||
|
Channel? channel = null;
|
||||||
|
|
||||||
|
if (config.ChannelId != 0 && channelCache.TryGetValue(config.ChannelId, out var configChannel))
|
||||||
|
{
|
||||||
|
channel = configChannel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
channel = channelCache.Values.FirstOrDefault(c => c.PlanetId == member.PlanetId && c.IsDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel == null) { Console.WriteLine("No channel found"); return; }
|
||||||
|
|
||||||
|
|
||||||
|
string message = config.Message
|
||||||
|
.Replace("{username}", member.Name)
|
||||||
|
.Replace("{fulluser}", member.User.NameAndTag)
|
||||||
|
.Replace("{nickname}", string.IsNullOrWhiteSpace(member.Nickname) ? member.Name : member.Nickname)
|
||||||
|
.Replace("{mention}", MessageHelper.Mention(member))
|
||||||
|
.Replace("{id}", $"{member.Id}");
|
||||||
|
|
||||||
|
await channel.SendMessageAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SetWelcomeChannel(long planetId, long channelId)
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = DatabaseHelper.GetConnection();
|
||||||
|
using SqliteCommand cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
INSERT INTO WelcomeConfigs (PlanetId, ChannelId) VALUES ($planetId, $channelId)
|
||||||
|
ON CONFLICT(PlanetId) DO UPDATE SET ChannelId = $channelId;
|
||||||
|
";
|
||||||
|
cmd.Parameters.AddWithValue("$planetId", planetId);
|
||||||
|
cmd.Parameters.AddWithValue("$channelId", channelId);
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(planetId, out var config))
|
||||||
|
{
|
||||||
|
config.ChannelId = channelId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache[planetId] = new WelcomeConfig{PlanetId = planetId, ChannelId = channelId};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SetWelcomeMessage(long planetId, string message)
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = DatabaseHelper.GetConnection();
|
||||||
|
using SqliteCommand cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
INSERT INTO WelcomeConfigs (PlanetId, Message) VALUES ($planetId, $message)
|
||||||
|
ON CONFLICT(PlanetId) DO UPDATE SET Message = $message;
|
||||||
|
";
|
||||||
|
cmd.Parameters.AddWithValue("$planetId", planetId);
|
||||||
|
cmd.Parameters.AddWithValue("$message", message);
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(planetId, out var config))
|
||||||
|
{
|
||||||
|
config.Message = message;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache[planetId] = new WelcomeConfig{PlanetId = planetId, Message = message};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SetActive(long planetId, bool active)
|
||||||
|
{
|
||||||
|
using SqliteConnection connection = DatabaseHelper.GetConnection();
|
||||||
|
using SqliteCommand cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
INSERT INTO WelcomeConfigs (PlanetId, Active) VALUES ($planetId, $active)
|
||||||
|
ON CONFLICT(PlanetId) DO UPDATE SET Active = $active;
|
||||||
|
";
|
||||||
|
cmd.Parameters.AddWithValue("$planetId", planetId);
|
||||||
|
cmd.Parameters.AddWithValue("$active", active ? 1 : 0);
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(planetId, out var config))
|
||||||
|
{
|
||||||
|
config.Active = active;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache[planetId] = new WelcomeConfig{PlanetId = planetId, Active = active};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool?> SetActive(long planetId)
|
||||||
|
{
|
||||||
|
if (!_cache.TryGetValue(planetId, out var config)) return null;
|
||||||
|
|
||||||
|
bool newActive = !config.Active;
|
||||||
|
await SetActive(planetId, newActive);
|
||||||
|
return newActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using Valour.Sdk.Client;
|
|||||||
using Valour.Sdk.Models;
|
using Valour.Sdk.Models;
|
||||||
using SkyBot.Services;
|
using SkyBot.Services;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using SkyBot.Helpers;
|
||||||
|
|
||||||
namespace SkyBot
|
namespace SkyBot
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,8 @@ namespace SkyBot
|
|||||||
public async Task StartAsync()
|
public async Task StartAsync()
|
||||||
{
|
{
|
||||||
StartTime = DateTime.UtcNow;
|
StartTime = DateTime.UtcNow;
|
||||||
|
await DatabaseHelper.InitializeAsync();
|
||||||
|
await WelcomeService.InitializeAsync();
|
||||||
await BotService.InitializeBotAsync(_client, _channelCache, _initializedPlanets);
|
await BotService.InitializeBotAsync(_client, _channelCache, _initializedPlanets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +37,6 @@ namespace SkyBot
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await new SkyBot().StartAsync();
|
await new SkyBot().StartAsync();
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("Ready and listening...");
|
Console.WriteLine("Ready and listening...");
|
||||||
await Task.Delay(Timeout.Infinite);
|
await Task.Delay(Timeout.Infinite);
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.2.0.0</Version>
|
<Version>0.2.1.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNetEnv" Version="3.1.1" />
|
<PackageReference Include="DotNetEnv" Version="3.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.5" />
|
||||||
<PackageReference Include="Valour.Sdk" Version="0.5.19" />
|
<PackageReference Include="Valour.Sdk" Version="0.5.19" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user