Disable global nullable.

This commit is contained in:
flash 2025-04-25 18:18:13 +00:00
parent 40f8fc2e86
commit f5c8f2ae1d
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
40 changed files with 209 additions and 200 deletions

View file

@ -23,7 +23,7 @@ namespace SharpChat {
Inherits = false; Inherits = false;
} }
public override bool Equals([NotNullWhen(true)] object obj) { public override bool Equals([NotNullWhen(true)] object? obj) {
return obj is ChatColour colour && Equals(colour); return obj is ChatColour colour && Equals(colour);
} }

View file

@ -18,7 +18,7 @@ namespace SharpChat {
public string Id { get; } public string Id { get; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now; public DateTimeOffset LastPing { get; set; } = DateTimeOffset.Now;
public ChatUser User { get; set; } public ChatUser? User { get; set; }
private int CloseCode { get; set; } = 1000; private int CloseCode { get; set; } = 1000;
@ -27,18 +27,16 @@ namespace SharpChat {
public bool IsAlive => !IsDisposed && !HasTimedOut; public bool IsAlive => !IsDisposed && !HasTimedOut;
public bool IsAuthed => IsAlive && User is not null;
public ChatConnection(IWebSocketConnection sock) { public ChatConnection(IWebSocketConnection sock) {
Socket = sock; Socket = sock;
Id = RNG.SecureRandomString(ID_LENGTH); Id = RNG.SecureRandomString(ID_LENGTH);
if(!IPAddress.TryParse(sock.ConnectionInfo.ClientIpAddress, out IPAddress addr)) if(!IPAddress.TryParse(sock.ConnectionInfo.ClientIpAddress, out IPAddress? addr))
throw new Exception("Unable to parse remote address?????"); throw new Exception("Unable to parse remote address?????");
if(IPAddress.IsLoopback(addr) if(IPAddress.IsLoopback(addr)
&& sock.ConnectionInfo.Headers.TryGetValue("X-Real-IP", out string addrStr) && sock.ConnectionInfo.Headers.TryGetValue("X-Real-IP", out string? addrStr)
&& IPAddress.TryParse(addrStr, out IPAddress realAddr)) && IPAddress.TryParse(addrStr, out IPAddress? realAddr))
addr = realAddr; addr = realAddr;
RemoteAddress = addr; RemoteAddress = addr;

View file

@ -38,7 +38,7 @@ namespace SharpChat {
return; return;
IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId)); IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId));
ChatUser target = users.FirstOrDefault(u => u.UserId != mce.SenderId); ChatUser? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
if(target == null) if(target == null)
return; return;
@ -52,15 +52,16 @@ namespace SharpChat {
true true
)); ));
} else { } else {
ChatChannel channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName)); ChatChannel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
SendTo(channel, new ChatMessageAddPacket( if(channel is not null)
mce.MessageId, SendTo(channel, new ChatMessageAddPacket(
DateTimeOffset.Now, mce.MessageId,
mce.SenderId, DateTimeOffset.Now,
mce.MessageText, mce.SenderId,
mce.IsAction, mce.MessageText,
false mce.IsAction,
)); false
));
} }
Events.AddEvent( Events.AddEvent(
@ -125,11 +126,11 @@ namespace SharpChat {
public void UpdateUser( public void UpdateUser(
ChatUser user, ChatUser user,
string userName = null, string? userName = null,
string nickName = null, string? nickName = null,
ChatColour? colour = null, ChatColour? colour = null,
ChatUserStatus? status = null, ChatUserStatus? status = null,
string statusText = null, string? statusText = null,
int? rank = null, int? rank = null,
ChatUserPermissions? perms = null, ChatUserPermissions? perms = null,
bool? isSuper = null, bool? isSuper = null,
@ -241,7 +242,7 @@ namespace SharpChat {
} }
public void SwitchChannel(ChatUser user, ChatChannel chan, string password) { public void SwitchChannel(ChatUser user, ChatChannel chan, string password) {
if(UserLastChannel.TryGetValue(user.UserId, out ChatChannel ulc) && chan == ulc) { if(UserLastChannel.TryGetValue(user.UserId, out ChatChannel? ulc) && chan == ulc) {
ForceChannel(user); ForceChannel(user);
return; return;
} }
@ -294,7 +295,7 @@ namespace SharpChat {
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.IsAuthed) if(conn.IsAlive && conn.User is not null)
conn.Send(packet); conn.Send(packet);
} }
@ -312,7 +313,7 @@ namespace SharpChat {
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
// might be faster to grab the users first and then cascade into that SendTo // might be faster to grab the users first and then cascade into that SendTo
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel)); IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAlive && c.User is not null && IsInChannel(c.User, channel));
foreach(ChatConnection conn in conns) foreach(ChatConnection conn in conns)
conn.Send(packet); conn.Send(packet);
} }
@ -322,7 +323,7 @@ namespace SharpChat {
ArgumentNullException.ThrowIfNull(packet); ArgumentNullException.ThrowIfNull(packet);
IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c)); IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c));
IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName)))); IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAlive && conn.User is not null && ChannelUsers.Any(cu => cu.UserId == conn.User.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
foreach(ChatConnection conn in conns) foreach(ChatConnection conn in conns)
conn.Send(packet); conn.Send(packet);
} }
@ -331,7 +332,7 @@ namespace SharpChat {
return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()]; return [.. Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct()];
} }
public void ForceChannel(ChatUser user, ChatChannel chan = null) { public void ForceChannel(ChatUser user, ChatChannel? chan = null) {
ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(user);
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
@ -340,7 +341,7 @@ namespace SharpChat {
SendTo(user, new UserChannelForceJoinPacket(chan)); SendTo(user, new UserChannelForceJoinPacket(chan));
} }
public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string password = null) { public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? hierarchy = null, string? password = null) {
ArgumentNullException.ThrowIfNull(channel); ArgumentNullException.ThrowIfNull(channel);
if(!Channels.Contains(channel)) if(!Channels.Contains(channel))
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel)); throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
@ -364,8 +365,8 @@ namespace SharpChat {
if(channel == null || Channels.Count < 1) if(channel == null || Channels.Count < 1)
return; return;
ChatChannel defaultChannel = Channels.FirstOrDefault(); ChatChannel? defaultChannel = Channels.FirstOrDefault();
if(defaultChannel == null) if(defaultChannel is null)
return; return;
// Remove channel from the listing // Remove channel from the listing

View file

@ -24,9 +24,9 @@ namespace SharpChat {
public int Rank { get; set; } = rank; public int Rank { get; set; } = rank;
public ChatUserPermissions Permissions { get; set; } = perms; public ChatUserPermissions Permissions { get; set; } = perms;
public bool IsSuper { get; set; } public bool IsSuper { get; set; }
public string NickName { get; set; } = nickName ?? string.Empty; public string NickName { get; set; } = nickName;
public ChatUserStatus Status { get; set; } = status; public ChatUserStatus Status { get; set; } = status;
public string StatusText { get; set; } = statusText ?? string.Empty; public string StatusText { get; set; } = statusText;
public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}"; public string LegacyName => string.IsNullOrWhiteSpace(NickName) ? UserName : $"~{NickName}";

View file

@ -15,7 +15,7 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
string statusText = ctx.Args.FirstOrDefault(); string? statusText = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(statusText)) if(string.IsNullOrWhiteSpace(statusText))
statusText = DEFAULT; statusText = DEFAULT;
else { else {

View file

@ -19,9 +19,12 @@ namespace SharpChat.Commands {
} }
Task.Run(async () => { Task.Run(async () => {
ctx.Chat.SendTo(ctx.User, new BanListPacket( MisuzuBanInfo[]? mbi = await Misuzu.GetBanListAsync();
await Misuzu.GetBanListAsync()
)); if(mbi is null)
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true));
else
ctx.Chat.SendTo(ctx.User, new BanListPacket(mbi));
}).Wait(); }).Wait();
} }
} }

