commit d8522584c4745897a798fd7f3a119f5eb6502629 Author: Tom Date: Mon Jun 24 22:31:45 2024 +0000 Socket classes for Hermes diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbbd0b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ \ No newline at end of file diff --git a/HermesSocketLibrary.csproj b/HermesSocketLibrary.csproj new file mode 100644 index 0000000..33b508e --- /dev/null +++ b/HermesSocketLibrary.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + diff --git a/Quests/ChatterQuestProgression.cs b/Quests/ChatterQuestProgression.cs new file mode 100644 index 0000000..8fbbb14 --- /dev/null +++ b/Quests/ChatterQuestProgression.cs @@ -0,0 +1,33 @@ + +namespace HermesSocketLibrary.Quests +{ + public class ChatterQuestProgression + { + private readonly long _chatterId; + private readonly Quest _quest; + private int _counter; + + public Quest Quest { get => _quest; } + public int Counter { get => _counter; } + + public ChatterQuestProgression(long chatterId, Quest quest) + { + _chatterId = chatterId; + _quest = quest; + _counter = 0; + } + + + public bool Process(string message, HashSet emotes) + { + bool good = _quest.Task.Process(_chatterId, message, emotes); + if (good) + { + _counter++; + + return _quest.Task.IsCompleted(_counter); + } + return false; + } + } +} \ No newline at end of file diff --git a/Quests/DailyQuest.cs b/Quests/DailyQuest.cs new file mode 100644 index 0000000..ab9e782 --- /dev/null +++ b/Quests/DailyQuest.cs @@ -0,0 +1,11 @@ +namespace HermesSocketLibrary.Quests +{ + public class DailyQuest : Quest + { + public DailyQuest(short id, IQuestTask task, DateOnly day) + : base(id, task, 1, day.ToDateTime(TimeOnly.MinValue), day.ToDateTime(TimeOnly.MaxValue)) + { + Type = task.Type | QuestType.Daily; + } + } +} \ No newline at end of file diff --git a/Quests/Quest.cs b/Quests/Quest.cs new file mode 100644 index 0000000..d76be7b --- /dev/null +++ b/Quests/Quest.cs @@ -0,0 +1,42 @@ +namespace HermesSocketLibrary.Quests +{ + public abstract class Quest + { + public short Id { get; } + public IQuestTask Task { get; } + public DateTime StartTime { get; } + public DateTime EndTime { get; } + public QuestType Type { get; protected set; } + public int Rewards { get; } + + public Quest(short id, IQuestTask task, int rewards, DateTime start, DateTime end) + { + Id = id; + Task = task; + Rewards = rewards; + StartTime = start; + EndTime = end; + } + + public bool IsDaily() + { + return Type.HasFlag(QuestType.Daily); + } + + public bool IsWeekly() + { + return Type.HasFlag(QuestType.Weekly); + } + + public bool IsMonthly() + { + return Type.HasFlag(QuestType.Monthly); + } + + public bool IsOngoing() + { + var now = DateTime.UtcNow; + return now >= StartTime && now <= EndTime; + } + } +} \ No newline at end of file diff --git a/Quests/QuestType.cs b/Quests/QuestType.cs new file mode 100644 index 0000000..a6ae19f --- /dev/null +++ b/Quests/QuestType.cs @@ -0,0 +1,15 @@ +namespace HermesSocketLibrary.Quests +{ + [Flags] + public enum QuestType + { + Message = 0x0001, + EmoteMessage = 0x0002, + Redemption = 0x0004, + WatchTime = 0x0008, + + Daily = 0x1000, + Weekly = 0x2000, + Monthly = 0x4000 + } +} \ No newline at end of file diff --git a/Quests/Tasks/EmoteMessageQuestTask.cs b/Quests/Tasks/EmoteMessageQuestTask.cs new file mode 100644 index 0000000..cfb02d6 --- /dev/null +++ b/Quests/Tasks/EmoteMessageQuestTask.cs @@ -0,0 +1,27 @@ +namespace HermesSocketLibrary.Quests.Tasks +{ + public class EmoteMessageQuestTask : IQuestTask + { + public string Name => $"send {Target} messages"; + public QuestType Type { get; set; } + public int Target { get; } + + public EmoteMessageQuestTask(int target) + { + Target = target; + } + + public bool IsCompleted(int counter) + { + return counter >= Target; + } + + public bool Process(long chatterId, string message, HashSet emotes) + { + if (string.IsNullOrWhiteSpace(message)) + return false; + + return emotes.Count > 0; + } + } +} \ No newline at end of file diff --git a/Quests/Tasks/MessageQuestTask.cs b/Quests/Tasks/MessageQuestTask.cs new file mode 100644 index 0000000..30e5894 --- /dev/null +++ b/Quests/Tasks/MessageQuestTask.cs @@ -0,0 +1,30 @@ +namespace HermesSocketLibrary.Quests.Tasks +{ + public class MessageQuestTask : IQuestTask + { + public string Name => $"send {Target} messages"; + public QuestType Type { get; set; } + public int Target { get; } + + public MessageQuestTask(int target) + { + Target = target; + } + + public bool IsCompleted(int counter) + { + return counter >= Target; + } + + public bool Process(long chatterId, string message, HashSet emotes) + { + if (string.IsNullOrWhiteSpace(message)) + return false; + + if (message.Length < 3) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Quests/Tasks/QuestTask.cs b/Quests/Tasks/QuestTask.cs new file mode 100644 index 0000000..9f2ecb9 --- /dev/null +++ b/Quests/Tasks/QuestTask.cs @@ -0,0 +1,11 @@ +namespace HermesSocketLibrary.Quests +{ + public interface IQuestTask + { + public string Name { get; } + public QuestType Type { get; set; } + public int Target { get; } + public abstract bool Process(long chatterId, string message, HashSet emotes); + public abstract bool IsCompleted(int counter); + } +} \ No newline at end of file diff --git a/Quests/WeeklyQuest.cs b/Quests/WeeklyQuest.cs new file mode 100644 index 0000000..842e982 --- /dev/null +++ b/Quests/WeeklyQuest.cs @@ -0,0 +1,12 @@ + +namespace HermesSocketLibrary.Quests +{ + public class WeeklyQuest : Quest + { + public WeeklyQuest(short id, IQuestTask task, DateOnly start) + : base(id, task, 5, start.ToDateTime(TimeOnly.MinValue), start.AddDays(6).ToDateTime(TimeOnly.MaxValue)) + { + Type = task.Type | QuestType.Weekly; + } + } +} \ No newline at end of file diff --git a/Requests/IRequest.cs b/Requests/IRequest.cs new file mode 100644 index 0000000..f44bf94 --- /dev/null +++ b/Requests/IRequest.cs @@ -0,0 +1,9 @@ +namespace HermesSocketLibrary.Requests +{ + public interface IRequest + { + string Name { get; } + + Task Grant(string sender, IDictionary data); + } +} \ No newline at end of file diff --git a/Requests/Messages/EmoteInfo.cs b/Requests/Messages/EmoteInfo.cs new file mode 100644 index 0000000..79666ef --- /dev/null +++ b/Requests/Messages/EmoteInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace HermesSocketLibrary.Requests.Messages +{ + public class EmoteInfo + { + public string Id { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Requests/Messages/TTSWordFilter.cs b/Requests/Messages/TTSWordFilter.cs new file mode 100644 index 0000000..394a3f1 --- /dev/null +++ b/Requests/Messages/TTSWordFilter.cs @@ -0,0 +1,17 @@ +namespace HermesSocketLibrary.Requests.Messages +{ + public class TTSWordFilter + { + public string? Id { get; set; } + public string? Search { get; set; } + public string? Replace { get; set; } + + public bool IsRegex { get; set; } + + + public TTSWordFilter() + { + IsRegex = true; + } + } +} \ No newline at end of file diff --git a/Requests/Messages/VoiceDetails.cs b/Requests/Messages/VoiceDetails.cs new file mode 100644 index 0000000..b89b1a6 --- /dev/null +++ b/Requests/Messages/VoiceDetails.cs @@ -0,0 +1,8 @@ +namespace HermesSocketLibrary.Requests.Messages +{ + public class VoiceDetails + { + public string Id { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Requests/RequestManager.cs b/Requests/RequestManager.cs new file mode 100644 index 0000000..bdddd10 --- /dev/null +++ b/Requests/RequestManager.cs @@ -0,0 +1,62 @@ +using System.Collections.Concurrent; +using System.Reflection; +using HermesSocketLibrary.Socket.Data; +using Microsoft.Extensions.DependencyInjection; +using Serilog; + +namespace HermesSocketLibrary.Requests +{ + public abstract class RequestManager + { + private readonly IDictionary _requests; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + + public RequestManager(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + + _requests = new ConcurrentDictionary(); + LoadRequests(); + } + + protected abstract string AssemblyName { get; } + + private void LoadRequests() + { + Type basetype = typeof(IRequest); + var types = Assembly.Load(AssemblyName).GetTypes().Where(t => t.IsClass && !t.IsAbstract && basetype.IsAssignableFrom(t)); + + foreach (var type in types) + { + _logger.Debug($"Loading IRequest for '{type.Name}'."); + var request = _serviceProvider.GetRequiredKeyedService(type.Name); + _requests.Add(request.Name, request); + } + } + + public async Task Grant(string sender, RequestMessage? message) + { + if (message == null || message.Type == null) + return new RequestResult(false, null); + + if (!_requests.TryGetValue(message.Type, out IRequest? request) || request == null) + { + _logger.Warning($"Did not find request type '{message.Type}'."); + return new RequestResult(false, null); + } + + try + { + return await request.Grant(sender, message.Data); + } + catch (Exception e) + { + _logger.Error(e, $"Failed to grant a request of type '{message.Type}'."); + } + return new RequestResult(false, null); + } + } +} \ No newline at end of file diff --git a/Requests/RequestResult.cs b/Requests/RequestResult.cs new file mode 100644 index 0000000..39537ee --- /dev/null +++ b/Requests/RequestResult.cs @@ -0,0 +1,16 @@ +namespace HermesSocketLibrary.Requests +{ + public class RequestResult + { + public bool Success; + public object? Result; + public bool NotifyClientsOnAccount; + + public RequestResult(bool success, object? result, bool notifyClientsOnAccount = true) + { + Success = success; + Result = result; + NotifyClientsOnAccount = notifyClientsOnAccount; + } + } +} \ No newline at end of file diff --git a/Socket/Data/ChatterMessage.cs b/Socket/Data/ChatterMessage.cs new file mode 100644 index 0000000..0de9b54 --- /dev/null +++ b/Socket/Data/ChatterMessage.cs @@ -0,0 +1,8 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class ChatterMessage + { + public long Id { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/EmoteDetailsMessage.cs b/Socket/Data/EmoteDetailsMessage.cs new file mode 100644 index 0000000..cdccf95 --- /dev/null +++ b/Socket/Data/EmoteDetailsMessage.cs @@ -0,0 +1,7 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class EmoteDetailsMessage + { + public Dictionary Emotes { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/EmoteUsageMessage.cs b/Socket/Data/EmoteUsageMessage.cs new file mode 100644 index 0000000..fb2ef7d --- /dev/null +++ b/Socket/Data/EmoteUsageMessage.cs @@ -0,0 +1,11 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class EmoteUsageMessage + { + public string MessageId { get; set; } + public DateTime DateTime { get; set; } + public long BroadcasterId { get; set; } + public HashSet Emotes { get; set; } + public long ChatterId { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/ErrorMessage.cs b/Socket/Data/ErrorMessage.cs new file mode 100644 index 0000000..7d6711b --- /dev/null +++ b/Socket/Data/ErrorMessage.cs @@ -0,0 +1,20 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class ErrorMessage + { + public Exception? Exception { get; set; } + public string Message { get; set; } + + public ErrorMessage(Exception exception, string message) + { + Exception = exception; + Message = message; + } + + public ErrorMessage(string message) + { + Message = message; + Exception = null; + } + } +} \ No newline at end of file diff --git a/Socket/Data/HeartbeatMessage.cs b/Socket/Data/HeartbeatMessage.cs new file mode 100644 index 0000000..bfb64cf --- /dev/null +++ b/Socket/Data/HeartbeatMessage.cs @@ -0,0 +1,8 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class HeartbeatMessage + { + public DateTime DateTime { get; set; } + public bool Respond { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/HermesLoginMessage.cs b/Socket/Data/HermesLoginMessage.cs new file mode 100644 index 0000000..5d602c0 --- /dev/null +++ b/Socket/Data/HermesLoginMessage.cs @@ -0,0 +1,7 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class HermesLoginMessage + { + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/LoginAckMessage.cs b/Socket/Data/LoginAckMessage.cs new file mode 100644 index 0000000..372830b --- /dev/null +++ b/Socket/Data/LoginAckMessage.cs @@ -0,0 +1,8 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class LoginAckMessage + { + public string UserId { get; set; } + public bool AnotherClient { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/RequestAckMessage.cs b/Socket/Data/RequestAckMessage.cs new file mode 100644 index 0000000..4e78142 --- /dev/null +++ b/Socket/Data/RequestAckMessage.cs @@ -0,0 +1,9 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class RequestAckMessage + { + public RequestMessage? Request { get; set; } + public string? Nounce { get; set; } + public object? Data { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/RequestMessage.cs b/Socket/Data/RequestMessage.cs new file mode 100644 index 0000000..80492df --- /dev/null +++ b/Socket/Data/RequestMessage.cs @@ -0,0 +1,9 @@ +namespace HermesSocketLibrary.Socket.Data +{ + public class RequestMessage + { + public string? Type { get; set; } + public IDictionary? Data { get; set; } + public string? Nounce { get; set; } + } +} \ No newline at end of file diff --git a/Socket/Data/SocketMessage.cs b/Socket/Data/SocketMessage.cs new file mode 100644 index 0000000..a294a58 --- /dev/null +++ b/Socket/Data/SocketMessage.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace HermesSocketLibrary.Socket.Data +{ + public class SocketMessage + { + [JsonPropertyName("op")] + public int? OpCode { get; set; } + [JsonPropertyName("d")] + public object? Data { get; set; } + } +} \ No newline at end of file