Files
EchoHub/src/EchoHub.Server.Irc/IrcBroadcaster.cs
T

144 lines
5.4 KiB
C#

using EchoHub.Core.Contracts;
using EchoHub.Core.DTOs;
namespace EchoHub.Server.Irc;
public class IrcBroadcaster : IChatBroadcaster
{
private readonly IrcGatewayService _gateway;
private readonly IMessageEncryptionService _encryption;
public IrcBroadcaster(IrcGatewayService gateway, IMessageEncryptionService encryption)
{
_gateway = gateway;
_encryption = encryption;
}
public async Task SendMessageToChannelAsync(string channelName, MessageDto message, string? excludeConnectionId = null)
{
// Decrypt transport-encrypted content for IRC clients (they can't handle
// app-layer encryption). E2E room ciphertext ($RC1$) passes through untouched.
var decryptedMessage = message with { Content = _encryption.Decrypt(message.Content) };
var lines = IrcMessageFormatter.FormatMessage(decryptedMessage, _gateway.Options.PublicBaseUrl);
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
// IRC convention: don't echo a message back to the connection that sent it
// (its client already displayed it locally). Match by connection id, not
// nickname — the same account may also be online via the TUI or a second
// IRC client, and those sessions must still receive the message.
if (conn.ConnectionId == excludeConnectionId)
continue;
foreach (var line in lines)
await conn.SendAsync(line);
}
}
public async Task SendUserJoinedAsync(string channelName, string username, UserPresenceDto? presence, string? excludeConnectionId = null)
{
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
if (conn.ConnectionId == excludeConnectionId) continue;
await conn.SendAsync($":{username}!{username}@echohub JOIN #{channelName}");
}
}
public async Task SendUserLeftAsync(string channelName, string username)
{
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
if (conn.Nickname == username) continue;
await conn.SendAsync($":{username}!{username}@echohub PART #{channelName}");
}
}
public async Task SendChannelUpdatedAsync(ChannelDto channel, string? channelName = null)
{
var target = channelName ?? channel.Name;
if (channel.Topic is null) return;
foreach (var conn in _gateway.GetConnectionsInChannel(target))
{
await conn.SendAsync($":{_gateway.Options.ServerName} TOPIC #{channel.Name} :{channel.Topic}");
}
}
public Task SendUserStatusChangedAsync(List<string> channelNames, UserPresenceDto presence)
{
// IRC has no active status broadcast. Clients discover away via WHOIS/WHO.
return Task.CompletedTask;
}
public async Task SendUserKickedAsync(string channelName, string username, string? reason)
{
var reasonText = reason is not null ? $" :{reason}" : "";
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
await conn.SendAsync($":{_gateway.Options.ServerName} KICK #{channelName} {username}{reasonText}");
}
}
public async Task SendUserBannedAsync(string username, string? reason)
{
var reasonText = reason ?? "You have been banned.";
foreach (var conn in _gateway.GetAllConnections())
{
if (conn.Nickname == username)
await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {username} :You have been banned: {reasonText}");
}
}
public async Task SendMessageDeletedAsync(string channelName, Guid messageId)
{
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {conn.Nickname ?? "*"} :Message {messageId} was deleted in #{channelName}");
}
}
public async Task SendChannelDeletedAsync(string channelName)
{
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {conn.Nickname ?? "*"} :Channel #{channelName} has been deleted");
}
}
public async Task SendChannelNukedAsync(string channelName)
{
foreach (var conn in _gateway.GetConnectionsInChannel(channelName))
{
await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {conn.Nickname ?? "*"} :All messages in #{channelName} have been cleared");
}
}
public async Task SendErrorAsync(string connectionId, string message)
{
if (!connectionId.StartsWith("irc-")) return;
if (_gateway.Connections.TryGetValue(connectionId, out var conn))
{
await conn.SendAsync($":{_gateway.Options.ServerName} NOTICE {conn.Nickname ?? "*"} :{message}");
}
}
public async Task ForceDisconnectUserAsync(List<string> connectionIds, string reason)
{
foreach (var connId in connectionIds)
{
if (!connId.StartsWith("irc-")) continue;
if (_gateway.Connections.TryGetValue(connId, out var conn))
{
try
{
await conn.SendAsync($"ERROR :Closing Link: {reason}");
await conn.DisposeAsync();
}
catch { /* connection may already be closed */ }
}
}
}
}