View file

@ -17,7 +17,7 @@ namespace SharpChat.Commands {
} }
string delChanName = string.Join('_', ctx.Args); string delChanName = string.Join('_', ctx.Args);
ChatChannel delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName)); ChatChannel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) { if(delChan == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, delChanName));

View file

@ -20,16 +20,16 @@ namespace SharpChat.Commands
return; return;
} }
string firstArg = ctx.Args.FirstOrDefault(); string? firstArg = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) { if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
StoredEventInfo delMsg = ctx.Chat.Events.GetEvent(delSeqId); StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId);
if(delMsg == null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) { if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.MESSAGE_DELETE_ERROR));
return; return;
} }

View file

@ -8,10 +8,10 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
string joinChanStr = ctx.Args.FirstOrDefault(); string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
ChatChannel joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr)); ChatChannel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan == null) { if(joinChan is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
ctx.Chat.ForceChannel(ctx.User); ctx.Chat.ForceChannel(ctx.User);
return; return;

View file

@ -21,13 +21,13 @@ namespace SharpChat.Commands {
return; return;
} }
string banUserTarget = ctx.Args.ElementAtOrDefault(0); string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
string banDurationStr = ctx.Args.ElementAtOrDefault(1); string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
int banReasonIndex = 1; int banReasonIndex = 1;
ChatUser banUser = null; ChatUser? banUser = null;
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) { if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUser == null ? "User" : banUserTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));
return; return;
} }
@ -59,7 +59,11 @@ namespace SharpChat.Commands {
string userIp = ctx.Chat.GetRemoteAddresses(banUser).FirstOrDefault()?.ToString() ?? string.Empty; string userIp = ctx.Chat.GetRemoteAddresses(banUser).FirstOrDefault()?.ToString() ?? string.Empty;
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations // obviously it makes no sense to only check for one ip address but that's current misuzu limitations
MisuzuBanInfo fbi = await Misuzu.CheckBanAsync(userId, userIp); MisuzuBanInfo? fbi = await Misuzu.CheckBanAsync(userId, userIp);
if(fbi is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true));
return;
}
if(fbi.IsBanned && !fbi.HasExpired) { if(fbi.IsBanned && !fbi.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));

View file

@ -20,7 +20,8 @@ namespace SharpChat.Commands {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return; return;
} }
ChatUser targetUser = null;
ChatUser? targetUser = null;
int offset = 0; int offset = 0;
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) { if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
@ -56,7 +57,7 @@ namespace SharpChat.Commands {
return; return;
} }
string previousName = targetUser == ctx.User ? (targetUser.NickName ?? targetUser.UserName) : null; string? previousName = targetUser.UserId == ctx.User.UserId ? (targetUser.NickName ?? targetUser.UserName) : null;
ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null); ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null);
} }
} }

View file

@ -20,8 +20,8 @@ namespace SharpChat.Commands {
return; return;
} }
string unbanAddrTarget = ctx.Args.FirstOrDefault(); string? unbanAddrTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress unbanAddr)) { if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
@ -29,7 +29,11 @@ namespace SharpChat.Commands {
unbanAddrTarget = unbanAddr.ToString(); unbanAddrTarget = unbanAddr.ToString();
Task.Run(async () => { Task.Run(async () => {
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget); MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget);
if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true));
return;
}
if(!banInfo.IsBanned || banInfo.HasExpired) { if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));

View file

