Socket classes for Hermes

This commit is contained in:
Tom 2024-06-24 22:31:45 +00:00
commit d8522584c4
26 changed files with 434 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
obj/

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.1-dev-10391" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00972" />
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
<PackageReference Include="Serilog.Sinks.Trace" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CommonSocketLibrary\CommonSocketLibrary.csproj" />
</ItemGroup>
</Project>

View File

@ -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<string> emotes)
{
bool good = _quest.Task.Process(_chatterId, message, emotes);
if (good)
{
_counter++;
return _quest.Task.IsCompleted(_counter);
}
return false;
}
}
}

11
Quests/DailyQuest.cs Normal file
View File

@ -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;
}
}
}

42
Quests/Quest.cs Normal file
View File

@ -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;
}
}
}

15
Quests/QuestType.cs Normal file
View File

@ -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
}
}

View File

@ -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<string> emotes)
{
if (string.IsNullOrWhiteSpace(message))
return false;
return emotes.Count > 0;
}
}
}

View File

@ -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<string> emotes)
{
if (string.IsNullOrWhiteSpace(message))
return false;
if (message.Length < 3)
return false;
return true;
}
}
}

11
Quests/Tasks/QuestTask.cs Normal file
View File

@ -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<string> emotes);
public abstract bool IsCompleted(int counter);
}
}

12
Quests/WeeklyQuest.cs Normal file
View File

@ -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;
}
}
}

9
Requests/IRequest.cs Normal file
View File

@ -0,0 +1,9 @@
namespace HermesSocketLibrary.Requests
{
public interface IRequest
{
string Name { get; }
Task<RequestResult> Grant(string sender, IDictionary<string, object> data);
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
namespace HermesSocketLibrary.Requests.Messages
{
public class VoiceDetails
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -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<string, IRequest> _requests;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
public RequestManager(IServiceProvider serviceProvider, ILogger logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
_requests = new ConcurrentDictionary<string, IRequest>();
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<IRequest>(type.Name);
_requests.Add(request.Name, request);
}
}
public async Task<RequestResult> 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);
}
}
}

16
Requests/RequestResult.cs Normal file
View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
namespace HermesSocketLibrary.Socket.Data
{
public class ChatterMessage
{
public long Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace HermesSocketLibrary.Socket.Data
{
public class EmoteDetailsMessage
{
public Dictionary<string, string> Emotes { get; set; }
}
}

View File

@ -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<string> Emotes { get; set; }
public long ChatterId { get; set; }
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
namespace HermesSocketLibrary.Socket.Data
{
public class HeartbeatMessage
{
public DateTime DateTime { get; set; }
public bool Respond { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace HermesSocketLibrary.Socket.Data
{
public class HermesLoginMessage
{
public string ApiKey { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace HermesSocketLibrary.Socket.Data
{
public class LoginAckMessage
{
public string UserId { get; set; }
public bool AnotherClient { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,9 @@
namespace HermesSocketLibrary.Socket.Data
{
public class RequestMessage
{
public string? Type { get; set; }
public IDictionary<string, object>? Data { get; set; }
public string? Nounce { get; set; }
}
}

View File

@ -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; }
}
}