This commit is contained in:
Bohica 2026-08-08 08:14:18 -05:00 committed by GitHub
commit fe796291e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 177 additions and 117 deletions

View file

@ -4,50 +4,50 @@ namespace Server.Engines.Chat
{
public class Channel
{
private readonly List<ChatUser> m_Banned;
private readonly List<ChatUser> m_Moderators;
private readonly List<ChatUser> m_Users;
private readonly List<ChatUser> m_Voices;
private string m_Name;
private string m_Password;
private bool m_VoiceRestricted;
private readonly List<ChatUser> _Banned;
private readonly List<ChatUser> _Moderators;
private readonly List<ChatUser> _Users;
private readonly List<ChatUser> _Voices;
private string _Name;
private string _Password;
private bool _VoiceRestricted;
public Channel(string name)
{
m_Name = name;
_Name = name;
m_Users = new List<ChatUser>();
m_Banned = new List<ChatUser>();
m_Moderators = new List<ChatUser>();
m_Voices = new List<ChatUser>();
_Users = new List<ChatUser>();
_Banned = new List<ChatUser>();
_Moderators = new List<ChatUser>();
_Voices = new List<ChatUser>();
}
public Channel(string name, string password) : this(name) => m_Password = password;
public Channel(string name, string password) : this(name) => _Password = password;
public string Name
{
get => m_Name;
get => _Name;
set
{
SendCommand(ChatCommand.RemoveChannel, m_Name);
m_Name = value;
SendCommand(ChatCommand.AddChannel, m_Name);
SendCommand(ChatCommand.JoinedChannel, m_Name);
SendCommand(ChatCommand.RemoveChannel, _Name);
_Name = value;
SendCommand(ChatCommand.AddChannel, _Name);
SendCommand(ChatCommand.JoinedChannel, _Name);
}
}
public string Password
{
get => m_Password;
set => m_Password = (value?.Trim()).DefaultIfNullOrEmpty(null);
get => _Password;
set => _Password = (value?.Trim()).DefaultIfNullOrEmpty(null);
}
public bool VoiceRestricted
{
get => m_VoiceRestricted;
get => _VoiceRestricted;
set
{
m_VoiceRestricted = value;
_VoiceRestricted = value;
if (value)
{
@ -63,20 +63,13 @@ namespace Server.Engines.Chat
}
public bool AlwaysAvailable { get; set; }
public static List<Channel> Channels { get; } = new();
public bool Contains(ChatUser user) => m_Users.Contains(user);
public bool IsBanned(ChatUser user) => m_Banned.Contains(user);
public bool CanTalk(ChatUser user) => !m_VoiceRestricted || m_Voices.Contains(user) || m_Moderators.Contains(user);
public bool IsModerator(ChatUser user) => m_Moderators.Contains(user);
public bool IsVoiced(ChatUser user) => m_Voices.Contains(user);
public bool ValidatePassword(string password) => m_Password?.InsensitiveEquals(password) != false;
public bool Contains(ChatUser user) => _Users.Contains(user);
public bool IsBanned(ChatUser user) => _Banned.Contains(user);
public bool CanTalk(ChatUser user) => !_VoiceRestricted || _Voices.Contains(user) || _Moderators.Contains(user);
public bool IsModerator(ChatUser user) => _Moderators.Contains(user);
public bool IsVoiced(ChatUser user) => _Voices.Contains(user);
public bool ValidatePassword(string password) => _Password?.InsensitiveEquals(password) != false;
public bool ValidateModerator(ChatUser user)
{
@ -104,7 +97,7 @@ namespace Server.Engines.Chat
{
if (Contains(user))
{
user.SendMessage(46, m_Name); // You are already in the conference '%1'.
user.SendMessage(46, _Name); // You are already in the conference '%1'.
return true;
}
@ -122,14 +115,14 @@ namespace Server.Engines.Chat
user.CurrentChannel?.RemoveUser(user); // Remove them from their current channel first
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.JoinedChannel, m_Name);
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.JoinedChannel, _Name);
SendCommand(ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
m_Users.Add(user);
_Users.Add(user);
user.CurrentChannel = this;
if (user.Mobile.AccessLevel >= AccessLevel.GameMaster || !AlwaysAvailable && m_Users.Count == 1)
if (user.Mobile.AccessLevel >= AccessLevel.GameMaster || !AlwaysAvailable && _Users.Count == 1)
{
AddModerator(user);
}
@ -143,23 +136,23 @@ namespace Server.Engines.Chat
{
if (Contains(user))
{
m_Users.Remove(user);
_Users.Remove(user);
user.CurrentChannel = null;
if (m_Moderators.Contains(user))
if (_Moderators.Contains(user))
{
m_Moderators.Remove(user);
_Moderators.Remove(user);
}
if (m_Voices.Contains(user))
if (_Voices.Contains(user))
{
m_Voices.Remove(user);
_Voices.Remove(user);
}
SendCommand(ChatCommand.RemoveUserFromChannel, user, user.Username);
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.LeaveChannel);
if (m_Users.Count == 0 && !AlwaysAvailable)
if (!AlwaysAvailable && _Users.Count == 0)
{
RemoveChannel(this);
}
@ -173,9 +166,9 @@ namespace Server.Engines.Chat
return;
}
if (!m_Banned.Contains(user))
if (!_Banned.Contains(user))
{
m_Banned.Add(user);
_Banned.Add(user);
}
Kick(user, moderator, true);
@ -183,9 +176,9 @@ namespace Server.Engines.Chat
public void RemoveBan(ChatUser user)
{
if (m_Banned.Contains(user))
if (_Banned.Contains(user))
{
m_Banned.Remove(user);
_Banned.Remove(user);
}
}
@ -242,7 +235,7 @@ namespace Server.Engines.Chat
if (!IsBanned(user) && !IsModerator(user) && !IsVoiced(user))
{
m_Voices.Add(user);
_Voices.Add(user);
if (moderator != null)
{
@ -264,7 +257,7 @@ namespace Server.Engines.Chat
if (!IsModerator(user) && IsVoiced(user))
{
m_Voices.Remove(user);
_Voices.Remove(user);
if (moderator != null)
{
@ -291,10 +284,10 @@ namespace Server.Engines.Chat
if (IsVoiced(user))
{
m_Voices.Remove(user);
_Voices.Remove(user);
}
m_Moderators.Add(user);
_Moderators.Add(user);
if (moderator != null)
{
@ -314,7 +307,7 @@ namespace Server.Engines.Chat
if (IsModerator(user))
{
m_Moderators.Remove(user);
_Moderators.Remove(user);
if (moderator != null)
{
@ -333,31 +326,31 @@ namespace Server.Engines.Chat
public void SendMessage(int number, ChatUser initiator, string param1 = null, string param2 = null)
{
for (var i = 0; i < m_Users.Count; ++i)
for (var i = _Users.Count - 1; i >= 0; --i)
{
var user = m_Users[i];
var user = _Users[i];
if (user == initiator)
{
continue;
}
if (user == initiator)
{
continue;
}
if (user.CheckOnline())
{
user.SendMessage(number, param1, param2);
}
else if (!Contains(user))
{
--i;
}
if (user.CheckOnline())
{
user.SendMessage(number, param1, param2);
}
else
{
RemoveUser(user);
}
}
}
public void SendIgnorableMessage(int number, ChatUser from, string param1, string param2)
{
for (var i = 0; i < m_Users.Count; ++i)
for (var i = _Users.Count - 1; i >= 0; --i)
{
var user = m_Users[i];
var user = _Users[i];
if (user.IsIgnored(from))
{
@ -368,9 +361,9 @@ namespace Server.Engines.Chat
{
user.SendMessage(number, from.Mobile, param1, param2);
}
else if (!Contains(user))
else
{
--i;
RemoveUser(user);
}
}
}
@ -382,9 +375,9 @@ namespace Server.Engines.Chat
public void SendCommand(ChatCommand command, ChatUser initiator, string param1 = null, string param2 = null)
{
for (var i = 0; i < m_Users.Count; ++i)
for (var i = _Users.Count - 1; i >= 0; --i)
{
var user = m_Users[i];
var user = _Users[i];
if (user == initiator)
{
@ -395,18 +388,18 @@ namespace Server.Engines.Chat
{
ChatSystem.SendCommandTo(user.Mobile, command, param1, param2);
}
else if (!Contains(user))
else
{
--i;
RemoveUser(user);
}
}
}
public void SendUsersTo(ChatUser to)
{
for (var i = 0; i < m_Users.Count; ++i)
for (var i = 0; i < _Users.Count; ++i)
{
var user = m_Users[i];
var user = _Users[i];
ChatSystem.SendCommandTo(to.Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username);
}
@ -452,12 +445,12 @@ namespace Server.Engines.Chat
return;
}
if (Channels.Contains(channel) && channel.m_Users.Count == 0)
if (Channels.Contains(channel) && channel._Users.Count == 0)
{
ChatUser.GlobalSendCommand(ChatCommand.RemoveChannel, channel.Name);
channel.m_Moderators.Clear();
channel.m_Voices.Clear();
channel._Moderators.Clear();
channel._Voices.Clear();
Channels.Remove(channel);
}
@ -469,7 +462,7 @@ namespace Server.Engines.Chat
{
var channel = Channels[i];
if (channel.m_Name == name)
if (channel._Name == name)
{
return channel;
}
@ -478,6 +471,9 @@ namespace Server.Engines.Chat
return null;
}
// currently, all chat is combined to a single webhook
// all static channels will be captured
// also captures player created channels
public static void Initialize()
{
AddStaticChannel("Newbie Help");

View file

@ -7,6 +7,7 @@ namespace Server.Engines.Chat
public static void Configure()
{
Enabled = ServerConfiguration.GetOrUpdateSetting("chat.enabled", false);
Discord.Configure();
}
public static void SendCommandTo(Mobile to, ChatCommand type, string param1 = null, string param2 = null)

View file

@ -12,9 +12,7 @@ namespace Server.Engines.Chat
}
public bool RequireModerator { get; }
public bool RequireConference { get; }
public OnChatAction Callback { get; }
}
}

View file

@ -2,16 +2,14 @@ namespace Server.Engines.Chat
{
public static class ChatActionHandlers
{
private static readonly ChatActionHandler[] m_Handlers;
private static readonly ChatActionHandler[] _Handlers;
static ChatActionHandlers()
{
m_Handlers = new ChatActionHandler[0x100];
_Handlers = new ChatActionHandler[0x100];
Register(0x41, true, true, ChangeChannelPassword);
Register(0x58, false, false, LeaveChat);
Register(0x61, false, true, ChannelMessage);
Register(0x62, false, false, JoinChannel);
Register(0x63, false, false, JoinNewChannel);
@ -42,17 +40,17 @@ namespace Server.Engines.Chat
public static void Register(int actionID, bool requireModerator, bool requireConference, OnChatAction callback)
{
if (actionID >= 0 && actionID < m_Handlers.Length)
if (actionID >= 0 && actionID < _Handlers.Length)
{
m_Handlers[actionID] = new ChatActionHandler(requireModerator, requireConference, callback);
_Handlers[actionID] = new ChatActionHandler(requireModerator, requireConference, callback);
}
}
public static ChatActionHandler GetHandler(int actionID)
{
if (actionID >= 0 && actionID < m_Handlers.Length)
if (actionID >= 0 && actionID < _Handlers.Length)
{
return m_Handlers[actionID];
return _Handlers[actionID];
}
return null;
@ -63,6 +61,8 @@ namespace Server.Engines.Chat
if (channel.CanTalk(from))
{
channel.SendIgnorableMessage(57, from, from.GetColorCharacter() + from.Username, param); // %1: %2
_ = Discord.SendChannelMessageAsync(channel.Name, from.Username, param);
}
else
{
@ -75,6 +75,8 @@ namespace Server.Engines.Chat
if (channel.CanTalk(from))
{
channel.SendIgnorableMessage(58, from, from.GetColorCharacter() + from.Username, param); // %1 %2
_ = Discord.SendChannelMessageAsync(channel.Name, from.Username, param);
}
else
{

View file

@ -15,12 +15,15 @@
using System;
using System.Buffers;
using Server.Logging;
using Server.Network;
namespace Server.Engines.Chat
{
public static class ChatPackets
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChatPackets));
public static unsafe void Configure()
{
IncomingPackets.Register(0xB5, 0x40, true, &OpenChatWindowRequest);
@ -97,7 +100,7 @@ namespace Server.Engines.Chat
}
catch (Exception e)
{
Console.WriteLine(e);
logger.Error(e, "Exception in chat action handler");
}
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Server.Collections;
namespace Server.Engines.Chat
{
@ -8,8 +9,8 @@ namespace Server.Engines.Chat
public const char ModeratorColorCharacter = '1';
public const char VoicedColorCharacter = '2';
private static readonly List<ChatUser> m_Users = new();
private static readonly Dictionary<Mobile, ChatUser> m_Table = new();
private static readonly List<ChatUser> _Users = new();
private static readonly Dictionary<Mobile, ChatUser> _Table = new();
public ChatUser(Mobile m, string username)
{
@ -20,25 +21,16 @@ namespace Server.Engines.Chat
}
public Mobile Mobile { get; }
public List<ChatUser> Ignored { get; }
public List<ChatUser> Ignoring { get; }
public string Username { get; }
public Channel CurrentChannel { get; set; }
public bool IsOnline => Mobile.NetState != null;
public bool Anonymous { get; set; }
public bool IgnorePrivateMessage { get; set; }
public bool IsModerator => CurrentChannel?.IsModerator(this) == true;
public char GetColorCharacter() =>
IsModerator ? ModeratorColorCharacter :
public char GetColorCharacter() => IsModerator ? ModeratorColorCharacter :
CurrentChannel?.IsVoiced(this) == true ? VoicedColorCharacter : NormalColorCharacter;
public bool CheckOnline()
@ -101,13 +93,14 @@ namespace Server.Engines.Chat
if (user != null)
{
Channel.SendChannelsTo(user);
return user;
}
user = new ChatUser(from, username);
m_Users.Add(user);
m_Table[from] = user;
_Users.Add(user);
_Table[from] = user;
Channel.SendChannelsTo(user);
@ -123,7 +116,7 @@ namespace Server.Engines.Chat
}
}
// ChatSystem.SendCommandTo( user.m_Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username );
// ChatSystem.SendCommandTo( user._Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username );
return user;
}
@ -135,32 +128,39 @@ namespace Server.Engines.Chat
return;
}
using var ignoringUsers = new PooledRefList<ChatUser>();
for (var i = 0; i < user.Ignoring.Count; ++i)
{
user.Ignoring[i].RemoveIgnored(user);
ignoringUsers.Add(user.Ignoring[i]);
}
for (var i = 0; i < ignoringUsers.Count; i++)
{
ignoringUsers[i].RemoveIgnored(user);
}
if (m_Users.Remove(user))
if (_Users.Remove(user))
{
ChatSystem.SendCommandTo(user.Mobile, ChatCommand.CloseChatWindow);
user.CurrentChannel?.RemoveUser(user);
m_Table.Remove(user.Mobile);
_Table.Remove(user.Mobile);
}
}
public static ChatUser GetChatUser(Mobile from)
{
m_Table.TryGetValue(from, out var c);
_Table.TryGetValue(from, out var c);
return c;
}
public static ChatUser GetChatUser(string username)
{
for (var i = 0; i < m_Users.Count; ++i)
for (var i = 0; i < _Users.Count; ++i)
{
var user = m_Users[i];
var user = _Users[i];
if (user.Username == username)
{
@ -180,9 +180,11 @@ namespace Server.Engines.Chat
ChatCommand command, ChatUser initiator = null, string param1 = null, string param2 = null
)
{
for (var i = 0; i < m_Users.Count; ++i)
using var usersToProcess = new PooledRefList<ChatUser>();
for (var i = 0; i < _Users.Count; ++i)
{
var user = m_Users[i];
var user = _Users[i];
if (user == initiator)
{
@ -191,9 +193,14 @@ namespace Server.Engines.Chat
if (user.CheckOnline())
{
ChatSystem.SendCommandTo(user.Mobile, command, param1, param2);
usersToProcess.Add(user);
}
}
for (var i = 0; i < usersToProcess.Count; i++)
{
ChatSystem.SendCommandTo(usersToProcess[i].Mobile, command, param1, param2);
}
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Server.Logging;
namespace Server.Engines.Chat
{
public static class Discord
{
private static readonly HttpClient _httpClient = new();
private static readonly ILogger _logger = LogFactory.GetLogger(typeof(Discord));
private static string _webhookUrl;
public static void Configure()
{
_webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK");
_logger.Information("Discord integration configured. Enabled: {Enabled}", IsEnabled);
}
public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl);
public static async Task SendChannelMessageAsync(string channelName, string username, string message)
{
var webhookUrl = _webhookUrl;
if (string.IsNullOrEmpty(webhookUrl))
{
return;
}
try
{
var payload = new
{
username = $"UO Chat",
content = $"**[{channelName}]** {username}: {message}",
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
await _httpClient.PostAsync(_webhookUrl, content);
_logger.Debug("Discord message sent for channel {ChannelName} user {Username}", channelName, username);
}
catch (Exception ex)
{
_logger.Error(ex, "Discord webhook failed for channel {ChannelName} user {Username}", channelName, username);
}
}
}
}