-Class UpdateProgressDialog
+
+Class NickColorHelper
-
+
- Assembly
- EchoHub.Client.dll
-
+ Deterministic per-nick colors for users who haven't picked a nickname color.
+The same nick always maps to the same palette entry (classic IRC client behavior),
+so a busy channel stays scannable without any configuration.
+
- public sealed class UpdateProgressDialog
+ public static class NickColorHelper
@@ -111,7 +114,7 @@ Class UpdateProgressDialog object
- UpdateProgressDialog
+ NickColorHelper
@@ -123,6 +126,9 @@ Class UpdateProgressDialog object.GetType()
+
@@ -145,121 +151,78 @@ Class UpdateProgressDialog Constructors
-
-
-
-
-
-
- UpdateProgressDialog(IApplication, string)
-
-
-
-
-
-
-
- public UpdateProgressDialog(IApplication app, string newVersion)
-
-
- Parameters
-
- app IApplication
-
- newVersion string
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Methods
-
+
-
- Close()
-
+
+ GetAttribute(string)
+
-
+ The color attribute for a nick. Used as a fallback when the user has no
+explicit nickname color set.
+
- public void Close()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Show()
-
-
-
-
-
-
-
- public void Show()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- UpdateProgress(float, string)
-
-
-
-
-
-
-
- public void UpdateProgress(float fraction, string statusText)
+ public static Attribute GetAttribute(string nick)
Parameters
- fraction float
-
- statusText string
+ nick string
+ Returns
+
+ - Attribute
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GetPaletteIndex(string, int)
+
+
+
+ Stable palette index for a nick: case-insensitive FNV-1a over the nick,
+reduced modulo paletteSize. Pure function (no Terminal.Gui
+types) so it is unit-testable without a display driver.
+
+
+
+
+ public static int GetPaletteIndex(string nick, int paletteSize)
+
+
+ Parameters
+
+ nick string
+
+ paletteSize int
+
+
+
+ Returns
+
+ - int
+
+
@@ -275,7 +238,7 @@ Class UpdateProgressDialog
- Edit this page
+ Edit this page
diff --git a/api/client/EchoHub.Client.UI.Helpers.html b/api/client/EchoHub.Client.UI.Helpers.html
index c9f23d0..c672aa3 100644
--- a/api/client/EchoHub.Client.UI.Helpers.html
+++ b/api/client/EchoHub.Client.UI.Helpers.html
@@ -111,6 +111,13 @@ so we replace them with fixed-width ASCII shortcodes for display only.
- HexColorHelper
Helper to parse hex colors to Terminal.Gui Attributes.
+
+
+
+ - NickColorHelper
+ Deterministic per-nick colors for users who haven't picked a nickname color.
+The same nick always maps to the same palette entry (classic IRC client behavior),
+so a busy channel stays scannable without any configuration.
diff --git a/api/client/EchoHub.Client.UI.ListSources.ChannelListSource.html b/api/client/EchoHub.Client.UI.ListSources.ChannelListSource.html
index 91e77d9..8267cfd 100644
--- a/api/client/EchoHub.Client.UI.ListSources.ChannelListSource.html
+++ b/api/client/EchoHub.Client.UI.ListSources.ChannelListSource.html
@@ -459,8 +459,8 @@ Active channel gets a > indicator, unread channels are bright with a count ba
-
- Update(List<string>, Dictionary<string, int>, string, IReadOnlySet<string>?)
+
+ Update(List<string>, Dictionary<string, int>, string, IReadOnlySet<string>?, IReadOnlySet<string>?)
@@ -468,7 +468,7 @@ Active channel gets a > indicator, unread channels are bright with a count ba
- public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null)
+ public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null, IReadOnlySet<string>? mentionChannels = null)
Parameters
@@ -481,6 +481,8 @@ Active channel gets a > indicator, unread channels are bright with a count ba
protectedChannels IReadOnlySet<string>
+ mentionChannels IReadOnlySet<string>
+
diff --git a/api/core/EchoHub.Core.Contracts.IChannelService.html b/api/core/EchoHub.Core.Contracts.IChannelService.html
index 9eb6fe7..c529e2e 100644
--- a/api/core/EchoHub.Core.Contracts.IChannelService.html
+++ b/api/core/EchoHub.Core.Contracts.IChannelService.html
@@ -385,6 +385,42 @@ Interface IChannelService
+
+
+ GetChannelMetaAsync(string)
+
+
+
+
+
+
+
+ Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+
+
+ Parameters
+
+ channelName string
+
+
+
+ Returns
+
+ - Task<ChannelMetaDto>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html b/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
index 9075f28..f7ef036 100644
--- a/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
+++ b/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
@@ -116,6 +116,42 @@ Interface IMessageEncryptionService Fields
+
+
+
+
+
+ CiphertextPrefix
+
+
+
+ Prefix marking transport/at-rest encrypted content. Decrypt(string) is a
+pass-through for values without it.
+
+
+
+
+ public const string CiphertextPrefix = "$ENC$v1$"
+
+
+
+
+
+ Field Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
Properties
diff --git a/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html
new file mode 100644
index 0000000..f59398d
--- /dev/null
+++ b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html
@@ -0,0 +1,878 @@
+
+
+
+
+ Class ChannelMetaDto | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Class ChannelMetaDto
+
+
+
+
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
+
+
+ public record ChannelMetaDto : IEquatable<ChannelMetaDto>
+
+
+
+
+
+
+ - Inheritance
+ -
+
+ ChannelMetaDto
+
+
+
+
+ - Implements
+ -
+
+
+
+
+
+
+ - Inherited Members
+ -
+
+ object.GetType()
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+
+
+
+
+
+
+ ChannelMetaDto(ChannelMetaDto)
+
+
+
+
+
+
+
+ protected ChannelMetaDto(ChannelMetaDto original)
+
+
+ Parameters
+
+ original ChannelMetaDto
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ChannelMetaDto(Guid, string, string?, bool, bool, int, int, long, DateTimeOffset)
+
+
+
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
+
+
+ public ChannelMetaDto(Guid Id, string Name, string? Topic, bool IsEncrypted, bool IsProtected, int MessageCount, int UniqueUserCount, long EstimatedSizeBytes, DateTimeOffset CreatedAt)
+
+
+ Parameters
+
+ Id Guid
+
+ Name string
+
+ Topic string
+
+ IsEncrypted bool
+
+ IsProtected bool
+
+ MessageCount int
+
+ UniqueUserCount int
+
+ EstimatedSizeBytes long
+
+ CreatedAt DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties
+
+
+
+
+
+
+ CreatedAt
+
+
+
+
+
+
+
+ public DateTimeOffset CreatedAt { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EqualityContract
+
+
+
+
+
+
+
+ protected virtual Type EqualityContract { get; }
+
+
+
+
+
+
+ Property Value
+
+ - Type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EstimatedSizeBytes
+
+
+
+
+
+
+
+ public long EstimatedSizeBytes { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Id
+
+
+
+
+
+
+
+ public Guid Id { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - Guid
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsEncrypted
+
+
+
+
+
+
+
+ public bool IsEncrypted { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsProtected
+
+
+
+
+
+
+
+ public bool IsProtected { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MessageCount
+
+
+
+
+
+
+
+ public int MessageCount { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+
+ public string Name { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Topic
+
+
+
+
+
+
+
+ public string? Topic { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UniqueUserCount
+
+
+
+
+
+
+
+ public int UniqueUserCount { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+ Deconstruct(out Guid, out string, out string?, out bool, out bool, out int, out int, out long, out DateTimeOffset)
+
+
+
+
+
+
+
+ public void Deconstruct(out Guid Id, out string Name, out string? Topic, out bool IsEncrypted, out bool IsProtected, out int MessageCount, out int UniqueUserCount, out long EstimatedSizeBytes, out DateTimeOffset CreatedAt)
+
+
+ Parameters
+
+ Id Guid
+
+ Name string
+
+ Topic string
+
+ IsEncrypted bool
+
+ IsProtected bool
+
+ MessageCount int
+
+ UniqueUserCount int
+
+ EstimatedSizeBytes long
+
+ CreatedAt DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Equals(ChannelMetaDto?)
+
+
+
+
+
+
+
+ public virtual bool Equals(ChannelMetaDto? other)
+
+
+ Parameters
+
+ other ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Equals(object?)
+
+
+
+
+
+
+
+ public override bool Equals(object? obj)
+
+
+ Parameters
+
+ obj object
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GetHashCode()
+
+
+
+
+
+
+
+ public override int GetHashCode()
+
+
+
+ Returns
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PrintMembers(StringBuilder)
+
+
+
+
+
+
+
+ protected virtual bool PrintMembers(StringBuilder builder)
+
+
+ Parameters
+
+ builder StringBuilder
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ToString()
+
+
+
+
+
+
+
+ public override string ToString()
+
+
+
+ Returns
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Operators
+
+
+
+
+
+
+ operator ==(ChannelMetaDto?, ChannelMetaDto?)
+
+
+
+
+
+
+
+ public static bool operator ==(ChannelMetaDto? left, ChannelMetaDto? right)
+
+
+ Parameters
+
+ left ChannelMetaDto
+
+ right ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ operator !=(ChannelMetaDto?, ChannelMetaDto?)
+
+
+
+
+
+
+
+ public static bool operator !=(ChannelMetaDto? left, ChannelMetaDto? right)
+
+
+ Parameters
+
+ left ChannelMetaDto
+
+ right ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit this page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/core/EchoHub.Core.DTOs.html b/api/core/EchoHub.Core.DTOs.html
index 4944bb3..4692b85 100644
--- a/api/core/EchoHub.Core.DTOs.html
+++ b/api/core/EchoHub.Core.DTOs.html
@@ -131,6 +131,14 @@ credential from a passphrase. Never includes the wrapped room key.
ChannelDto
+
+ - ChannelMetaDto
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
- ChannelOperationResult
diff --git a/api/server/EchoHub.Server.Config.UploadLimits.html b/api/server/EchoHub.Server.Config.UploadLimits.html
new file mode 100644
index 0000000..69864cc
--- /dev/null
+++ b/api/server/EchoHub.Server.Config.UploadLimits.html
@@ -0,0 +1,564 @@
+
+
+
+
+ Class UploadLimits | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Class UploadLimits
+
+
+
+
+ Admin-configurable upload limits, bound from the Uploads configuration section.
+Sizes are expressed in megabytes in configuration; the *Bytes accessors convert them
+for enforcement. Every default mirrors HubConstants so an absent or partial
+Uploads section preserves the historical built-in limits.
+
+
+
+
+ public sealed class UploadLimits
+
+
+
+
+
+
+ - Inheritance
+ -
+
+ UploadLimits
+
+
+
+
+
+
+ - Inherited Members
+ -
+
+ object.GetType()
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+
+
+
+
+
+
+ UploadLimits()
+
+
+
+
+
+
+
+ public UploadLimits()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties
+
+
+
+
+
+
+ MaxAttachmentsPerMessage
+
+
+
+
+
+
+
+ public int MaxAttachmentsPerMessage { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAudioSizeBytes
+
+
+
+
+
+
+
+ public long MaxAudioSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAudioSizeMB
+
+
+
+
+
+
+
+ public int MaxAudioSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAvatarSizeBytes
+
+
+
+
+
+
+
+ public long MaxAvatarSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAvatarSizeMB
+
+
+
+
+
+
+
+ public int MaxAvatarSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxFileSizeBytes
+
+
+
+
+
+
+
+ public long MaxFileSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxFileSizeMB
+
+
+
+
+
+
+
+ public int MaxFileSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxImageSizeBytes
+
+
+
+
+
+
+
+ public long MaxImageSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxImageSizeMB
+
+
+
+
+
+
+
+ public int MaxImageSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxRequestBodyBytes
+
+
+
+ Absolute ceiling for one message request body (largest file × the attachment cap). Used to
+size the request-body and multipart limits so a configured increase actually takes effect.
+
+
+
+
+ public long MaxRequestBodyBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+ MaxForKind(AttachmentKind)
+
+
+
+ Maximum accepted size for a single attachment of the given kind.
+
+
+
+
+ public long MaxForKind(AttachmentKind kind)
+
+
+ Parameters
+
+ kind AttachmentKind
+
+
+
+ Returns
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit this page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/server/EchoHub.Server.Config.html b/api/server/EchoHub.Server.Config.html
new file mode 100644
index 0000000..544abc8
--- /dev/null
+++ b/api/server/EchoHub.Server.Config.html
@@ -0,0 +1,130 @@
+
+
+
+
+ Namespace EchoHub.Server.Config | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Namespace EchoHub.Server.Config
+
+
+
+
+
+Classes
+
+
+ - UploadLimits
+ Admin-configurable upload limits, bound from the Uploads configuration section.
+Sizes are expressed in megabytes in configuration; the *Bytes accessors convert them
+for enforcement. Every default mirrors HubConstants so an absent or partial
+Uploads section preserves the historical built-in limits.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/server/EchoHub.Server.Controllers.ChannelsController.html b/api/server/EchoHub.Server.Controllers.ChannelsController.html
index b951f2b..4212b3c 100644
--- a/api/server/EchoHub.Server.Controllers.ChannelsController.html
+++ b/api/server/EchoHub.Server.Controllers.ChannelsController.html
@@ -159,8 +159,8 @@ public class ChannelsController : ControllerBase
-
- ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService)
+
+ ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService, UploadLimits)
@@ -168,7 +168,7 @@ public class ChannelsController : ControllerBase
- public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption)
+ public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption, UploadLimits uploadLimits)
Parameters
@@ -187,6 +187,8 @@ public class ChannelsController : ControllerBase
encryption IMessageEncryptionService
+ uploadLimits UploadLimits
+
@@ -318,6 +320,46 @@ public Task<IActionResult> GetChannelCrypto(string channel)
+
+
+
+ GetChannelMeta(string)
+
+
+
+ Human-facing summary of a channel (message count, unique posters, estimated size,
+created date, room id). Available for encrypted channels too — these are metadata the
+server tracks even though it cannot read the messages themselves.
+
+
+
+
+ [HttpGet("{channel}/meta")]
+public Task<IActionResult> GetChannelMeta(string channel)
+
+
+ Parameters
+
+ channel string
+
+
+
+ Returns
+
+ - Task<IActionResult>
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -417,8 +459,6 @@ room-encrypted preview (preview), aligned by file order — the ser
[HttpPost("{channel}/messages")]
[EnableRateLimiting("upload")]
-[RequestSizeLimit(1048576000)]
-[RequestFormLimits(MultipartBodyLengthLimit = 1048576000)]
public Task<IActionResult> SendMessageWithAttachments(string channel, string? size = null)
diff --git a/api/server/EchoHub.Server.Controllers.UsersController.html b/api/server/EchoHub.Server.Controllers.UsersController.html
index 969d230..59588f2 100644
--- a/api/server/EchoHub.Server.Controllers.UsersController.html
+++ b/api/server/EchoHub.Server.Controllers.UsersController.html
@@ -159,8 +159,8 @@ public class UsersController : ControllerBase
-
- UsersController(IUserService, ImageToAsciiService)
+
+ UsersController(IUserService, ImageToAsciiService, UploadLimits)
@@ -168,7 +168,7 @@ public class UsersController : ControllerBase
- public UsersController(IUserService userService, ImageToAsciiService asciiService)
+ public UsersController(IUserService userService, ImageToAsciiService asciiService, UploadLimits uploadLimits)
Parameters
@@ -177,6 +177,8 @@ public class UsersController : ControllerBase
asciiService ImageToAsciiService
+ uploadLimits UploadLimits
+
diff --git a/api/server/EchoHub.Server.Services.ChannelService.html b/api/server/EchoHub.Server.Services.ChannelService.html
index 3230cbe..f9768dc 100644
--- a/api/server/EchoHub.Server.Services.ChannelService.html
+++ b/api/server/EchoHub.Server.Services.ChannelService.html
@@ -462,6 +462,42 @@ Class ChannelService
+
+
+ GetChannelMetaAsync(string)
+
+
+
+
+
+
+
+ public Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+
+
+ Parameters
+
+ channelName string
+
+
+
+ Returns
+
+ - Task<ChannelMetaDto>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/toc.html b/api/toc.html
index e3066f2..7dad86e 100644
--- a/api/toc.html
+++ b/api/toc.html
@@ -229,9 +229,6 @@
- Assembly
- EchoHub.Client.dll
Deterministic per-nick colors for users who haven't picked a nickname color. +The same nick always maps to the same palette entry (classic IRC client behavior), +so a busy channel stays scannable without any configuration.
+public sealed class UpdateProgressDialog
+ public static class NickColorHelper
public UpdateProgressDialog(IApplication app, string newVersion)
- app IApplicationnewVersion string+ GetAttribute(string) +
- +The color attribute for a nick. Used as a fallback when the user has no +explicit nickname color set.
+public void Close()
- - Show() - -
- - - - -public void Show()
- - UpdateProgress(float, string) - -
- - - - -public void UpdateProgress(float fraction, string statusText)
+ public static Attribute GetAttribute(string nick)
Parameters
-
-
fractionfloat
-
- statusTextstring
+ nickstring
Returns
+-
+
- Attribute + +
+ GetPaletteIndex(string, int) + +
+ +Stable palette index for a nick: case-insensitive FNV-1a over the nick,
+reduced modulo paletteSize. Pure function (no Terminal.Gui
+types) so it is unit-testable without a display driver.
public static int GetPaletteIndex(string nick, int paletteSize)
+ Parameters
+-
+
nickstring
+
+ paletteSizeint
+
+
Returns
+-
+
- int + +
- HexColorHelper
Helper to parse hex colors to Terminal.Gui Attributes.
+
+
-
+
- NickColorHelper +
Deterministic per-nick colors for users who haven't picked a nickname color. +The same nick always maps to the same palette entry (classic IRC client behavior), +so a busy channel stays scannable without any configuration.
- Update(List<string>, Dictionary<string, int>, string, IReadOnlySet<string>?)
+
+ Update(List<string>, Dictionary<string, int>, string, IReadOnlySet<string>?, IReadOnlySet<string>?)
@@ -468,7 +468,7 @@ Active channel gets a > indicator, unread channels are bright with a count ba
- public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null)
+ public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null, IReadOnlySet<string>? mentionChannels = null)
Parameters
@@ -481,6 +481,8 @@ Active channel gets a > indicator, unread channels are bright with a count ba
protectedChannels IReadOnlySet<string>
+ mentionChannels IReadOnlySet<string>
+
diff --git a/api/core/EchoHub.Core.Contracts.IChannelService.html b/api/core/EchoHub.Core.Contracts.IChannelService.html
index 9eb6fe7..c529e2e 100644
--- a/api/core/EchoHub.Core.Contracts.IChannelService.html
+++ b/api/core/EchoHub.Core.Contracts.IChannelService.html
@@ -385,6 +385,42 @@ Interface IChannelService
+
+
+ GetChannelMetaAsync(string)
+
+
+
+
+
+
+
+ Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+
+
+ Parameters
+
+ channelName string
+
+
+
+ Returns
+
+ - Task<ChannelMetaDto>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html b/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
index 9075f28..f7ef036 100644
--- a/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
+++ b/api/core/EchoHub.Core.Contracts.IMessageEncryptionService.html
@@ -116,6 +116,42 @@ Interface IMessageEncryptionService Fields
+
+
+
+
+
+ CiphertextPrefix
+
+
+
+ Prefix marking transport/at-rest encrypted content. Decrypt(string) is a
+pass-through for values without it.
+
+
+
+
+ public const string CiphertextPrefix = "$ENC$v1$"
+
+
+
+
+
+ Field Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
Properties
diff --git a/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html
new file mode 100644
index 0000000..f59398d
--- /dev/null
+++ b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html
@@ -0,0 +1,878 @@
+
+
+
+
+ Class ChannelMetaDto | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Class ChannelMetaDto
+
+
+
+
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
+
+
+ public record ChannelMetaDto : IEquatable<ChannelMetaDto>
+
+
+
+
+
+
+ - Inheritance
+ -
+
+ ChannelMetaDto
+
+
+
+
+ - Implements
+ -
+
+
+
+
+
+
+ - Inherited Members
+ -
+
+ object.GetType()
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+
+
+
+
+
+
+ ChannelMetaDto(ChannelMetaDto)
+
+
+
+
+
+
+
+ protected ChannelMetaDto(ChannelMetaDto original)
+
+
+ Parameters
+
+ original ChannelMetaDto
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ChannelMetaDto(Guid, string, string?, bool, bool, int, int, long, DateTimeOffset)
+
+
+
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
+
+
+ public ChannelMetaDto(Guid Id, string Name, string? Topic, bool IsEncrypted, bool IsProtected, int MessageCount, int UniqueUserCount, long EstimatedSizeBytes, DateTimeOffset CreatedAt)
+
+
+ Parameters
+
+ Id Guid
+
+ Name string
+
+ Topic string
+
+ IsEncrypted bool
+
+ IsProtected bool
+
+ MessageCount int
+
+ UniqueUserCount int
+
+ EstimatedSizeBytes long
+
+ CreatedAt DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties
+
+
+
+
+
+
+ CreatedAt
+
+
+
+
+
+
+
+ public DateTimeOffset CreatedAt { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EqualityContract
+
+
+
+
+
+
+
+ protected virtual Type EqualityContract { get; }
+
+
+
+
+
+
+ Property Value
+
+ - Type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EstimatedSizeBytes
+
+
+
+
+
+
+
+ public long EstimatedSizeBytes { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Id
+
+
+
+
+
+
+
+ public Guid Id { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - Guid
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsEncrypted
+
+
+
+
+
+
+
+ public bool IsEncrypted { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsProtected
+
+
+
+
+
+
+
+ public bool IsProtected { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MessageCount
+
+
+
+
+
+
+
+ public int MessageCount { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+
+
+
+ public string Name { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Topic
+
+
+
+
+
+
+
+ public string? Topic { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UniqueUserCount
+
+
+
+
+
+
+
+ public int UniqueUserCount { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+ Deconstruct(out Guid, out string, out string?, out bool, out bool, out int, out int, out long, out DateTimeOffset)
+
+
+
+
+
+
+
+ public void Deconstruct(out Guid Id, out string Name, out string? Topic, out bool IsEncrypted, out bool IsProtected, out int MessageCount, out int UniqueUserCount, out long EstimatedSizeBytes, out DateTimeOffset CreatedAt)
+
+
+ Parameters
+
+ Id Guid
+
+ Name string
+
+ Topic string
+
+ IsEncrypted bool
+
+ IsProtected bool
+
+ MessageCount int
+
+ UniqueUserCount int
+
+ EstimatedSizeBytes long
+
+ CreatedAt DateTimeOffset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Equals(ChannelMetaDto?)
+
+
+
+
+
+
+
+ public virtual bool Equals(ChannelMetaDto? other)
+
+
+ Parameters
+
+ other ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Equals(object?)
+
+
+
+
+
+
+
+ public override bool Equals(object? obj)
+
+
+ Parameters
+
+ obj object
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GetHashCode()
+
+
+
+
+
+
+
+ public override int GetHashCode()
+
+
+
+ Returns
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PrintMembers(StringBuilder)
+
+
+
+
+
+
+
+ protected virtual bool PrintMembers(StringBuilder builder)
+
+
+ Parameters
+
+ builder StringBuilder
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ToString()
+
+
+
+
+
+
+
+ public override string ToString()
+
+
+
+ Returns
+
+ - string
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Operators
+
+
+
+
+
+
+ operator ==(ChannelMetaDto?, ChannelMetaDto?)
+
+
+
+
+
+
+
+ public static bool operator ==(ChannelMetaDto? left, ChannelMetaDto? right)
+
+
+ Parameters
+
+ left ChannelMetaDto
+
+ right ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ operator !=(ChannelMetaDto?, ChannelMetaDto?)
+
+
+
+
+
+
+
+ public static bool operator !=(ChannelMetaDto? left, ChannelMetaDto? right)
+
+
+ Parameters
+
+ left ChannelMetaDto
+
+ right ChannelMetaDto
+
+
+
+ Returns
+
+ - bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit this page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/core/EchoHub.Core.DTOs.html b/api/core/EchoHub.Core.DTOs.html
index 4944bb3..4692b85 100644
--- a/api/core/EchoHub.Core.DTOs.html
+++ b/api/core/EchoHub.Core.DTOs.html
@@ -131,6 +131,14 @@ credential from a passphrase. Never includes the wrapped room key.
ChannelDto
+
+ - ChannelMetaDto
+ Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
+
- ChannelOperationResult
diff --git a/api/server/EchoHub.Server.Config.UploadLimits.html b/api/server/EchoHub.Server.Config.UploadLimits.html
new file mode 100644
index 0000000..69864cc
--- /dev/null
+++ b/api/server/EchoHub.Server.Config.UploadLimits.html
@@ -0,0 +1,564 @@
+
+
+
+
+ Class UploadLimits | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Class UploadLimits
+
+
+
+
+ Admin-configurable upload limits, bound from the Uploads configuration section.
+Sizes are expressed in megabytes in configuration; the *Bytes accessors convert them
+for enforcement. Every default mirrors HubConstants so an absent or partial
+Uploads section preserves the historical built-in limits.
+
+
+
+
+ public sealed class UploadLimits
+
+
+
+
+
+
+ - Inheritance
+ -
+
+ UploadLimits
+
+
+
+
+
+
+ - Inherited Members
+ -
+
+ object.GetType()
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+
+
+
+
+
+
+ UploadLimits()
+
+
+
+
+
+
+
+ public UploadLimits()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties
+
+
+
+
+
+
+ MaxAttachmentsPerMessage
+
+
+
+
+
+
+
+ public int MaxAttachmentsPerMessage { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAudioSizeBytes
+
+
+
+
+
+
+
+ public long MaxAudioSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAudioSizeMB
+
+
+
+
+
+
+
+ public int MaxAudioSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAvatarSizeBytes
+
+
+
+
+
+
+
+ public long MaxAvatarSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxAvatarSizeMB
+
+
+
+
+
+
+
+ public int MaxAvatarSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxFileSizeBytes
+
+
+
+
+
+
+
+ public long MaxFileSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxFileSizeMB
+
+
+
+
+
+
+
+ public int MaxFileSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxImageSizeBytes
+
+
+
+
+
+
+
+ public long MaxImageSizeBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxImageSizeMB
+
+
+
+
+
+
+
+ public int MaxImageSizeMB { get; init; }
+
+
+
+
+
+
+ Property Value
+
+ - int
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MaxRequestBodyBytes
+
+
+
+ Absolute ceiling for one message request body (largest file × the attachment cap). Used to
+size the request-body and multipart limits so a configured increase actually takes effect.
+
+
+
+
+ public long MaxRequestBodyBytes { get; }
+
+
+
+
+
+
+ Property Value
+
+ - long
+
+
+
+
+
+
+
+
+
+
+ Methods
+
+
+
+
+
+
+ MaxForKind(AttachmentKind)
+
+
+
+ Maximum accepted size for a single attachment of the given kind.
+
+
+
+
+ public long MaxForKind(AttachmentKind kind)
+
+
+ Parameters
+
+ kind AttachmentKind
+
+
+
+ Returns
+
+ - long
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit this page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/server/EchoHub.Server.Config.html b/api/server/EchoHub.Server.Config.html
new file mode 100644
index 0000000..544abc8
--- /dev/null
+++ b/api/server/EchoHub.Server.Config.html
@@ -0,0 +1,130 @@
+
+
+
+
+ Namespace EchoHub.Server.Config | EchoHub Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Namespace EchoHub.Server.Config
+
+
+
+
+
+Classes
+
+
+ - UploadLimits
+ Admin-configurable upload limits, bound from the Uploads configuration section.
+Sizes are expressed in megabytes in configuration; the *Bytes accessors convert them
+for enforcement. Every default mirrors HubConstants so an absent or partial
+Uploads section preserves the historical built-in limits.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/server/EchoHub.Server.Controllers.ChannelsController.html b/api/server/EchoHub.Server.Controllers.ChannelsController.html
index b951f2b..4212b3c 100644
--- a/api/server/EchoHub.Server.Controllers.ChannelsController.html
+++ b/api/server/EchoHub.Server.Controllers.ChannelsController.html
@@ -159,8 +159,8 @@ public class ChannelsController : ControllerBase
-
- ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService)
+
+ ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService, UploadLimits)
@@ -168,7 +168,7 @@ public class ChannelsController : ControllerBase
- public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption)
+ public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption, UploadLimits uploadLimits)
Parameters
@@ -187,6 +187,8 @@ public class ChannelsController : ControllerBase
encryption IMessageEncryptionService
+ uploadLimits UploadLimits
+
@@ -318,6 +320,46 @@ public Task<IActionResult> GetChannelCrypto(string channel)
+
+
+
+ GetChannelMeta(string)
+
+
+
+ Human-facing summary of a channel (message count, unique posters, estimated size,
+created date, room id). Available for encrypted channels too — these are metadata the
+server tracks even though it cannot read the messages themselves.
+
+
+
+
+ [HttpGet("{channel}/meta")]
+public Task<IActionResult> GetChannelMeta(string channel)
+
+
+ Parameters
+
+ channel string
+
+
+
+ Returns
+
+ - Task<IActionResult>
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -417,8 +459,6 @@ room-encrypted preview (preview), aligned by file order — the ser
[HttpPost("{channel}/messages")]
[EnableRateLimiting("upload")]
-[RequestSizeLimit(1048576000)]
-[RequestFormLimits(MultipartBodyLengthLimit = 1048576000)]
public Task<IActionResult> SendMessageWithAttachments(string channel, string? size = null)
diff --git a/api/server/EchoHub.Server.Controllers.UsersController.html b/api/server/EchoHub.Server.Controllers.UsersController.html
index 969d230..59588f2 100644
--- a/api/server/EchoHub.Server.Controllers.UsersController.html
+++ b/api/server/EchoHub.Server.Controllers.UsersController.html
@@ -159,8 +159,8 @@ public class UsersController : ControllerBase
-
- UsersController(IUserService, ImageToAsciiService)
+
+ UsersController(IUserService, ImageToAsciiService, UploadLimits)
@@ -168,7 +168,7 @@ public class UsersController : ControllerBase
- public UsersController(IUserService userService, ImageToAsciiService asciiService)
+ public UsersController(IUserService userService, ImageToAsciiService asciiService, UploadLimits uploadLimits)
Parameters
@@ -177,6 +177,8 @@ public class UsersController : ControllerBase
asciiService ImageToAsciiService
+ uploadLimits UploadLimits
+
diff --git a/api/server/EchoHub.Server.Services.ChannelService.html b/api/server/EchoHub.Server.Services.ChannelService.html
index 3230cbe..f9768dc 100644
--- a/api/server/EchoHub.Server.Services.ChannelService.html
+++ b/api/server/EchoHub.Server.Services.ChannelService.html
@@ -462,6 +462,42 @@ Class ChannelService
+
+
+ GetChannelMetaAsync(string)
+
+
+
+
+
+
+
+ public Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+
+
+ Parameters
+
+ channelName string
+
+
+
+ Returns
+
+ - Task<ChannelMetaDto>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/toc.html b/api/toc.html
index e3066f2..7dad86e 100644
--- a/api/toc.html
+++ b/api/toc.html
@@ -229,9 +229,6 @@
public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null)
+ public void Update(List<string> channels, Dictionary<string, int> unread, string activeChannel, IReadOnlySet<string>? protectedChannels = null, IReadOnlySet<string>? mentionChannels = null)
protectedChannels IReadOnlySet<string>mentionChannels IReadOnlySet<string>Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+ channelName string+ CiphertextPrefix + +
+ +Prefix marking transport/at-rest encrypted content. Decrypt(string) is a +pass-through for values without it.
+public const string CiphertextPrefix = "$ENC$v1$"
+ Field Value
+-
+
- string + +
Properties
diff --git a/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html new file mode 100644 index 0000000..f59398d --- /dev/null +++ b/api/core/EchoHub.Core.DTOs.ChannelMetaDto.html @@ -0,0 +1,878 @@ + + + + +Table of Contents
+ ++Class ChannelMetaDto +
+ + + +Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
public record ChannelMetaDto : IEquatable<ChannelMetaDto>
+ -
+
- Inheritance +
-
+
+ ChannelMetaDto+
+
-
+
- Implements +
- + + +
-
+
- Inherited Members +
-
+ + object.GetType() ++ + + +
Constructors +
+ + + + ++ ChannelMetaDto(ChannelMetaDto) + +
+ + + + +protected ChannelMetaDto(ChannelMetaDto original)
+ Parameters
+-
+
originalChannelMetaDto
+
+
+ ChannelMetaDto(Guid, string, string?, bool, bool, int, int, long, DateTimeOffset) + +
+ +Human-facing summary of a channel (the /meta command). For encrypted channels the
+server still knows these figures — count, timestamps, and stored blob sizes — even though it
+cannot read the content itself. EstimatedSizeBytes is the sum of stored
+attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
public ChannelMetaDto(Guid Id, string Name, string? Topic, bool IsEncrypted, bool IsProtected, int MessageCount, int UniqueUserCount, long EstimatedSizeBytes, DateTimeOffset CreatedAt)
+ Parameters
+-
+
IdGuid
+
+ Namestring
+
+ Topicstring
+
+ IsEncryptedbool
+
+ IsProtectedbool
+
+ MessageCountint
+
+ UniqueUserCountint
+
+ EstimatedSizeByteslong
+
+ CreatedAtDateTimeOffset
+
+
Properties +
+ + + + ++ CreatedAt + +
+ + + + +public DateTimeOffset CreatedAt { get; init; }
+ Property Value
+-
+
- DateTimeOffset + +
+ EqualityContract + +
+ + + + +protected virtual Type EqualityContract { get; }
+ Property Value
+-
+
- Type + +
+ EstimatedSizeBytes + +
+ + + + +public long EstimatedSizeBytes { get; init; }
+ Property Value
+-
+
- long + +
+ Id + +
+ + + + +public Guid Id { get; init; }
+ Property Value
+-
+
- Guid + +
+ IsEncrypted + +
+ + + + +public bool IsEncrypted { get; init; }
+ Property Value
+-
+
- bool + +
+ IsProtected + +
+ + + + +public bool IsProtected { get; init; }
+ Property Value
+-
+
- bool + +
+ MessageCount + +
+ + + + +public int MessageCount { get; init; }
+ Property Value
+-
+
- int + +
+ Name + +
+ + + + +public string Name { get; init; }
+ Property Value
+-
+
- string + +
+ Topic + +
+ + + + +public string? Topic { get; init; }
+ Property Value
+-
+
- string + +
+ UniqueUserCount + +
+ + + + +public int UniqueUserCount { get; init; }
+ Property Value
+-
+
- int + +
Methods +
+ + + + ++ Deconstruct(out Guid, out string, out string?, out bool, out bool, out int, out int, out long, out DateTimeOffset) + +
+ + + + +public void Deconstruct(out Guid Id, out string Name, out string? Topic, out bool IsEncrypted, out bool IsProtected, out int MessageCount, out int UniqueUserCount, out long EstimatedSizeBytes, out DateTimeOffset CreatedAt)
+ Parameters
+-
+
IdGuid
+
+ Namestring
+
+ Topicstring
+
+ IsEncryptedbool
+
+ IsProtectedbool
+
+ MessageCountint
+
+ UniqueUserCountint
+
+ EstimatedSizeByteslong
+
+ CreatedAtDateTimeOffset
+
+
+ Equals(ChannelMetaDto?) + +
+ + + + +public virtual bool Equals(ChannelMetaDto? other)
+ Parameters
+-
+
otherChannelMetaDto
+
+
Returns
+-
+
- bool + +
+ Equals(object?) + +
+ + + + +public override bool Equals(object? obj)
+ Parameters
+-
+
objobject
+
+
Returns
+-
+
- bool + +
+ GetHashCode() + +
+ + + + +public override int GetHashCode()
+ Returns
+-
+
- int + +
+ PrintMembers(StringBuilder) + +
+ + + + +protected virtual bool PrintMembers(StringBuilder builder)
+ Parameters
+-
+
builderStringBuilder
+
+
Returns
+-
+
- bool + +
+ ToString() + +
+ + + + +public override string ToString()
+ Returns
+-
+
- string + +
Operators +
+ + + + ++ operator ==(ChannelMetaDto?, ChannelMetaDto?) + +
+ + + + +public static bool operator ==(ChannelMetaDto? left, ChannelMetaDto? right)
+ Parameters
+-
+
leftChannelMetaDto
+
+ rightChannelMetaDto
+
+
Returns
+-
+
- bool + +
+ operator !=(ChannelMetaDto?, ChannelMetaDto?) + +
+ + + + +public static bool operator !=(ChannelMetaDto? left, ChannelMetaDto? right)
+ Parameters
+-
+
leftChannelMetaDto
+
+ rightChannelMetaDto
+
+
Returns
+-
+
- bool + +
-
+
- ChannelMetaDto +
Human-facing summary of a channel (the
+/metacommand). For encrypted channels the +server still knows these figures — count, timestamps, and stored blob sizes — even though it +cannot read the content itself. EstimatedSizeBytes is the sum of stored +attachment blob sizes plus message text length, so it is an estimate, not an exact on-disk total.
+
- ChannelOperationResult diff --git a/api/server/EchoHub.Server.Config.UploadLimits.html b/api/server/EchoHub.Server.Config.UploadLimits.html new file mode 100644 index 0000000..69864cc --- /dev/null +++ b/api/server/EchoHub.Server.Config.UploadLimits.html @@ -0,0 +1,564 @@ + + + + +
- Inheritance +
-
+
+ UploadLimits+
+
- Inherited Members +
-
+ + object.GetType() ++ + + + + +
- int + +
- long + +
- int + +
- long + +
- int + +
- long + +
- int + +
- long + +
- int + +
- long + +
kindAttachmentKind
+
+ - long + +
- UploadLimits +
Admin-configurable upload limits, bound from the
+Uploadsconfiguration section. +Sizes are expressed in megabytes in configuration; the*Bytesaccessors convert them +for enforcement. Every default mirrors HubConstants so an absent or partial +Uploadssection preserves the historical built-in limits.
+ encryptionIMessageEncryptionService
+ uploadLimitsUploadLimits
+
Table of Contents
+ ++Class UploadLimits +
+ + + +Admin-configurable upload limits, bound from the Uploads configuration section.
+Sizes are expressed in megabytes in configuration; the *Bytes accessors convert them
+for enforcement. Every default mirrors HubConstants so an absent or partial
+Uploads section preserves the historical built-in limits.
public sealed class UploadLimits
+ -
+
-
+
Constructors +
+ + + + ++ UploadLimits() + +
+ + + + +public UploadLimits()
+ Properties +
+ + + + ++ MaxAttachmentsPerMessage + +
+ + + + +public int MaxAttachmentsPerMessage { get; init; }
+ Property Value
+-
+
+ MaxAudioSizeBytes + +
+ + + + +public long MaxAudioSizeBytes { get; }
+ Property Value
+-
+
+ MaxAudioSizeMB + +
+ + + + +public int MaxAudioSizeMB { get; init; }
+ Property Value
+-
+
+ MaxAvatarSizeBytes + +
+ + + + +public long MaxAvatarSizeBytes { get; }
+ Property Value
+-
+
+ MaxAvatarSizeMB + +
+ + + + +public int MaxAvatarSizeMB { get; init; }
+ Property Value
+-
+
+ MaxFileSizeBytes + +
+ + + + +public long MaxFileSizeBytes { get; }
+ Property Value
+-
+
+ MaxFileSizeMB + +
+ + + + +public int MaxFileSizeMB { get; init; }
+ Property Value
+-
+
+ MaxImageSizeBytes + +
+ + + + +public long MaxImageSizeBytes { get; }
+ Property Value
+-
+
+ MaxImageSizeMB + +
+ + + + +public int MaxImageSizeMB { get; init; }
+ Property Value
+-
+
+ MaxRequestBodyBytes + +
+ +Absolute ceiling for one message request body (largest file × the attachment cap). Used to +size the request-body and multipart limits so a configured increase actually takes effect.
+public long MaxRequestBodyBytes { get; }
+ Property Value
+-
+
Methods +
+ + + + ++ MaxForKind(AttachmentKind) + +
+ +Maximum accepted size for a single attachment of the given kind.
+public long MaxForKind(AttachmentKind kind)
+ Parameters
+-
+
Returns
+-
+
Table of Contents
+ +Namespace EchoHub.Server.Config
+ + + + ++Classes +
+-
+
- ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService)
+
+ ChannelsController(IChannelService, EchoHubDbContext, FileStorageService, ImageToAsciiService, IHttpClientFactory, IChatService, IMessageEncryptionService, UploadLimits)
@@ -168,7 +168,7 @@ public class ChannelsController : ControllerBase
- public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption)
+ public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption, UploadLimits uploadLimits)
Parameters
@@ -187,6 +187,8 @@ public class ChannelsController : ControllerBase
public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption)
+ public ChannelsController(IChannelService channelService, EchoHubDbContext db, FileStorageService fileStorage, ImageToAsciiService asciiService, IHttpClientFactory httpClientFactory, IChatService chatService, IMessageEncryptionService encryption, UploadLimits uploadLimits)
+ GetChannelMeta(string) + +
+ +Human-facing summary of a channel (message count, unique posters, estimated size, +created date, room id). Available for encrypted channels too — these are metadata the +server tracks even though it cannot read the messages themselves.
+[HttpGet("{channel}/meta")]
+public Task<IActionResult> GetChannelMeta(string channel)
+ Parameters
+-
+
channelstring
+
+
Returns
+-
+
- Task<IActionResult> + +
@@ -417,8 +459,6 @@ room-encrypted preview (preview), aligned by file order — the ser
[HttpPost("{channel}/messages")]
[EnableRateLimiting("upload")]
-[RequestSizeLimit(1048576000)]
-[RequestFormLimits(MultipartBodyLengthLimit = 1048576000)]
public Task<IActionResult> SendMessageWithAttachments(string channel, string? size = null)
diff --git a/api/server/EchoHub.Server.Controllers.UsersController.html b/api/server/EchoHub.Server.Controllers.UsersController.html
index 969d230..59588f2 100644
--- a/api/server/EchoHub.Server.Controllers.UsersController.html
+++ b/api/server/EchoHub.Server.Controllers.UsersController.html
@@ -159,8 +159,8 @@ public class UsersController : ControllerBase
-
- UsersController(IUserService, ImageToAsciiService)
+
+ UsersController(IUserService, ImageToAsciiService, UploadLimits)
@@ -168,7 +168,7 @@ public class UsersController : ControllerBase
- public UsersController(IUserService userService, ImageToAsciiService asciiService)
+ public UsersController(IUserService userService, ImageToAsciiService asciiService, UploadLimits uploadLimits)
Parameters
@@ -177,6 +177,8 @@ public class UsersController : ControllerBase
asciiService ImageToAsciiService
+ uploadLimits UploadLimits
+
diff --git a/api/server/EchoHub.Server.Services.ChannelService.html b/api/server/EchoHub.Server.Services.ChannelService.html
index 3230cbe..f9768dc 100644
--- a/api/server/EchoHub.Server.Services.ChannelService.html
+++ b/api/server/EchoHub.Server.Services.ChannelService.html
@@ -462,6 +462,42 @@ Class ChannelService
+
+
+ GetChannelMetaAsync(string)
+
+
+
+
+
+
+
+ public Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+
+
+ Parameters
+
+ channelName string
+
+
+
+ Returns
+
+ - Task<ChannelMetaDto>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/toc.html b/api/toc.html
index e3066f2..7dad86e 100644
--- a/api/toc.html
+++ b/api/toc.html
@@ -229,9 +229,6 @@
[HttpPost("{channel}/messages")]
[EnableRateLimiting("upload")]
-[RequestSizeLimit(1048576000)]
-[RequestFormLimits(MultipartBodyLengthLimit = 1048576000)]
public Task<IActionResult> SendMessageWithAttachments(string channel, string? size = null)
+ UsersController(IUserService, ImageToAsciiService, UploadLimits)
@@ -168,7 +168,7 @@ public class UsersController : ControllerBasepublic UsersController(IUserService userService, ImageToAsciiService asciiService)
+ public UsersController(IUserService userService, ImageToAsciiService asciiService, UploadLimits uploadLimits)
Parameters
@@ -177,6 +177,8 @@ public class UsersController : ControllerBaseasciiService ImageToAsciiServiceuploadLimits UploadLimits+ GetChannelMetaAsync(string) + +
+ + + + +public Task<ChannelMetaDto?> GetChannelMetaAsync(string channelName)
+ Parameters
+-
+
channelNamestring
+
+
Returns
+-
+
- Task<ChannelMetaDto> + +
diff --git a/api/toc.html b/api/toc.html index e3066f2..7dad86e 100644 --- a/api/toc.html +++ b/api/toc.html @@ -229,9 +229,6 @@
Table of Contents
+ +Encrypted Rooms (Password-Protected Channels)
+ +An encrypted room is a channel whose entire content — every message and every file — is +end-to-end encrypted with a key derived from a shared passphrase. Only people who know the +passphrase can read the room. Not even the server owner can read the content, yet the server +can still gate who joins, and it can count and measure what's stored (message count, file sizes, +timestamps) without ever seeing the plaintext.
+This is a stronger guarantee than the transport and at-rest encryption described +elsewhere, where the server decrypts every message to process it. Here the server is treated as +untrusted for content: it holds only ciphertext and wrapped keys.
+++The passphrase is the only key. There is no recovery. If everyone who knows a room's +passphrase forgets it, that room's history is permanently unreadable — by design.
+
What the server can and cannot see
+| The server can see | +The server cannot see | +
|---|---|
| That the channel is encrypted | +Message text | +
| Message count and timestamps | +File contents | +
| Who sent each message (sender identity) | +Image previews (ASCII art) | +
| Each attachment's file name and byte size | +The passphrase, the room key, or the key-encryption key | +
The estimated total size (via /meta) |
+Anything that would let it decrypt the above | +
File names are stored in plaintext so the file list stays usable — treat a file name itself as +non-secret. Everything inside the file is encrypted.
+Key hierarchy
+Three keys are derived from one passphrase. The passphrase, the key-encryption key, and the room +content key never leave the client.
+flowchart TD
+ P[Passphrase] -->|PBKDF2-SHA256, 210k iterations, per-room salt| OKM[64-byte output]
+ OKM --> AK["Auth key (first 32 bytes)"]
+ OKM --> KEK["Key-encryption key / KEK (last 32 bytes)"]
+ AK -->|sent as lowercase hex| SRV1["Server: BCrypt-hash as the join gate"]
+ RCK["Room Content Key (random 256-bit)"] -->|encrypts all content| CONTENT[Messages + files + previews]
+ RCK -->|AES-256-GCM wrap under KEK| WRAP["Wrapped room key"]
+ WRAP -->|stored| SRV2["Server: stores wrapped key + salt only"]
+
+-
+
- Auth key — the join credential. Derived from the passphrase, sent to the server as hex, and +stored only as a BCrypt hash. Proving knowledge of it is what lets you join; it reveals +nothing about the content key. +
- Key-encryption key (KEK) — never sent. Used locally to wrap (encrypt) and unwrap the room +content key. +
- Room Content Key (RCK) — a random 256-bit key generated once, at room creation. It encrypts +every message and file. The server stores it only in wrapped form, so it can hand the wrapped key +to a joiner but can never unwrap it itself. +
All content encryption is AES-256-GCM with a random 12-byte nonce and a 16-byte authentication +tag per item, so identical inputs never produce identical ciphertext, and any tampering is detected.
+Room-encrypted text carries a self-describing prefix so clients and the server can tell it apart +from plaintext:
+$RC1$base64(nonce || tag || ciphertext)
+
+Creating a room
+The client does all the cryptography locally, then hands the server only what it needs to gate joins +and store (but not read) the content.
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Client: generate salt + random Room Content Key (RCK)
+ Client->>Client: DeriveKeys(passphrase, salt) → auth key + KEK
+ Client->>Client: wrap RCK under KEK
+ Client->>Server: create channel { authKey(hex), salt, wrappedRoomKey }
+ Server->>Server: BCrypt-hash auth key, store salt + wrapped key
+ Note over Server: Server never receives passphrase, KEK, or RCK
+
+Joining a room
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: GET /crypto → { isEncrypted, salt }
+ Client->>Client: DeriveKeys(passphrase, salt) → auth key + KEK
+ Client->>Server: join { authKey(hex) }
+ Server->>Server: BCrypt-verify against stored hash
+ alt correct passphrase
+ Server->>Client: history (ciphertext) + wrapped room key
+ Client->>Client: unwrap RCK with KEK, then decrypt everything locally
+ else wrong passphrase
+ Server->>Client: rejected (join gate fails)
+ end
+
+A wrong passphrase fails the BCrypt gate, so the server never even hands out the wrapped key. Even if +it did, an attacker without the KEK cannot unwrap it.
+What gets encrypted
+When you send a message or attach files to an encrypted room, the client encrypts each part with the +room content key before uploading:
+-
+
- Message text →
$RC1$…ciphertext.
+ - Files (any kind) → the whole blob is AES-256-GCM encrypted client-side; the server stores an +opaque ciphertext blob. +
- Image ASCII previews → rendered on the client, then room-encrypted. The server never sees the +rendered art. +
The server records each attachment's kind, file name, and byte size (of the ciphertext +blob) as metadata, and broadcasts the ciphertext to other members, who decrypt locally.
+Changing the passphrase
+/passwd <old> <new> rotates the passphrase. Because only the wrapping of the room content key
+changes — not the RCK itself — all existing history stays readable:
-
+
- The client proves knowledge of the old passphrase (old auth key). +
- It unwraps the RCK with the old KEK, then re-wraps it under the new KEK (new salt). +
- It uploads the new auth key + salt + re-wrapped key. The content is never re-encrypted. +
Inspecting a room
+Use /meta in any channel to see what the server knows about it, including encrypted rooms:
Room info for #private-room:
+ Room ID 3f2a…-…-…
+ Created 7/16/2026 2:31 PM
+ Messages 128
+ Unique users 4
+ Est. size 42.5 MB
+ Protection end-to-end encrypted
+
+Est. size is the sum of stored attachment blob sizes plus message text length — an estimate of the
+room's footprint, computed entirely from metadata the server holds without reading any content.
Limitations & security notes
+-
+
- No recovery. A lost passphrase means unrecoverable history. Keep it safe; there is no reset. +
- File names are plaintext. They stay readable so the file list works — don't put secrets in a +file name. +
- IRC is disabled for encrypted rooms. The IRC gateway forwards plaintext and cannot participate +in the room's key scheme, so encrypted channels are not bridged to IRC. +
- Metadata is visible. Message counts, timestamps, sender identities, file names, and sizes are
+intentionally readable so the server can moderate at the metadata level and report
/meta.
+ - Endpoint trust. End-to-end encryption protects content from the server and the network, not +from a compromised client device that already holds the passphrase. +
Related
+-
+
- Message Encryption — transport (
$ENC$v1$) and optional at-rest database +encryption, where the server does decrypt content for processing. Encrypted rooms are a separate, +stronger layer that sits on top.
+
Limitations
- TLS-inspecting proxies — if a corporate proxy terminates TLS with a trusted root CA, it can intercept the key exchange (
GET /api/server/encryption-key) and read all traffic. A future upgrade to ECDH key exchange would address this.
- - Server has full access — the server decrypts all messages for processing. This is not end-to-end encryption between users; it's transport encryption between client and server. +
- Server has full access — the server decrypts all messages for processing. This is not end-to-end encryption between users; it's transport encryption between client and server. For true end-to-end encryption where the server cannot read content, use encrypted rooms.
- IRC clients receive plaintext — IRC is an open protocol and third-party clients cannot participate in the encryption scheme.
Troubleshooting
diff --git a/articles/toc.html b/articles/toc.html index dc29b5a..25dea6e 100644 --- a/articles/toc.html +++ b/articles/toc.html @@ -32,6 +32,9 @@Release history for EchoHub.
Releases
-
+
- v0.2.13 - Chat Visual Overhaul, Auto-Join All Channels & Persistent Read Positions
- v0.2.12 - End-to-End Encrypted Channels, IRC Channel Keys, Image Save & Ctrl+W Crash Fix
- v0.2.11 - EchoHubSpace Auth, Live Directory Updates & Server Browser Metadata
- v0.2.10 - Command Palette, Infinite History Scroll & Auto-Updater Fixes diff --git a/changelog/toc.html b/changelog/toc.html index 5522b12..81c7cfb 100644 --- a/changelog/toc.html +++ b/changelog/toc.html @@ -15,6 +15,9 @@
- Overview +
- + v0.2.13 +
- v0.2.12 diff --git a/changelog/toc.json b/changelog/toc.json index 26ccc79..08e3dfa 100644 --- a/changelog/toc.json +++ b/changelog/toc.json @@ -1,2 +1,2 @@ -{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"v0.2.12","href":"v0.2.12.html","topicHref":"v0.2.12.html"},{"name":"v0.2.11","href":"v0.2.11.html","topicHref":"v0.2.11.html"},{"name":"v0.2.10","href":"v0.2.10.html","topicHref":"v0.2.10.html"},{"name":"v0.2.9","href":"v0.2.9.html","topicHref":"v0.2.9.html"},{"name":"v0.2.8","href":"v0.2.8.html","topicHref":"v0.2.8.html"},{"name":"v0.2.7","href":"v0.2.7.html","topicHref":"v0.2.7.html"},{"name":"v0.2.6","href":"v0.2.6.html","topicHref":"v0.2.6.html"},{"name":"v0.2.5","href":"v0.2.5.html","topicHref":"v0.2.5.html"},{"name":"v0.2.4","href":"v0.2.4.html","topicHref":"v0.2.4.html"},{"name":"v0.2.3","href":"v0.2.3.html","topicHref":"v0.2.3.html"},{"name":"v0.2.2","href":"v0.2.2.html","topicHref":"v0.2.2.html"},{"name":"v0.2.1","href":"v0.2.1.html","topicHref":"v0.2.1.html"},{"name":"v0.2.0","href":"v0.2.0.html","topicHref":"v0.2.0.html"},{"name":"v0.1.1","href":"v0.1.1.html","topicHref":"v0.1.1.html"},{"name":"v0.1.0","href":"v0.1.0.html","topicHref":"v0.1.0.html"}]} +{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"v0.2.13","href":"v0.2.13.html","topicHref":"v0.2.13.html"},{"name":"v0.2.12","href":"v0.2.12.html","topicHref":"v0.2.12.html"},{"name":"v0.2.11","href":"v0.2.11.html","topicHref":"v0.2.11.html"},{"name":"v0.2.10","href":"v0.2.10.html","topicHref":"v0.2.10.html"},{"name":"v0.2.9","href":"v0.2.9.html","topicHref":"v0.2.9.html"},{"name":"v0.2.8","href":"v0.2.8.html","topicHref":"v0.2.8.html"},{"name":"v0.2.7","href":"v0.2.7.html","topicHref":"v0.2.7.html"},{"name":"v0.2.6","href":"v0.2.6.html","topicHref":"v0.2.6.html"},{"name":"v0.2.5","href":"v0.2.5.html","topicHref":"v0.2.5.html"},{"name":"v0.2.4","href":"v0.2.4.html","topicHref":"v0.2.4.html"},{"name":"v0.2.3","href":"v0.2.3.html","topicHref":"v0.2.3.html"},{"name":"v0.2.2","href":"v0.2.2.html","topicHref":"v0.2.2.html"},{"name":"v0.2.1","href":"v0.2.1.html","topicHref":"v0.2.1.html"},{"name":"v0.2.0","href":"v0.2.0.html","topicHref":"v0.2.0.html"},{"name":"v0.1.1","href":"v0.1.1.html","topicHref":"v0.1.1.html"},{"name":"v0.1.0","href":"v0.1.0.html","topicHref":"v0.1.0.html"}]} diff --git a/changelog/v0.2.12.html b/changelog/v0.2.12.html index 1f575e1..2ab0cf7 100644 --- a/changelog/v0.2.12.html +++ b/changelog/v0.2.12.html @@ -120,7 +120,10 @@
- Attach a file by drag & drop or by pasting — drop a file onto the terminal, or copy a file in your file manager and press Ctrl+V, to stage it as an attachment (the next Enter sends it with your caption). Multiple files at once are supported. Ctrl+V still pastes text when the clipboard holds text; Ctrl+Y is a paste alias. On Windows the copied-file paste reads the clipboard's file list directly (Windows Terminal never pastes copied files as text), with
xclip/wl-pasteused on Linux - Pick ASCII-art size for attached images —
/sizeopens a Small/Medium/Large picker (40×40 / 80×80 / 120×120) with descriptions,/size <s|m|l>sets it directly, and/send <file> -lsets it for that message. The choice is a saved preference and applies to copy-paste/drag-drop images (which have no per-file flag); the current size is shown in the staging tray - New
TransparentLighttheme — dark characters on a transparent background, for light terminal color schemes (/theme transparentlight)
- - Timestamps in messages are now aware of the current culture and display the short time pattern for today's messages and the short date+time pattern for older messages. +
/metacommand — shows a summary of the current room: room id, created date, message count, unique participant count, estimated storage size, and protection level (open / password-protected / end-to-end encrypted). Works for encrypted rooms too, since these are all metadata the server tracks without reading content. (/infois an alias.)
+- Configurable upload limits — a new
Uploadssection in the server settings sets the maximum size per file, image, audio clip, and avatar, plus the maximum attachments per message. Absent values fall back to the previous built-in limits, so existing servers are unaffected until they opt in.
+ - Updater progress bar — the self-updater now draws a real progress bar on the console (
Downloading [████████░░] 62%) for each step, replacing the plain status lines, so a large download shows visible progress.
+ - Timestamps in messages are culture-aware and now always include the date: today's messages show a compact date + short time, and older messages show the general short date/time.
Bug Fixes
-
@@ -128,12 +131,16 @@
- Attachments whose files have been pruned (retention cleanup deletes blobs older than
Storage:RetentionDaysbut left the message rows) no longer render a dead download/preview. When channel history loads, the server checks which attachment blobs still exist: missing ones are dropped from the message, and an attachment-only message whose files are all gone is removed from the database. - Fixed intermittent crash on Ctrl+W — Terminal.Gui binds Ctrl+W to clipboard-cut, and Windows clipboard contention (another app holding the clipboard) threw an unhandled
Win32Exceptionthat took the app down. Ctrl+W now deletes the previous word (readline behavior, no clipboard), and all clipboard shortcuts (Ctrl+X/C/V/Y) are guarded so transient clipboard failures log a warning instead of crashing - Fixed emoji shortcode replacement permanently disabling itself if a cursor update threw mid-replacement +
- Fixed new messages showing no date — today's messages rendered time-only, and the "is it today?" check compared the server's UTC date against the local date, so the classification could also be wrong near midnight. Timestamps are now converted to local time first and always include the date. +
- Fixed the self-updater hanging at "extracting" — the update ran while the Terminal.Gui main loop still owned the console, so the old and new processes deadlocked over it. The update now runs after the TUI shuts down, on a clean console
- IRC
LISTno longer leaks private channels; protected channels are marked[+k]
API Changes
ChannelDtogainsisProtectedandisEncrypted;CreateChannelRequestgains optionalpassword,encryptionSalt, andwrappedRoomKey; SignalRJoinChanneltakes an optional secondpasswordargument andJoinChannelResultgainspasswordRequired,encryptionSalt, andwrappedRoomKey(older clients must update to join over SignalR)- New endpoints:
GET /api/channels/{channel}/crypto(public crypto metadata — salt only, never the wrapped key) andPOST /api/channels/{channel}/rekey(creator-only passphrase change)
+ - New endpoint
GET /api/channels/{channel}/metareturns aChannelMetaDto(room id, name, topic, encrypted/protected flags, message count, unique user count, estimated size, created date) backing the/metacommand
+ - New server
Uploadsconfiguration section (MaxFileSizeMB,MaxImageSizeMB,MaxAudioSizeMB,MaxAvatarSizeMB,MaxAttachmentsPerMessage); the message-upload endpoint's request-body ceiling is now derived from these values at runtime rather than from compile-time constants - The upload endpoint accepts
typeandcontentform fields for encrypted channels, where the client supplies the declared message type and room-encrypted content ImageToAsciiServiceandFileValidationHelpermoved fromEchoHub.ServertoEchoHub.Coreso the client can render ASCII art and detect file types for encrypted uploads- Message shape change:
MessageDtodropsType/AttachmentUrl/AttachmentFileName/AttachmentFileSizeand gainsAttachments(a list ofAttachmentDto { Kind, Url, FileName, FileSize, AsciiPreview }, null/empty for plain text). NewAttachmententity + table with a cascade FK toMessage
diff --git a/changelog/v0.2.13.html b/changelog/v0.2.13.html
new file mode 100644
index 0000000..4768f52
--- /dev/null
+++ b/changelog/v0.2.13.html
@@ -0,0 +1,140 @@
+
+
+
+
+ - Aligned message layout (WeeChat-style) — every message renders as
HH:mm nickname │ text: a dim timestamp, the nick right-aligned in a fixed 12-column gutter, and a dim│rail separating names from content. Word-wrapped continuation lines, multi-line messages, attachments, ASCII-art image previews, and link embeds all align under the text column and extend the rail, so conversations read as a clean two-column grid. Over-long nicks are truncated with…in the gutter only — profiles, mentions, and the users panel always use the full name. System and status lines (joined,is now Away, …) show--in the nick column, replacing the old**prefix.
+ - Deterministic nickname colors — users who haven't picked a
/colornow get an automatic, stable color derived from their nick (12-entry palette chosen to stay readable on dark and light backgrounds). The same user is the same color in message headers and the online-users panel, on every client, every session. An explicitly chosen nickname color still takes priority.
+ - Date separator rules — a full-width
── Wed, Jul 16 2026 ─────rule is inserted whenever the calendar day changes (live, in loaded history, and when scrolling back through older messages). Timestamps are now a compactHH:mm— the date lives in the rules instead of being repeated on every line.
+ - "New messages" marker — the first message that arrives in a channel you're not viewing gets an orange
── new messages ──rule above it (irssi-style). Switch to the channel and the marker shows exactly where you left off; it survives the history reload on channel switch and is consumed when you move away again.
+ - Status bar activity segment — channels with unread messages appear in the status bar as
│ Act: #dev,#random(up to 4,+noverflow). Channels where you were @mentioned show in orange, others in cyan.
+ - Connecting spinner — transitional connection states (Connecting, Reconnecting, Authenticating…) now animate a braille spinner in the status bar instead of sitting on static text. +
- Mention-aware channel list — a channel where you were @mentioned turns orange in the channel list (name and unread badge), escalating above the cyan plain-unread highlight. The highlight clears when you view the channel. +
- Welcome banner — with no channel selected (fresh start, or after disconnecting) the chat pane shows a gold-gradient ASCII "ECHOHUB" logo with the version and key hints, instead of an empty box. Narrow panes get a compact variant. +
- Rounded frame borders — the channels, chat, input, and users panels draw with rounded corners. +
- Auto-join all channels at connect — the client now joins every channel it lists for you (public channels and prior memberships) as part of connecting, so message events flow for all of them: unread badges, @mention highlights, and the status-bar activity segment work without having to open each channel first. Protected channels you've never entered are skipped silently (joining them stays a prompted, manual action), and channels you
/leaveare remembered and excluded until you join them again.
+ - Read positions survive restarts — the client persists the last message you read per channel (locally, per server). On the next connect it compares that against fetched history, so messages that arrived while you were offline still produce unread counts, mention highlights, and a correctly placed "new messages" marker. +
- Theme-tinted frame borders — themes can now color the window frame borders independently of text via a new optional
bordersection (hex values supported). The Transparent and TransparentLight themes use it to draw dim gray borders instead of stark white ones, for a subtler, glassier look. Custom theme JSON files without abordersection keep their base colors.
+ - The @mention highlight no longer drops off the second and later lines of a long mention message — wrapped continuation lines now inherit the highlight. +
- The unread-position marker is re-anchored after a channel switch reloads history, instead of being wiped by the reload before you could see it. +
- Cached room keys for end-to-end encrypted channels no longer disappear from the config on reconnect. Saving the server entry after a successful connect replaced it wholesale, wiping the cached keys (and forcing a passphrase re-entry on the next launch); the entry is now updated in place. +
- A failed channel rejoin after a reconnect (e.g. a channel deleted while you were away) no longer aborts rejoining the remaining channels. +
- Client config file access is now serialized across threads (token refresh, room-key cache, and read-position checkpoints all write it), preventing rare config corruption. +
Table of Contents
+ +v0.2.13
+ +A visual overhaul of the chat client — WeeChat-style column layout, per-user nick colors, date rules, unread markers, an activity status segment, and an ASCII welcome banner — plus reliable cross-channel notifications: the client now joins all your channels at connect so unread counts and @mentions light up everywhere, and read positions persist across restarts so activity that happened while you were offline still shows. Modern polish, old IRC soul.
+New Features
+-
+
Bug Fixes
+-
+
- > GetChannelsAsync() Returns Task
- > GetEncryptionKeyAsync() public Task
- > GetChannelsAsync() Returns Task
- > GetEncryptionKeyAsync() public Task