feat: announce avatar events

This commit is contained in:
Oliver Booth 2023-08-26 13:03:42 +01:00
parent 69edcfe3f5
commit 7a6ae083da
Signed by: oliverbooth
GPG Key ID: B89D139977693FED
5 changed files with 127 additions and 14 deletions

View File

@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Text.RegularExpressions;
using Discord;
using Discord.Interactions;
@ -9,7 +9,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using VPLink.Commands;
using VpSharp;
using VpSharp.Entities;
namespace VPLink.Services;
@ -25,7 +24,6 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
private readonly IConfiguration _configuration;
private readonly InteractionService _interactionService;
private readonly DiscordSocketClient _discordClient;
private readonly VirtualParadiseClient _virtualParadiseClient;
private readonly Subject<IUserMessage> _messageReceived = new();
/// <summary>
@ -36,20 +34,17 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
/// <param name="configuration">The configuration.</param>
/// <param name="interactionService">The interaction service.</param>
/// <param name="discordClient">The Discord client.</param>
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
public DiscordService(ILogger<DiscordService> logger,
IServiceProvider serviceProvider,
IConfiguration configuration,
InteractionService interactionService,
DiscordSocketClient discordClient,
VirtualParadiseClient virtualParadiseClient)
DiscordSocketClient discordClient)
{
_logger = logger;
_serviceProvider = serviceProvider;
_configuration = configuration;
_interactionService = interactionService;
_discordClient = discordClient;
_virtualParadiseClient = virtualParadiseClient;
}
/// <inheritdoc />
@ -114,6 +109,38 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
return _interactionService.RegisterCommandsGloballyAsync();
}
/// <inheritdoc />
public Task AnnounceArrival(VirtualParadiseAvatar avatar)
{
if (avatar is null) throw new ArgumentNullException(nameof(avatar));
if (!TryGetRelayChannel(out ITextChannel? channel)) return Task.CompletedTask;
var embed = new EmbedBuilder();
embed.WithColor(0x00FF00);
embed.WithTitle("📥 Avatar Joined");
embed.WithDescription(avatar.Name);
embed.WithTimestamp(DateTimeOffset.UtcNow);
embed.WithFooter($"Session {avatar.Session}");
return channel.SendMessageAsync(embed: embed.Build());
}
/// <inheritdoc />
public Task AnnounceDeparture(VirtualParadiseAvatar avatar)
{
if (avatar is null) throw new ArgumentNullException(nameof(avatar));
if (!TryGetRelayChannel(out ITextChannel? channel)) return Task.CompletedTask;
var embed = new EmbedBuilder();
embed.WithColor(0xFF0000);
embed.WithTitle("📤 Avatar Left");
embed.WithDescription(avatar.Name);
embed.WithTimestamp(DateTimeOffset.UtcNow);
embed.WithFooter($"Session {avatar.Session}");
return channel.SendMessageAsync(embed: embed.Build());
}
/// <inheritdoc />
public Task SendMessageAsync(VirtualParadiseMessage message)
{
@ -134,12 +161,7 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
_logger.LogInformation("Message by {Author}: {Content}", author, message.Content);
var channelId = _configuration.GetSection("Discord:ChannelId").Get<ulong>();
if (_discordClient.GetChannel(channelId) is not ITextChannel channel)
{
_logger.LogError("Channel {ChannelId} does not exist", channelId);
return Task.CompletedTask;
}
if (!TryGetRelayChannel(out ITextChannel? channel)) return Task.CompletedTask;
string unescaped = UnescapeRegex.Replace(message.Content, "$1");
string escaped = EscapeRegex.Replace(unescaped, "\\$1");
@ -148,6 +170,20 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
return channel.SendMessageAsync($"**{displayName}**: {escaped}");
}
private bool TryGetRelayChannel([NotNullWhen(true)] out ITextChannel? channel)
{
var channelId = _configuration.GetValue<ulong>("Discord:ChannelId");
if (_discordClient.GetChannel(channelId) is ITextChannel textChannel)
{
channel = textChannel;
return true;
}
_logger.LogError("Channel {ChannelId} does not exist", channelId);
channel = null;
return false;
}
[GeneratedRegex(@"\\(\*|_|`|~|\\)", RegexOptions.Compiled)]
private static partial Regex GetUnescapeRegex();

View File