@ -20,13 +20,13 @@ namespace SharpChat.Commands {
} }
bool unbanUserTargetIsName = true; bool unbanUserTargetIsName = true;
string unbanUserTarget = ctx.Args.FirstOrDefault(); string? unbanUserTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanUserTarget)) { if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
return; return;
} }
ChatUser unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget)); ChatUser? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) { if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
unbanUserTargetIsName = false; unbanUserTargetIsName = false;
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId); unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);
@ -36,7 +36,11 @@ namespace SharpChat.Commands {
unbanUserTarget = unbanUser.UserId.ToString(); unbanUserTarget = unbanUser.UserId.ToString();
Task.Run(async () => { Task.Run(async () => {
MisuzuBanInfo banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName); MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName);
if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.GENERIC_ERROR, true));
return;
}
if(!banInfo.IsBanned || banInfo.HasExpired) { if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));

View file

@ -15,8 +15,8 @@ namespace SharpChat.Commands {
return; return;
} }
string ipUserStr = ctx.Args.FirstOrDefault(); string? ipUserStr = ctx.Args.FirstOrDefault();
ChatUser ipUser; ChatUser? ipUser = null;
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) { if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User")); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));

View file

@ -16,8 +16,8 @@ namespace SharpChat.Commands {
return; return;
} }
string whisperUserStr = ctx.Args.FirstOrDefault(); string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
ChatUser whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr)); ChatUser? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) { if(whisperUser == null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, whisperUserStr));

View file

@ -10,7 +10,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
StringBuilder whoChanSB = new(); StringBuilder whoChanSB = new();
string whoChanStr = ctx.Args.FirstOrDefault(); string? whoChanStr = ctx.Args.FirstOrDefault();
if(string.IsNullOrEmpty(whoChanStr)) { if(string.IsNullOrEmpty(whoChanStr)) {
foreach(ChatUser whoUser in ctx.Chat.Users) { foreach(ChatUser whoUser in ctx.Chat.Users) {
@ -29,9 +29,9 @@ namespace SharpChat.Commands {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.USERS_LISTING_SERVER, false, whoChanSB));
} else { } else {
ChatChannel whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr)); ChatChannel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
if(whoChan == null) { if(whoChan is null) {
ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr)); ctx.Chat.SendTo(ctx.User, new LegacyCommandResponse(LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
return; return;
} }

View file

@ -1,15 +1,15 @@
using System; using System;
namespace SharpChat.Config { namespace SharpChat.Config {
public class CachedValue<T>(IConfig config, string name, TimeSpan lifetime, T fallback) { public class CachedValue<T>(IConfig config, string name, TimeSpan lifetime, T? fallback) {
private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config)); private IConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); private string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
private object ConfigAccess { get; } = new(); private object ConfigAccess { get; } = new();
private object CurrentValue { get; set; } private object? CurrentValue { get; set; } = default(T);
private DateTimeOffset LastRead { get; set; } private DateTimeOffset LastRead { get; set; }
public T Value { public T? Value {
get { get {
lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls lock(ConfigAccess) { // this lock doesn't really make sense since it doesn't affect other config calls
DateTimeOffset now = DateTimeOffset.Now; DateTimeOffset now = DateTimeOffset.Now;
@ -19,18 +19,18 @@ namespace SharpChat.Config {
Logger.Debug($"Read {Name} ({CurrentValue})"); Logger.Debug($"Read {Name} ({CurrentValue})");
} }
} }
return (T)CurrentValue; return (T?)CurrentValue;
} }
} }
public static implicit operator T(CachedValue<T> val) => val.Value; public static implicit operator T?(CachedValue<T?> val) => val.Value;
public void Refresh() { public void Refresh() {
LastRead = DateTimeOffset.MinValue; LastRead = DateTimeOffset.MinValue;
} }
public override string ToString() { public override string ToString() {
return Value.ToString(); return Value?.ToString() ?? string.Empty;
} }
} }
} }

View file

@ -10,22 +10,22 @@ namespace SharpChat.Config {
/// <summary> /// <summary>
/// Reads a raw (string) value from the config. /// Reads a raw (string) value from the config.
/// </summary> /// </summary>
string ReadValue(string name, string fallback = null); string? ReadValue(string name, string? fallback = null);
/// <summary> /// <summary>
/// Reads and casts value from the config. /// Reads and casts value from the config.
/// </summary> /// </summary>
/// <exception cref="ConfigTypeException">Type conversion failed.</exception> /// <exception cref="ConfigTypeException">Type conversion failed.</exception>
T ReadValue<T>(string name, T fallback = default); T? ReadValue<T>(string name, T? fallback = default);
/// <summary> /// <summary>
/// Reads and casts a value from the config. Returns fallback when type conversion fails. /// Reads and casts a value from the config. Returns fallback when type conversion fails.
/// </summary> /// </summary>
T SafeReadValue<T>(string name, T fallback); T? SafeReadValue<T>(string name, T? fallback);
/// <summary> /// <summary>
/// Creates an object that caches the read value for a certain amount of time, avoiding disk reads for frequently used non-static values. /// Creates an object that caches the read value for a certain amount of time, avoiding disk reads for frequently used non-static values.
/// </summary> /// </summary>
CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null); CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null);
} }
} }

View file

@ -9,15 +9,15 @@ namespace SharpChat.Config {
return Prefix + name; return Prefix + name;
} }
public string ReadValue(string name, string fallback = null) { public string? ReadValue(string name, string? fallback = null) {
return Config.ReadValue(GetName(name), fallback); return Config.ReadValue(GetName(name), fallback);
} }
public T ReadValue<T>(string name, T fallback = default) { public T? ReadValue<T>(string name, T? fallback = default) {
return Config.ReadValue(GetName(name), fallback); return Config.ReadValue(GetName(name), fallback);
} }
public T SafeReadValue<T>(string name, T fallback) { public T? SafeReadValue<T>(string name, T? fallback) {
return Config.SafeReadValue(GetName(name), fallback); return Config.SafeReadValue(GetName(name), fallback);
} }
@ -25,7 +25,7 @@ namespace SharpChat.Config {
return Config.ScopeTo(GetName(prefix)); return Config.ScopeTo(GetName(prefix));
} }
public CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null) { public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
return Config.ReadCached(GetName(name), fallback, lifetime); return Config.ReadCached(GetName(name), fallback, lifetime);
} }

