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(
|
TerritoryIds: TerritoryRegistry.GetIds(
|
||||||
"Ul'dah - Steps of Nald",
|
"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 System.Security.Cryptography;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto.Chat;
|
using LightlessSync.API.Dto.Chat;
|
||||||
|
using LightlessSyncServer.Configuration;
|
||||||
using LightlessSyncServer.Models;
|
using LightlessSyncServer.Models;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace LightlessSyncServer.Services;
|
namespace LightlessSyncServer.Services;
|
||||||
|
|
||||||
@@ -21,14 +23,132 @@ public sealed class ChatChannelService : IDisposable
|
|||||||
private static readonly TimeSpan InactiveParticipantCleanupInterval = TimeSpan.FromMinutes(1);
|
private static readonly TimeSpan InactiveParticipantCleanupInterval = TimeSpan.FromMinutes(1);
|
||||||
private readonly Timer _inactiveParticipantCleanupTimer;
|
private readonly Timer _inactiveParticipantCleanupTimer;
|
||||||
|
|
||||||
public ChatChannelService(ILogger<ChatChannelService> logger)
|
public ChatChannelService(ILogger<ChatChannelService> logger, IOptions<ChatZoneOverridesOptions>? zoneOverrides = null)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_zoneDefinitions = ChatZoneDefinitions.Defaults
|
_zoneDefinitions = BuildZoneDefinitions(zoneOverrides?.Value);
|
||||||
.ToDictionary(definition => definition.Key, StringComparer.OrdinalIgnoreCase);
|
|
||||||
_inactiveParticipantCleanupTimer = new Timer(_ => CleanupExpiredInactiveParticipants(), null, InactiveParticipantCleanupInterval, InactiveParticipantCleanupInterval);
|
_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() =>
|
public IReadOnlyList<ZoneChatChannelInfoDto> GetZoneChannelInfos() =>
|
||||||
_zoneDefinitions.Values
|
_zoneDefinitions.Values
|
||||||
.Select(definition => new ZoneChatChannelInfoDto(
|
.Select(definition => new ZoneChatChannelInfoDto(
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ public class Startup
|
|||||||
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("LightlessSync"));
|
services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("LightlessSync"));
|
||||||
services.Configure<LightlessConfigurationBase>(Configuration.GetRequiredSection("LightlessSync"));
|
services.Configure<LightlessConfigurationBase>(Configuration.GetRequiredSection("LightlessSync"));
|
||||||
services.Configure<BroadcastOptions>(Configuration.GetSection("Broadcast"));
|
services.Configure<BroadcastOptions>(Configuration.GetSection("Broadcast"));
|
||||||
|
services.Configure<ChatZoneOverridesOptions>(Configuration.GetSection("ChatZoneOverrides"));
|
||||||
|
|
||||||
services.AddSingleton<IBroadcastConfiguration, BroadcastConfiguration>();
|
services.AddSingleton<IBroadcastConfiguration, BroadcastConfiguration>();
|
||||||
services.AddSingleton<ServerTokenGenerator>();
|
services.AddSingleton<ServerTokenGenerator>();
|
||||||
|
|||||||
@@ -41,6 +41,9 @@
|
|||||||
"PairRequestRateLimit": 5,
|
"PairRequestRateLimit": 5,
|
||||||
"PairRequestRateWindow": 60
|
"PairRequestRateWindow": 60
|
||||||
},
|
},
|
||||||
|
"ChatZoneOverrides": {
|
||||||
|
"Zones": []
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
"Endpoints": {
|
"Endpoints": {
|
||||||
|
|||||||
Reference in New Issue
Block a user