@ -14,10 +14,24 @@ public interface IDiscordService
/// <value>An observable that is triggered when a message is received from the Discord channel.</value>
IObservable<IUserMessage> OnMessageReceived { get; }
/// <summary>
/// Announces the arrival of an avatar.
/// </summary>
/// <param name="avatar">The avatar.</param>
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
Task AnnounceArrival(VirtualParadiseAvatar avatar);
/// <summary>
/// Announces the arrival of an avatar.
/// </summary>
/// <param name="avatar">The avatar.</param>
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
Task AnnounceDeparture(VirtualParadiseAvatar avatar);
/// <summary>
/// Sends a message to the Discord channel.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
Task SendMessageAsync(VirtualParadiseMessage message);
}
}

View File

@ -8,6 +8,22 @@ namespace VPLink.Services;
/// </summary>
public interface IVirtualParadiseService
{
/// <summary>
/// Gets an observable that is triggered when an avatar enters the Virtual Paradise world.
/// </summary>
/// <value>
/// An observable that is triggered when an avatar enters the Virtual Paradise world.
/// </value>
IObservable<VirtualParadiseAvatar> OnAvatarJoined { get; }
/// <summary>
/// Gets an observable that is triggered when an avatar exits the Virtual Paradise world.
/// </summary>
/// <value>
/// An observable that is triggered when an avatar exits the Virtual Paradise world.
/// </value>
IObservable<VirtualParadiseAvatar> OnAvatarLeft { get; }
/// <summary>
/// Gets an observable that is triggered when a message is received from the Virtual Paradise world server.
/// </summary>

View File

@ -45,6 +45,9 @@ internal sealed class RelayService : BackgroundService
.Where(m => m.Author != _discordClient.CurrentUser)
.SubscribeAsync(_virtualParadiseService.SendMessageAsync);
_virtualParadiseService.OnAvatarJoined.SubscribeAsync(_discordService.AnnounceArrival);
_virtualParadiseService.OnAvatarLeft.SubscribeAsync(_discordService.AnnounceDeparture);
_virtualParadiseService.OnMessageReceived
.Where(m => m.Author != _virtualParadiseClient.CurrentAvatar)
.SubscribeAsync(_discordService.SendMessageAsync);

View File

@ -17,6 +17,8 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
private readonly IConfiguration _configuration;
private readonly VirtualParadiseClient _virtualParadiseClient;
private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
private readonly Subject<VirtualParadiseAvatar> _avatarJoined = new();
private readonly Subject<VirtualParadiseAvatar> _avatarLeft = new();
/// <summary>
/// Initializes a new instance of the <see cref="VirtualParadiseService" /> class.
@ -33,6 +35,12 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
_virtualParadiseClient = virtualParadiseClient;
}
/// <inheritdoc />
public IObservable<VirtualParadiseAvatar> OnAvatarJoined => _avatarJoined.AsObservable();
/// <inheritdoc />
public IObservable<VirtualParadiseAvatar> OnAvatarLeft => _avatarJoined.AsObservable();
/// <inheritdoc />
public IObservable<VirtualParadiseMessage> OnMessageReceived => _messageReceived.AsObservable();
@ -59,6 +67,8 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
{
_logger.LogInformation("Establishing relay");
_virtualParadiseClient.MessageReceived.Subscribe(_messageReceived);
_virtualParadiseClient.AvatarJoined.Subscribe(OnVirtualParadiseAvatarJoined);
_virtualParadiseClient.AvatarLeft.Subscribe(OnVirtualParadiseAvatarLeft);
string username = _configuration.GetSection("VirtualParadise:Username").Value ??
throw new InvalidOperationException("Username is not set.");
@ -76,4 +86,38 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
_logger.LogInformation("Entering world {World}", world);
await _virtualParadiseClient.EnterAsync(world).ConfigureAwait(false);
}
private void OnVirtualParadiseAvatarJoined(VirtualParadiseAvatar avatar)
{
if (!_configuration.GetValue<bool>("Bot:AnnounceAvatarEvents"))
{
_logger.LogDebug("Join/leave events are disabled, ignoring event");
return;
}
if (avatar.IsBot && !_configuration.GetSection("Bot:AnnounceBots").Get<bool>())
{
_logger.LogDebug("Bot events are disabled, ignoring event");
return;
}
_avatarJoined.OnNext(avatar);
}
private void OnVirtualParadiseAvatarLeft(VirtualParadiseAvatar avatar)
{
if (!_configuration.GetValue<bool>("Bot:AnnounceAvatarEvents"))
{
_logger.LogDebug("Join/leave events are disabled, ignoring event");
return;
}
if (avatar.IsBot && !_configuration.GetSection("Bot:AnnounceBots").Get<bool>())
{
_logger.LogDebug("Bot events are disabled, ignoring event");
return;
}
_avatarLeft.OnNext(avatar);
}
}