View file

@ -27,14 +27,14 @@ namespace SharpChat.Config {
return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)); return new StreamConfig(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite));
} }
public string ReadValue(string name, string fallback = null) { public string? ReadValue(string name, string? fallback = null) {
if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong if(!Lock.WaitOne(LOCK_TIMEOUT)) // don't catch this, if this happens something is Very Wrong
throw new ConfigLockException(); throw new ConfigLockException();
try { try {
Stream.Seek(0, SeekOrigin.Begin); Stream.Seek(0, SeekOrigin.Begin);
string line; string? line;
while((line = StreamReader.ReadLine()) != null) { while((line = StreamReader.ReadLine()) != null) {
if(string.IsNullOrWhiteSpace(line)) if(string.IsNullOrWhiteSpace(line))
continue; continue;
@ -56,8 +56,8 @@ namespace SharpChat.Config {
return fallback; return fallback;
} }
public T ReadValue<T>(string name, T fallback = default) { public T? ReadValue<T>(string name, T? fallback = default) {
object value = ReadValue(name); object? value = ReadValue(name);
if(value == null) if(value == null)
return fallback; return fallback;
@ -77,7 +77,7 @@ namespace SharpChat.Config {
} }
} }
public T SafeReadValue<T>(string name, T fallback) { public T? SafeReadValue<T>(string name, T? fallback) {
try { try {
return ReadValue(name, fallback); return ReadValue(name, fallback);
} catch(ConfigTypeException) { } catch(ConfigTypeException) {
@ -94,7 +94,7 @@ namespace SharpChat.Config {
return new ScopedConfig(this, prefix); return new ScopedConfig(this, prefix);
} }
public CachedValue<T> ReadCached<T>(string name, T fallback = default, TimeSpan? lifetime = null) { public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback); return new CachedValue<T>(this, name, lifetime ?? CACHE_LIFETIME, fallback);
} }

View file

@ -13,11 +13,11 @@ namespace SharpChat.EventStorage
int senderRank, int senderRank,
string senderNick, string senderNick,
ChatUserPermissions senderPerms, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
void RemoveEvent(StoredEventInfo evt); void RemoveEvent(StoredEventInfo evt);
StoredEventInfo GetEvent(long seqId); StoredEventInfo? GetEvent(long seqId);
IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0); IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0);
} }
} }

View file

