mirror of
https://github.com/oliverbooth/VPLink
synced 2024-11-09 23:45:40 +00:00
feat: announce avatar events
This commit is contained in:
parent
69edcfe3f5
commit
7a6ae083da
@ -1,6 +1,6 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Interactions;
|
using Discord.Interactions;
|
||||||
@ -9,7 +9,6 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using VPLink.Commands;
|
using VPLink.Commands;
|
||||||
using VpSharp;
|
|
||||||
using VpSharp.Entities;
|
using VpSharp.Entities;
|
||||||
|
|
||||||
namespace VPLink.Services;
|
namespace VPLink.Services;
|
||||||
@ -25,7 +24,6 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly InteractionService _interactionService;
|
private readonly InteractionService _interactionService;
|
||||||
private readonly DiscordSocketClient _discordClient;
|
private readonly DiscordSocketClient _discordClient;
|
||||||
private readonly VirtualParadiseClient _virtualParadiseClient;
|
|
||||||
private readonly Subject<IUserMessage> _messageReceived = new();
|
private readonly Subject<IUserMessage> _messageReceived = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,20 +34,17 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
|
|||||||
/// <param name="configuration">The configuration.</param>
|
/// <param name="configuration">The configuration.</param>
|
||||||
/// <param name="interactionService">The interaction service.</param>
|
/// <param name="interactionService">The interaction service.</param>
|
||||||
/// <param name="discordClient">The Discord client.</param>
|
/// <param name="discordClient">The Discord client.</param>
|
||||||
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
|
|
||||||
public DiscordService(ILogger<DiscordService> logger,
|
public DiscordService(ILogger<DiscordService> logger,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
InteractionService interactionService,
|
InteractionService interactionService,
|
||||||
DiscordSocketClient discordClient,
|
DiscordSocketClient discordClient)
|
||||||
VirtualParadiseClient virtualParadiseClient)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_interactionService = interactionService;
|
_interactionService = interactionService;
|
||||||
_discordClient = discordClient;
|
_discordClient = discordClient;
|
||||||
_virtualParadiseClient = virtualParadiseClient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -114,6 +109,38 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
|
|||||||
return _interactionService.RegisterCommandsGloballyAsync();
|
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 />
|
/// <inheritdoc />
|
||||||
public Task SendMessageAsync(VirtualParadiseMessage message)
|
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);
|
_logger.LogInformation("Message by {Author}: {Content}", author, message.Content);
|
||||||
|
|
||||||
var channelId = _configuration.GetSection("Discord:ChannelId").Get<ulong>();
|
if (!TryGetRelayChannel(out ITextChannel? channel)) return Task.CompletedTask;
|
||||||
if (_discordClient.GetChannel(channelId) is not ITextChannel channel)
|
|
||||||
{
|
|
||||||
_logger.LogError("Channel {ChannelId} does not exist", channelId);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
string unescaped = UnescapeRegex.Replace(message.Content, "$1");
|
string unescaped = UnescapeRegex.Replace(message.Content, "$1");
|
||||||
string escaped = EscapeRegex.Replace(unescaped, "\\$1");
|
string escaped = EscapeRegex.Replace(unescaped, "\\$1");
|
||||||
@ -148,6 +170,20 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
|
|||||||
return channel.SendMessageAsync($"**{displayName}**: {escaped}");
|
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)]
|
[GeneratedRegex(@"\\(\*|_|`|~|\\)", RegexOptions.Compiled)]
|
||||||
private static partial Regex GetUnescapeRegex();
|
private static partial Regex GetUnescapeRegex();
|
||||||
|
|
||||||
|
@ -14,6 +14,20 @@ public interface IDiscordService
|
|||||||
/// <value>An observable that is triggered when a message is received from the Discord channel.</value>
|
/// <value>An observable that is triggered when a message is received from the Discord channel.</value>
|
||||||
IObservable<IUserMessage> OnMessageReceived { get; }
|
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>
|
/// <summary>
|
||||||
/// Sends a message to the Discord channel.
|
/// Sends a message to the Discord channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,6 +8,22 @@ namespace VPLink.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IVirtualParadiseService
|
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>
|
/// <summary>
|
||||||
/// Gets an observable that is triggered when a message is received from the Virtual Paradise world server.
|
/// Gets an observable that is triggered when a message is received from the Virtual Paradise world server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -45,6 +45,9 @@ internal sealed class RelayService : BackgroundService
|
|||||||
.Where(m => m.Author != _discordClient.CurrentUser)
|
.Where(m => m.Author != _discordClient.CurrentUser)
|
||||||
.SubscribeAsync(_virtualParadiseService.SendMessageAsync);
|
.SubscribeAsync(_virtualParadiseService.SendMessageAsync);
|
||||||
|
|
||||||
|
_virtualParadiseService.OnAvatarJoined.SubscribeAsync(_discordService.AnnounceArrival);
|
||||||
|
_virtualParadiseService.OnAvatarLeft.SubscribeAsync(_discordService.AnnounceDeparture);
|
||||||
|
|
||||||
_virtualParadiseService.OnMessageReceived
|
_virtualParadiseService.OnMessageReceived
|
||||||
.Where(m => m.Author != _virtualParadiseClient.CurrentAvatar)
|
.Where(m => m.Author != _virtualParadiseClient.CurrentAvatar)
|
||||||
.SubscribeAsync(_discordService.SendMessageAsync);
|
.SubscribeAsync(_discordService.SendMessageAsync);
|
||||||
|
@ -17,6 +17,8 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly VirtualParadiseClient _virtualParadiseClient;
|
private readonly VirtualParadiseClient _virtualParadiseClient;
|
||||||
private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
|
private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
|
||||||
|
private readonly Subject<VirtualParadiseAvatar> _avatarJoined = new();
|
||||||
|
private readonly Subject<VirtualParadiseAvatar> _avatarLeft = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VirtualParadiseService" /> class.
|
/// Initializes a new instance of the <see cref="VirtualParadiseService" /> class.
|
||||||
@ -33,6 +35,12 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
|
|||||||
_virtualParadiseClient = virtualParadiseClient;
|
_virtualParadiseClient = virtualParadiseClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IObservable<VirtualParadiseAvatar> OnAvatarJoined => _avatarJoined.AsObservable();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IObservable<VirtualParadiseAvatar> OnAvatarLeft => _avatarJoined.AsObservable();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IObservable<VirtualParadiseMessage> OnMessageReceived => _messageReceived.AsObservable();
|
public IObservable<VirtualParadiseMessage> OnMessageReceived => _messageReceived.AsObservable();
|
||||||
|
|
||||||
@ -59,6 +67,8 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Establishing relay");
|
_logger.LogInformation("Establishing relay");
|
||||||
_virtualParadiseClient.MessageReceived.Subscribe(_messageReceived);
|
_virtualParadiseClient.MessageReceived.Subscribe(_messageReceived);
|
||||||
|
_virtualParadiseClient.AvatarJoined.Subscribe(OnVirtualParadiseAvatarJoined);
|
||||||
|
_virtualParadiseClient.AvatarLeft.Subscribe(OnVirtualParadiseAvatarLeft);
|
||||||
|
|
||||||
string username = _configuration.GetSection("VirtualParadise:Username").Value ??
|
string username = _configuration.GetSection("VirtualParadise:Username").Value ??
|
||||||
throw new InvalidOperationException("Username is not set.");
|
throw new InvalidOperationException("Username is not set.");
|
||||||
@ -76,4 +86,38 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
|
|||||||
_logger.LogInformation("Entering world {World}", world);
|
_logger.LogInformation("Entering world {World}", world);
|
||||||
await _virtualParadiseClient.EnterAsync(world).ConfigureAwait(false);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user