commit c24818505fb2519ceaacd9a5e5b7571666dbd4f1 Author: skyjoshua Date: Sat Feb 28 21:51:34 2026 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..932fb31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/ +obj/ +Reactor.sln +.env \ No newline at end of file diff --git a/Commands/HelpCommand.cs b/Commands/HelpCommand.cs new file mode 100644 index 0000000..493d519 --- /dev/null +++ b/Commands/HelpCommand.cs @@ -0,0 +1,17 @@ +using Valour.Sdk.Models; + +namespace Reactor.Commands; + +public static class HelpComamnd +{ + public static async Task Execute(Dictionary 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}"); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..39a91d6 --- /dev/null +++ b/Program.cs @@ -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 _channelCache = new(); + private HashSet _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); + } + } +} \ No newline at end of file diff --git a/Reactor.csproj b/Reactor.csproj new file mode 100644 index 0000000..f7b91fe --- /dev/null +++ b/Reactor.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + diff --git a/utils.cs b/utils.cs new file mode 100644 index 0000000..8ff18a9 --- /dev/null +++ b/utils.cs @@ -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 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(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(); + } + }; +}; \ No newline at end of file