@ -4,16 +4,21 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage {
{
public partial class MariaDBEventStorage(string connString) : IEventStorage { public partial class MariaDBEventStorage(string connString) : IEventStorage {
private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString)); private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
public void AddEvent( public void AddEvent(
long id, string type, long id,
string type,
string channelName, string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms, long senderId,
object data = null, string senderName,
ChatColour senderColour,
int senderRank,
string senderNick,
ChatUserPermissions senderPerms,
object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(type);
@ -37,30 +42,9 @@ namespace SharpChat.EventStorage
); );
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public StoredEventInfo? GetEvent(long seqId) {
ArgumentNullException.ThrowIfNull(type);
long id = SharpId.Next();
AddEvent(
id, type,
channel?.Name,
user?.UserId ?? 0,
user?.UserName ?? string.Empty,
user?.Colour ?? ChatColour.None,
user?.Rank ?? 0,
user?.NickName,
user?.Permissions ?? 0,
data,
flags
);
return id;
}
public StoredEventInfo GetEvent(long seqId) {
try { try {
using MySqlDataReader reader = RunQuery( using MySqlDataReader? reader = RunQuery(
"SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+ ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
+ ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
@ -70,6 +54,9 @@ namespace SharpChat.EventStorage
new MySqlParameter("id", seqId) new MySqlParameter("id", seqId)
); );
if(reader is null)
return null;
while(reader.Read()) { while(reader.Read()) {
StoredEventInfo evt = ReadEvent(reader); StoredEventInfo evt = ReadEvent(reader);
if(evt != null) if(evt != null)
@ -92,7 +79,7 @@ namespace SharpChat.EventStorage
ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")), ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")),
reader.GetInt32("event_sender_rank"), reader.GetInt32("event_sender_rank"),
(ChatUserPermissions)reader.GetInt32("event_sender_perms"), (ChatUserPermissions)reader.GetInt32("event_sender_perms"),
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : reader.GetString("event_sender_nick") reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")
), ),
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")), DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")), reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
@ -106,7 +93,7 @@ namespace SharpChat.EventStorage
List<StoredEventInfo> events = []; List<StoredEventInfo> events = [];
try { try {
using MySqlDataReader reader = RunQuery( using MySqlDataReader? reader = RunQuery(
"SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
+ ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
+ ", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
@ -120,6 +107,8 @@ namespace SharpChat.EventStorage
new MySqlParameter("amount", amount), new MySqlParameter("amount", amount),
new MySqlParameter("offset", offset) new MySqlParameter("offset", offset)
); );
if(reader is null)
return events;
while(reader.Read()) { while(reader.Read()) {
StoredEventInfo evt = ReadEvent(reader); StoredEventInfo evt = ReadEvent(reader);

View file

@ -12,7 +12,7 @@ namespace SharpChat.EventStorage {
); );
} }
public static string BuildConnString(string host, string username, string password, string database) { public static string BuildConnString(string? host, string? username, string? password, string? database) {
return new MySqlConnectionStringBuilder { return new MySqlConnectionStringBuilder {
Server = host, Server = host,
UserID = username, UserID = username,
@ -49,7 +49,7 @@ namespace SharpChat.EventStorage {
return 0; return 0;
} }
private MySqlDataReader RunQuery(string command, params MySqlParameter[] parameters) { private MySqlDataReader? RunQuery(string command, params MySqlParameter[] parameters) {
try { try {
MySqlConnection conn = GetConnection(); MySqlConnection conn = GetConnection();
MySqlCommand cmd = conn.CreateCommand(); MySqlCommand cmd = conn.CreateCommand();
@ -64,7 +64,8 @@ namespace SharpChat.EventStorage {
return null; return null;
} }
private object RunQueryValue(string command, params MySqlParameter[] parameters) { private T RunQueryValue<T>(string command, params MySqlParameter[] parameters)
where T : struct {
try { try {
using MySqlConnection conn = GetConnection(); using MySqlConnection conn = GetConnection();
using MySqlCommand cmd = conn.CreateCommand(); using MySqlCommand cmd = conn.CreateCommand();
@ -72,12 +73,15 @@ namespace SharpChat.EventStorage {
cmd.Parameters.AddRange(parameters); cmd.Parameters.AddRange(parameters);
cmd.CommandText = command; cmd.CommandText = command;
cmd.Prepare(); cmd.Prepare();
return cmd.ExecuteScalar();
object? raw = cmd.ExecuteScalar();
if(raw is T value)
return value;
} catch(MySqlException ex) { } catch(MySqlException ex) {
Logger.Write(ex); Logger.Write(ex);
} }
return null; return default;
} }
} }
} }

View file

@ -4,7 +4,7 @@ using System;
namespace SharpChat.EventStorage { namespace SharpChat.EventStorage {
public partial class MariaDBEventStorage { public partial class MariaDBEventStorage {
private void DoMigration(string name, Action action) { private void DoMigration(string name, Action action) {
bool done = (long)RunQueryValue( bool done = RunQueryValue<long>(
"SELECT COUNT(*) FROM `sqc_migrations` WHERE `migration_name` = @name", "SELECT COUNT(*) FROM `sqc_migrations` WHERE `migration_name` = @name",
new MySqlParameter("name", name) new MySqlParameter("name", name)
) > 0; ) > 0;

View file

@ -5,19 +5,19 @@ namespace SharpChat.EventStorage {
public class StoredEventInfo( public class StoredEventInfo(
long id, long id,
string type, string type,
ChatUser sender, ChatUser? sender,
DateTimeOffset created, DateTimeOffset created,
DateTimeOffset? deleted, DateTimeOffset? deleted,
string channelName, string? channelName,
JsonDocument data, JsonDocument data,
StoredEventFlags flags StoredEventFlags flags
) { ) {
public long Id { get; set; } = id; public long Id { get; set; } = id;
public string Type { get; set; } = type; public string Type { get; set; } = type;
public ChatUser Sender { get; set; } = sender; public ChatUser? Sender { get; set; } = sender;
public DateTimeOffset Created { get; set; } = created; public DateTimeOffset Created { get; set; } = created;
public DateTimeOffset? Deleted { get; set; } = deleted; public DateTimeOffset? Deleted { get; set; } = deleted;
public string ChannelName { get; set; } = channelName; public string? ChannelName { get; set; } = channelName;
public StoredEventFlags Flags { get; set; } = flags; public StoredEventFlags Flags { get; set; } = flags;
public JsonDocument Data { get; set; } = data; public JsonDocument Data { get; set; } = data;
} }

View file

@ -8,10 +8,16 @@ namespace SharpChat.EventStorage {
private readonly Dictionary<long, StoredEventInfo> Events = []; private readonly Dictionary<long, StoredEventInfo> Events = [];
public void AddEvent( public void AddEvent(
long id, string type, long id,
string type,
string channelName, string channelName,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms, long senderId,
object data = null, string senderName,
ChatColour senderColour,
int senderRank,
string senderNick,
ChatUserPermissions senderPerms,
object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(type);
@ -28,8 +34,8 @@ namespace SharpChat.EventStorage {
), DateTimeOffset.Now, null, channelName, hack, flags)); ), DateTimeOffset.Now, null, channelName, hack, flags));
} }
public StoredEventInfo GetEvent(long seqId) { public StoredEventInfo? GetEvent(long seqId) {
return Events.TryGetValue(seqId, out StoredEventInfo evt) ? evt : null; return Events.TryGetValue(seqId, out StoredEventInfo? evt) ? evt : null;
} }
public void RemoveEvent(StoredEventInfo evt) { public void RemoveEvent(StoredEventInfo evt) {

View file

@ -25,7 +25,6 @@ namespace SharpChat.Events {
public string SenderNickName { get; } = senderNickName; public string SenderNickName { get; } = senderNickName;
public ChatUserPermissions SenderPerms { get; } = senderPerms; public ChatUserPermissions SenderPerms { get; } = senderPerms;
public DateTimeOffset MessageCreated { get; } = msgCreated; public DateTimeOffset MessageCreated { get; } = msgCreated;
public string MessageChannel { get; }
public string MessageText { get; } = msgText; public string MessageText { get; } = msgText;
public bool IsPrivate { get; } = isPrivate; public bool IsPrivate { get; } = isPrivate;
public bool IsAction { get; } = isAction; public bool IsAction { get; } = isAction;

View file

@ -13,7 +13,7 @@ namespace SharpChat.Misuzu {
public long UserId { get; set; } public long UserId { get; set; }
[JsonPropertyName("username")] [JsonPropertyName("username")]
public string UserName { get; set; } public string? UserName { get; set; }
[JsonPropertyName("colour_raw")] [JsonPropertyName("colour_raw")]
public int ColourRaw { get; set; } public int ColourRaw { get; set; }

View file

@ -7,10 +7,10 @@ namespace SharpChat.Misuzu {
public bool IsBanned { get; set; } public bool IsBanned { get; set; }
[JsonPropertyName("user_id")] [JsonPropertyName("user_id")]
public string UserId { get; set; } public string? UserId { get; set; }
[JsonPropertyName("ip_addr")] [JsonPropertyName("ip_addr")]
public string RemoteAddress { get; set; } public string? RemoteAddress { get; set; }
[JsonPropertyName("is_perma")] [JsonPropertyName("is_perma")]
public bool IsPermanent { get; set; } public bool IsPermanent { get; set; }
@ -20,7 +20,7 @@ namespace SharpChat.Misuzu {
// only populated in list request // only populated in list request
[JsonPropertyName("user_name")] [JsonPropertyName("user_name")]
public string UserName { get; set; } public string? UserName { get; set; }
[JsonPropertyName("user_colour")] [JsonPropertyName("user_colour")]
public int UserColourRaw { get; set; } public int UserColourRaw { get; set; }

View file

@ -46,11 +46,11 @@ namespace SharpChat.Misuzu {
} }
public string CreateBufferSignature(byte[] bytes) { public string CreateBufferSignature(byte[] bytes) {
using HMACSHA256 algo = new(Encoding.UTF8.GetBytes(SecretKey)); using HMACSHA256 algo = new(Encoding.UTF8.GetBytes(SecretKey!));
return string.Concat(algo.ComputeHash(bytes).Select(c => c.ToString("x2"))); return string.Concat(algo.ComputeHash(bytes).Select(c => c.ToString("x2")));
} }
public async Task<MisuzuAuthInfo> AuthVerifyAsync(string method, string token, string ipAddr) { public async Task<MisuzuAuthInfo?> AuthVerifyAsync(string method, string token, string ipAddr) {
method ??= string.Empty; method ??= string.Empty;
token ??= string.Empty; token ??= string.Empty;
ipAddr ??= string.Empty; ipAddr ??= string.Empty;
@ -112,7 +112,7 @@ namespace SharpChat.Misuzu {
} }
} }
public async Task<MisuzuBanInfo> CheckBanAsync(string userId = null, string ipAddr = null, bool userIdIsName = false) { public async Task<MisuzuBanInfo?> CheckBanAsync(string? userId = null, string? ipAddr = null, bool userIdIsName = false) {
userId ??= string.Empty; userId ??= string.Empty;
ipAddr ??= string.Empty; ipAddr ??= string.Empty;
@ -134,7 +134,7 @@ namespace SharpChat.Misuzu {
); );
} }
public async Task<MisuzuBanInfo[]> GetBanListAsync() { public async Task<MisuzuBanInfo[]?> GetBanListAsync() {
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString(); string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
string url = string.Format(BANS_LIST_URL, BaseURL, Uri.EscapeDataString(now)); string url = string.Format(BANS_LIST_URL, BaseURL, Uri.EscapeDataString(now));
string sig = string.Format(BANS_LIST_SIG, now); string sig = string.Format(BANS_LIST_SIG, now);
@ -169,8 +169,8 @@ namespace SharpChat.Misuzu {
string target = kind switch { string target = kind switch {
BanRevokeKind.UserId => banInfo.UserId, BanRevokeKind.UserId => banInfo.UserId,
BanRevokeKind.RemoteAddress => banInfo.RemoteAddress, BanRevokeKind.RemoteAddress => banInfo.RemoteAddress,
_ => string.Empty, _ => null,
}; } ?? string.Empty;
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString(); string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
string url = string.Format(BANS_REVOKE_URL, BaseURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now)); string url = string.Format(BANS_REVOKE_URL, BaseURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));

View file

@ -12,9 +12,9 @@ namespace SharpChat.Packet {
public class AuthFailPacket : ServerPacket { public class AuthFailPacket : ServerPacket {
public AuthFailReason Reason { get; private set; } public AuthFailReason Reason { get; private set; }
public MisuzuBanInfo BanInfo { get; private set; } public MisuzuBanInfo? BanInfo { get; private set; }
public AuthFailPacket(AuthFailReason reason, MisuzuBanInfo fbi = null) { public AuthFailPacket(AuthFailReason reason, MisuzuBanInfo? fbi = null) {
Reason = reason; Reason = reason;
if(reason == AuthFailReason.Banned) if(reason == AuthFailReason.Banned)
@ -43,10 +43,10 @@ namespace SharpChat.Packet {
if(Reason == AuthFailReason.Banned) { if(Reason == AuthFailReason.Banned) {
sb.Append('\t'); sb.Append('\t');
if(BanInfo.IsPermanent) if(BanInfo?.IsPermanent == true)
sb.Append("-1"); sb.Append("-1");
else else
sb.Append(BanInfo.ExpiresAt.ToUnixTimeSeconds()); sb.Append(BanInfo?.ExpiresAt.ToUnixTimeSeconds() ?? 0);
} }
yield return sb.ToString(); yield return sb.ToString();

View file

@ -17,7 +17,7 @@ namespace SharpChat.Packet {
sb.Append("\t-1\t0\fbanlist\f"); sb.Append("\t-1\t0\fbanlist\f");
foreach(MisuzuBanInfo ban in Bans) { foreach(MisuzuBanInfo ban in Bans) {
string banStr = string.IsNullOrEmpty(ban.UserName) ? ban.RemoteAddress : ban.UserName; string banStr = string.IsNullOrEmpty(ban.UserName) ? (ban.RemoteAddress ?? "::") : (ban.UserName ?? $"({ban.UserId})");
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr); sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr);
} }

View file

@ -27,7 +27,7 @@ namespace SharpChat.Packet
switch(Event.Type) { switch(Event.Type) {
case "msg:add": case "msg:add":
case "SharpChat.Events.ChatMessage": case "SharpChat.Events.ChatMessage":
if(isBroadcast) { if(isBroadcast || Event.Sender is null) {
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fsay\f"); sb.Append("0\fsay\f");
} else { } else {
@ -39,11 +39,11 @@ namespace SharpChat.Packet
sb.Append("<i>"); sb.Append("<i>");
sb.Append( sb.Append(
Event.Data.RootElement.GetProperty("text").GetString() (Event.Data.RootElement.GetProperty("text").GetString()?
.Replace("<", "&lt;") .Replace("<", "&lt;")
.Replace(">", "&gt;") .Replace(">", "&gt;")
.Replace("\n", " <br/> ") .Replace("\n", " <br/> ")
.Replace("\t", " ") .Replace("\t", " ")) ?? string.Empty
); );
if(isAction) if(isAction)
@ -54,21 +54,21 @@ namespace SharpChat.Packet
case "SharpChat.Events.UserConnectEvent": case "SharpChat.Events.UserConnectEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjoin\f"); sb.Append("0\fjoin\f");
sb.Append(Event.Sender.LegacyName); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "chan:join": case "chan:join":
case "SharpChat.Events.UserChannelJoinEvent": case "SharpChat.Events.UserChannelJoinEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\fjchan\f"); sb.Append("0\fjchan\f");
sb.Append(Event.Sender.LegacyName); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "chan:leave": case "chan:leave":
case "SharpChat.Events.UserChannelLeaveEvent": case "SharpChat.Events.UserChannelLeaveEvent":
sb.Append(V1_CHATBOT); sb.Append(V1_CHATBOT);
sb.Append("0\flchan\f"); sb.Append("0\flchan\f");
sb.Append(Event.Sender.LegacyName); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
case "user:disconnect": case "user:disconnect":
@ -93,7 +93,7 @@ namespace SharpChat.Packet
} }
sb.Append('\f'); sb.Append('\f');
sb.Append(Event.Sender.LegacyName); sb.Append(Event.Sender?.LegacyName ?? "?????");
break; break;
} }

View file

@ -26,14 +26,14 @@ namespace SharpChat.PacketHandlers {
public void Handle(ChatPacketHandlerContext ctx) { public void Handle(ChatPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3); string[] args = ctx.SplitText(3);
string authMethod = args.ElementAtOrDefault(1); string? authMethod = args.ElementAtOrDefault(1);
if(string.IsNullOrWhiteSpace(authMethod)) { if(string.IsNullOrWhiteSpace(authMethod)) {
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
string authToken = args.ElementAtOrDefault(2); string? authToken = args.ElementAtOrDefault(2);
if(string.IsNullOrWhiteSpace(authToken)) { if(string.IsNullOrWhiteSpace(authToken)) {
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
@ -47,7 +47,7 @@ namespace SharpChat.PacketHandlers {
} }
Task.Run(async () => { Task.Run(async () => {
MisuzuAuthInfo fai; MisuzuAuthInfo? fai;
string ipAddr = ctx.Connection.RemoteAddress.ToString(); string ipAddr = ctx.Connection.RemoteAddress.ToString();
try { try {
@ -63,14 +63,14 @@ namespace SharpChat.PacketHandlers {
#endif #endif
} }
if(!fai.Success) { if(fai?.Success != true) {
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}"); Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai?.Reason ?? "unknown"}");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
return; return;
} }
MisuzuBanInfo fbi; MisuzuBanInfo? fbi;
try { try {
fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr); fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr);
} catch(Exception ex) { } catch(Exception ex) {
@ -84,7 +84,7 @@ namespace SharpChat.PacketHandlers {
#endif #endif
} }
if(fbi.IsBanned && !fbi.HasExpired) { if(fbi?.IsBanned == true && !fbi.HasExpired) {
Logger.Write($"<{ctx.Connection.Id}> User is banned."); Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Banned, fbi)); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Banned, fbi));
ctx.Connection.Dispose(); ctx.Connection.Dispose();
@ -93,12 +93,12 @@ namespace SharpChat.PacketHandlers {
await ctx.Chat.ContextAccess.WaitAsync(); await ctx.Chat.ContextAccess.WaitAsync();
try { try {
ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); ChatUser? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
if(user == null) if(user == null)
user = new ChatUser( user = new ChatUser(
fai.UserId, fai.UserId,
fai.UserName, fai.UserName ?? $"({fai.UserId})",
fai.Colour, fai.Colour,
fai.Rank, fai.Rank,
fai.Permissions fai.Permissions
@ -106,7 +106,7 @@ namespace SharpChat.PacketHandlers {
else else
ctx.Chat.UpdateUser( ctx.Chat.UpdateUser(
user, user,
userName: fai.UserName, userName: fai.UserName ?? $"({fai.UserId})",
colour: fai.Colour, colour: fai.Colour,
rank: fai.Rank, rank: fai.Rank,
perms: fai.Permissions perms: fai.Permissions
@ -125,7 +125,7 @@ namespace SharpChat.PacketHandlers {
if(File.Exists("welcome.txt")) { if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x)); IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
string line = lines.ElementAtOrDefault(RNG.Next(lines.Count())); string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
if(!string.IsNullOrWhiteSpace(line)) if(!string.IsNullOrWhiteSpace(line))
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line)); ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));

View file

@ -27,10 +27,8 @@ namespace SharpChat.PacketHandlers {
public void Handle(ChatPacketHandlerContext ctx) { public void Handle(ChatPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3); string[] args = ctx.SplitText(3);
ChatUser user = ctx.Connection.User; ChatUser? user = ctx.Connection.User;
string? messageText = args.ElementAtOrDefault(2);
// No longer concats everything after index 1 with \t, no previous implementation did that either
string messageText = args.ElementAtOrDefault(2);
if(user == null || !user.Can(ChatUserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText)) if(user == null || !user.Can(ChatUserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText))
return; return;
@ -41,8 +39,8 @@ namespace SharpChat.PacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel channel) if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel? channel)
&& !ctx.Chat.IsInChannel(user, channel)) && (channel is null || !ctx.Chat.IsInChannel(user, channel)))
return; return;
if(user.Status != ChatUserStatus.Online) if(user.Status != ChatUserStatus.Online)
@ -62,19 +60,11 @@ namespace SharpChat.PacketHandlers {
if(messageText.StartsWith('/')) { if(messageText.StartsWith('/')) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel); ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
IChatCommand command = null;
foreach(IChatCommand cmd in Commands) foreach(IChatCommand cmd in Commands)
if(cmd.IsMatch(context)) { if(cmd.IsMatch(context)) {
command = cmd; cmd.Dispatch(context);
break; return;
} }
if(command != null) {
command.Dispatch(context);
return;
}
} }
ctx.Chat.DispatchEvent(new MessageCreateEvent( ctx.Chat.DispatchEvent(new MessageCreateEvent(

View file

@ -11,7 +11,7 @@ namespace SharpChat {
public class Program { public class Program {
public const string CONFIG = "sharpchat.cfg"; public const string CONFIG = "sharpchat.cfg";
public static void Main(string[] args) { public static void Main() {
Console.WriteLine(@" _____ __ ________ __ "); Console.WriteLine(@" _____ __ ________ __ ");
Console.WriteLine(@" / ___// /_ ____ __________ / ____/ /_ ____ _/ /_"); Console.WriteLine(@" / ___// /_ ____ __________ / ____/ /_ ____ _/ /_");
Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/"); Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/");
@ -29,7 +29,7 @@ namespace SharpChat {
using ManualResetEvent mre = new(false); using ManualResetEvent mre = new(false);
bool hasCancelled = false; bool hasCancelled = false;
void cancelKeyPressHandler(object sender, ConsoleCancelEventArgs ev) { void cancelKeyPressHandler(object? sender, ConsoleCancelEventArgs ev) {
Console.CancelKeyPress -= cancelKeyPressHandler; Console.CancelKeyPress -= cancelKeyPressHandler;
hasCancelled = true; hasCancelled = true;
ev.Cancel = true; ev.Cancel = true;

View file

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View file

@ -1,4 +1,6 @@
using Fleck; #nullable disable
using Fleck;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;

View file

@ -19,9 +19,9 @@ namespace SharpChat {
#endif #endif
try { try {
using Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"SharpChat.version.txt"); using Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"SharpChat.version.txt")!;
using StreamReader sr = new(s); using StreamReader sr = new(s);
VersionString = sr.ReadLine().Trim(); VersionString = sr.ReadLine()!.Trim();
VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString; VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString;
} catch { } catch {
VersionStringShort = VersionString = UNKNOWN; VersionStringShort = VersionString = UNKNOWN;

View file

@ -53,24 +53,27 @@ namespace SharpChat {
Context = new ChatContext(evtStore); Context = new ChatContext(evtStore);
string[] channelNames = config.ReadValue("channels", DEFAULT_CHANNELS); string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
if(channelNames is not null)
foreach(string channelName in channelNames) {
IConfig channelCfg = config.ScopeTo($"channels:{channelName}");
foreach(string channelName in channelNames) { string name = channelCfg.SafeReadValue("name", string.Empty)!;
IConfig channelCfg = config.ScopeTo($"channels:{channelName}"); if(string.IsNullOrWhiteSpace(name))
name = channelName;
string name = channelCfg.SafeReadValue("name", string.Empty); ChatChannel channelInfo = new(
if(string.IsNullOrWhiteSpace(name)) name,
name = channelName; channelCfg.SafeReadValue("password", string.Empty)!,
rank: channelCfg.SafeReadValue("minRank", 0)
);
ChatChannel channelInfo = new( Context.Channels.Add(channelInfo);
name, DefaultChannel ??= channelInfo;
channelCfg.SafeReadValue("password", string.Empty), }
rank: channelCfg.SafeReadValue("minRank", 0)
);
Context.Channels.Add(channelInfo); if(DefaultChannel is null)
DefaultChannel ??= channelInfo; throw new Exception("The default channel could not be determined.");
}
GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections)); GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections));
@ -156,13 +159,13 @@ namespace SharpChat {
// this doesn't affect non-authed connections????? // this doesn't affect non-authed connections?????
if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) { if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) {
ChatUser banUser = null; ChatUser? banUser = null;
string banAddr = string.Empty; string banAddr = string.Empty;
TimeSpan banDuration = TimeSpan.MinValue; TimeSpan banDuration = TimeSpan.MinValue;
Context.ContextAccess.Wait(); Context.ContextAccess.Wait();
try { try {
if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter rateLimiter)) if(!Context.UserRateLimiters.TryGetValue(conn.User.UserId, out RateLimiter? rateLimiter))
Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter( Context.UserRateLimiters.Add(conn.User.UserId, rateLimiter = new RateLimiter(
ChatUser.DEFAULT_SIZE, ChatUser.DEFAULT_SIZE,
ChatUser.DEFAULT_MINIMUM_DELAY, ChatUser.DEFAULT_MINIMUM_DELAY,
@ -202,7 +205,7 @@ namespace SharpChat {
} }
ChatPacketHandlerContext context = new(msg, Context, conn); ChatPacketHandlerContext context = new(msg, Context, conn);
IChatPacketHandler handler = conn.User is null IChatPacketHandler? handler = conn.User is null
? GuestHandlers.FirstOrDefault(h => h.IsMatch(context)) ? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
: AuthedHandlers.FirstOrDefault(h => h.IsMatch(context)); : AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));