Initial commit

This commit is contained in:
2026-02-28 21:51:34 +00:00
commit c24818505f
5 changed files with 254 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
bin/
obj/
Reactor.sln
.env

17
Commands/HelpCommand.cs Normal file
View File

@@ -0,0 +1,17 @@
using Valour.Sdk.Models;
namespace Reactor.Commands;
public static class HelpComamnd
{
public static async Task Execute(Dictionary<long, Channel> channelCache, long channelId, String prefix, string memberPing)
{
string helpMessage = $@"**Reactor Commands**:
- `{prefix}help` - Shows this list.";
if (channelCache.TryGetValue(channelId, out var channel))
{
await channel.SendMessageAsync($"{memberPing}\n{helpMessage}");
}
}
}

135
Program.cs Normal file
View File

@@ -0,0 +1,135 @@
using Valour.Sdk.Client;
using Valour.Sdk.Models;
using DotNetEnv;
using Valour.Shared.Models;
using Reactor.Commands;
namespace Reactor
{
public class Reactor
{
private ValourClient _client;
private Dictionary<long, Channel> _channelCache = new();
private HashSet<long> _initializedPlanets = new();
private string _prefix = "r.";
public Reactor(string token)
{
Env.Load();
_client = new ValourClient("https://api.valour.gg/");
_client.SetupHttpClient();
InitializeBotAsync(token).GetAwaiter().GetResult();
}
//Initialize the bot.
private async Task InitializeBotAsync(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
Console.WriteLine("TOKEN not set.");
return;
}
var loginResult = await _client.InitializeUser(token);
if (!loginResult.Success)
{
Console.WriteLine($"Login failed: {loginResult.Message}");
return;
}
Console.WriteLine($"Logged in as {_client.Me.Name} (ID: {_client.Me.Id})");
await InitializePlanetsAsync();
_client.PlanetService.JoinedPlanetsUpdated += async () =>
{
await InitializePlanetsAsync();
};
_client.MessageService.MessageReceived += async (msg) => await HandleMessageAsync(msg);
Console.WriteLine("Bot ready and listening...");
}
//Initalize the planets.
private async Task InitializePlanetsAsync()
{
foreach (var planet in _client.PlanetService.JoinedPlanets)
{
if (_initializedPlanets.Contains(planet.Id))
continue;
Console.WriteLine($"Initializing Planet: {planet.Name}");
await planet.EnsureReadyAsync();
await planet.FetchInitialDataAsync();
foreach (var channel in planet.Channels)
{
_channelCache[channel.Id] = channel;
if (channel.ChannelType == ChannelTypeEnum.PlanetChat)
{
await channel.OpenWithResult("Reactor");
Console.WriteLine($"Realtime opened for: {planet.Name} -> {channel.Name}");
}
}
_initializedPlanets.Add(planet.Id);
}
}
//Message handler.
private async Task HandleMessageAsync(Message message)
{
if (message.AuthorUserId == _client.Me.Id) return;
string content = message.Content ?? "";
if (string.IsNullOrWhiteSpace(content)) return;
if (!content.StartsWith(_prefix)) return;
long channelId = message.ChannelId;
var member = await message.FetchAuthorMemberAsync();
string memberPing = member != null ? $"«@m-{member.Id}»" : "";
string withoutPrefix = content.Substring(_prefix.Length);
var parts = withoutPrefix.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) return;
string command = parts[0].ToLower();
string[] args = parts[1..];
switch (command)
{
case "help":
await HelpComamnd.Execute(_channelCache, channelId, _prefix, memberPing);
break;
}
}
}
//Because it required a main or something idk I hate C# :)
public class Program
{
public static async Task Main(string[] args)
{
Env.Load();
var token = Environment.GetEnvironmentVariable("TOKEN");
if (string.IsNullOrWhiteSpace(token))
{
Console.WriteLine("TOKEN not set.");
return;
}
var bot = new Reactor(token);
await Task.Delay(Timeout.Infinite);
}
}
}

16
Reactor.csproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
<PackageReference Include="Valour.Sdk" Version="0.5.19" />
</ItemGroup>
</Project>

82
utils.cs Normal file
View File

@@ -0,0 +1,82 @@
using System.Globalization;
using System.Text.Json;
using Valour.Sdk.Models;
namespace Reactor
{
public static class Utils
{
private static readonly HttpClient _http = new HttpClient();
private static long _valourUserCount;
public static long ValourUserCount => _valourUserCount;
public static bool IsSingleEmoji(string input)
{
if (string.IsNullOrWhiteSpace(input))
return false;
input = input.Trim();
var enumerator = StringInfo.GetTextElementEnumerator(input);
int count = 0;
while (enumerator.MoveNext())
count++;
return count == 1;
}
public static bool ContainsAny(string input, params string[] values)
{
var lower = input.ToLower();
foreach (var value in values)
{
if (lower.Contains(value.ToLower()))
return true;
};
return false;
}
public static async Task SendReplyAsync(Dictionary<long, Channel> channelCache, long channel, string reply)
{
if (channelCache.TryGetValue(channel, out var chan))
{
await chan.SendMessageAsync(reply);
}
else
{
Console.WriteLine($"Channel {channel} was not found in the cache.");
};
}
public static async Task UpdateValourUserCountAsync()
{
try
{
var response = await _http.GetStringAsync("https://api.valour.gg/api/users/count");
_valourUserCount = JsonSerializer.Deserialize<long>(response);
Console.WriteLine($"Valour user count updated: {_valourUserCount}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to update Valour user count: {ex.Message}");
}
}
public static void StartValourUserUpdater()
{
var timer = new System.Timers.Timer(300_000);
timer.Elapsed += async (_, _) => await UpdateValourUserCountAsync();
timer.AutoReset = true;
timer.Start();
}
};
};