chat zone config, add more default zones
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LightlessSyncServer.Configuration;
|
||||
|
||||
public sealed class ChatZoneOverridesOptions
|
||||
{
|
||||
public List<ChatZoneOverride>? Zones { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ChatZoneOverride
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string? DisplayName { get; set; }
|
||||
public List<string>? TerritoryNames { get; set; }
|
||||
public List<ushort>? TerritoryIds { get; set; }
|
||||
}
|
||||
@@ -61,7 +61,160 @@ internal static class ChatZoneDefinitions
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds(
|
||||
"Ul'dah - Steps of Nald",
|
||||
"Ul'dah - Steps of Thal"))
|
||||
"Ul'dah - Steps of Thal")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "ishgard",
|
||||
DisplayName: "Ishgard",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "ishgard"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Foundation",
|
||||
"The Pillars"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds(
|
||||
"Foundation",
|
||||
"The Pillars")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "kugane",
|
||||
DisplayName: "Kugane",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "kugane"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Kugane"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Kugane")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "crystarium",
|
||||
DisplayName: "The Crystarium",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "crystarium"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"The Crystarium"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("The Crystarium")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "oldsharlayan",
|
||||
DisplayName: "Old Sharlayan",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "oldsharlayan"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Old Sharlayan"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Old Sharlayan")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "tuliyollal",
|
||||
DisplayName: "Tuliyollal",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "tuliyollal"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Tuliyollal"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Tuliyollal")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "eulmore",
|
||||
DisplayName: "Eulmore",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "eulmore"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Eulmore"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Eulmore")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "idyllshire",
|
||||
DisplayName: "Idyllshire",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "idyllshire"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Idyllshire"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Idyllshire")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "rhalgrsreach",
|
||||
DisplayName: "Rhalgr's Reach",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "rhalgrsreach"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Rhalgr's Reach"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Rhalgr's Reach")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "radzathan",
|
||||
DisplayName: "Radz-at-Han",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "radzathan"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Radz-at-Han"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Radz-at-Han")),
|
||||
new ZoneChannelDefinition(
|
||||
Key: "solutionnine",
|
||||
DisplayName: "Solution Nine",
|
||||
Descriptor: new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = "solutionnine"
|
||||
},
|
||||
TerritoryNames: new[]
|
||||
{
|
||||
"Solution Nine"
|
||||
},
|
||||
TerritoryIds: TerritoryRegistry.GetIds("Solution Nine"))
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Security.Cryptography;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Dto.Chat;
|
||||
using LightlessSyncServer.Configuration;
|
||||
using LightlessSyncServer.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace LightlessSyncServer.Services;
|
||||
|
||||
@@ -21,14 +23,132 @@ public sealed class ChatChannelService : IDisposable
|
||||
private static readonly TimeSpan InactiveParticipantCleanupInterval = TimeSpan.FromMinutes(1);
|
||||
private readonly Timer _inactiveParticipantCleanupTimer;
|
||||
|
||||
public ChatChannelService(ILogger<ChatChannelService> logger)
|
||||
public ChatChannelService(ILogger<ChatChannelService> logger, IOptions<ChatZoneOverridesOptions>? zoneOverrides = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_zoneDefinitions = ChatZoneDefinitions.Defaults
|
||||
.ToDictionary(definition => definition.Key, StringComparer.OrdinalIgnoreCase);
|
||||
_zoneDefinitions = BuildZoneDefinitions(zoneOverrides?.Value);
|
||||
_inactiveParticipantCleanupTimer = new Timer(_ => CleanupExpiredInactiveParticipants(), null, InactiveParticipantCleanupInterval, InactiveParticipantCleanupInterval);
|
||||
}
|
||||
|
||||
private Dictionary<string, ZoneChannelDefinition> BuildZoneDefinitions(ChatZoneOverridesOptions? overrides)
|
||||
{
|
||||
var definitions = ChatZoneDefinitions.Defaults
|
||||
.ToDictionary(definition => definition.Key, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (overrides?.Zones is null || overrides.Zones.Count == 0)
|
||||
{
|
||||
return definitions;
|
||||
}
|
||||
|
||||
foreach (var entry in overrides.Zones)
|
||||
{
|
||||
if (entry is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryCreateZoneDefinition(entry, out var definition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
definitions[definition.Key] = definition;
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private bool TryCreateZoneDefinition(ChatZoneOverride entry, out ZoneChannelDefinition definition)
|
||||
{
|
||||
definition = default;
|
||||
|
||||
var key = NormalizeZoneKey(entry.Key);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
_logger.LogWarning("Skipped chat zone override with missing key.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var territoryIds = new HashSet<ushort>();
|
||||
if (entry.TerritoryIds is not null)
|
||||
{
|
||||
foreach (var candidate in entry.TerritoryIds)
|
||||
{
|
||||
if (candidate > 0)
|
||||
{
|
||||
territoryIds.Add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var territoryNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (entry.TerritoryNames is not null)
|
||||
{
|
||||
foreach (var name in entry.TerritoryNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
continue;
|
||||
|
||||
var trimmed = name.Trim();
|
||||
territoryNames.Add(trimmed);
|
||||
if (TerritoryRegistry.TryGetIds(trimmed, out var ids))
|
||||
{
|
||||
territoryIds.UnionWith(ids);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Chat zone override {Zone} references unknown territory '{Territory}'.", key, trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (territoryIds.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("Skipped chat zone override for {Zone}: no territory IDs resolved.", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (territoryNames.Count == 0)
|
||||
{
|
||||
foreach (var territoryId in territoryIds)
|
||||
{
|
||||
if (TerritoryRegistry.ById.TryGetValue(territoryId, out var territory))
|
||||
{
|
||||
territoryNames.Add(territory.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (territoryNames.Count == 0)
|
||||
{
|
||||
territoryNames.Add("Territory");
|
||||
}
|
||||
|
||||
var descriptor = new ChatChannelDescriptor
|
||||
{
|
||||
Type = ChatChannelType.Zone,
|
||||
WorldId = 0,
|
||||
ZoneId = 0,
|
||||
CustomKey = key
|
||||
};
|
||||
|
||||
var displayName = string.IsNullOrWhiteSpace(entry.DisplayName)
|
||||
? key
|
||||
: entry.DisplayName.Trim();
|
||||
|
||||
definition = new ZoneChannelDefinition(
|
||||
key,
|
||||
displayName,
|
||||
descriptor,
|
||||
territoryNames.ToArray(),
|
||||
territoryIds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string NormalizeZoneKey(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
|
||||
|
||||
public IReadOnlyList<ZoneChatChannelInfoDto> GetZoneChannelInfos() =>
|
||||
_zoneDefinitions.Values
|
||||
.Select(definition => new ZoneChatChannelInfoDto(
|
||||
|
||||
@@ -93,6 +93,7 @@ public class Startup
|
||||
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("LightlessSync"));
|
||||
services.Configure<LightlessConfigurationBase>(Configuration.GetRequiredSection("LightlessSync"));
|
||||
services.Configure<BroadcastOptions>(Configuration.GetSection("Broadcast"));
|
||||
services.Configure<ChatZoneOverridesOptions>(Configuration.GetSection("ChatZoneOverrides"));
|
||||
|
||||
services.AddSingleton<IBroadcastConfiguration, BroadcastConfiguration>();
|
||||
services.AddSingleton<ServerTokenGenerator>();
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
"PairRequestRateLimit": 5,
|
||||
"PairRequestRateWindow": 60
|
||||
},
|
||||
"ChatZoneOverrides": {
|
||||
"Zones": []
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
|
||||
Reference in New Issue
Block a user