Enabled explicit nullable.

This commit is contained in:
flash 2024-05-10 19:18:55 +00:00
parent b95cd06cb1
commit a6569815af
40 changed files with 254 additions and 227 deletions

View file

@ -15,14 +15,14 @@ namespace SharpChat {
public ChatChannel( public ChatChannel(
ChatUser owner, ChatUser owner,
string name, string name,
string password = null, string? password = null,
bool isTemporary = false, bool isTemporary = false,
int rank = 0 int rank = 0
) : this(name, password, isTemporary, rank, owner?.UserId ?? 0) {} ) : this(name, password, isTemporary, rank, owner?.UserId ?? 0) {}
public ChatChannel( public ChatChannel(
string name, string name,
string password = null, string? password = null,
bool isTemporary = false, bool isTemporary = false,
int rank = 0, int rank = 0,
long ownerId = 0 long ownerId = 0
@ -34,7 +34,7 @@ namespace SharpChat {
OwnerId = ownerId; OwnerId = ownerId;
} }
public bool NameEquals(string name) { public bool NameEquals(string? name) {
return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase); return string.Equals(name, Name, StringComparison.InvariantCultureIgnoreCase);
} }

View file

@ -1,6 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace SharpChat {
namespace SharpChat {
public readonly struct ChatColour { public readonly struct ChatColour {
public readonly byte Red; public readonly byte Red;
public readonly byte Green; public readonly byte Green;
@ -23,7 +21,7 @@ namespace SharpChat {
Inherits = false; Inherits = false;
} }
public override bool Equals([NotNullWhen(true)] object obj) { public override bool Equals(object? obj) {
return obj is ChatColour colour && Equals(colour); return obj is ChatColour colour && Equals(colour);
} }

View file

@ -17,7 +17,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;
@ -32,12 +32,12 @@ namespace SharpChat {
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.ContainsKey("X-Real-IP") && sock.ConnectionInfo.Headers.ContainsKey("X-Real-IP")
&& IPAddress.TryParse(sock.ConnectionInfo.Headers["X-Real-IP"], out IPAddress realAddr)) && IPAddress.TryParse(sock.ConnectionInfo.Headers["X-Real-IP"], out IPAddress? realAddr))
addr = realAddr; addr = realAddr;
RemoteAddress = addr; RemoteAddress = addr;

View file

@ -34,7 +34,7 @@ namespace SharpChat {
// e.g. nook sees @Arysil and Arysil sees @nook // e.g. nook sees @Arysil and Arysil sees @nook
// this entire routine is garbage, channels should probably in the db // this entire routine is garbage, channels should probably in the db
if(!mce.ChannelName.StartsWith("@")) if(mce.ChannelName?.StartsWith("@") != true)
return; return;
IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1); IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1);
@ -42,7 +42,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;
@ -56,15 +56,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 != 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(
@ -105,8 +106,10 @@ namespace SharpChat {
} }
} }
public bool IsInChannel(ChatUser user, ChatChannel channel) { public bool IsInChannel(ChatUser? user, ChatChannel? channel) {
return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name)); return user != null
&& channel != null
&& ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
} }
public string[] GetUserChannelNames(ChatUser user) { public string[] GetUserChannelNames(ChatUser user) {
@ -129,11 +132,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,
@ -143,7 +146,7 @@ namespace SharpChat {
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));
bool hasChanged = false; bool hasChanged = false;
string previousName = null; string? previousName = null;
if(userName != null && !user.UserName.Equals(userName)) { if(userName != null && !user.UserName.Equals(userName)) {
user.UserName = userName; user.UserName = userName;
@ -275,7 +278,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;
} }
@ -365,7 +368,7 @@ namespace SharpChat {
throw new ArgumentNullException(nameof(packet)); throw new ArgumentNullException(nameof(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.IsAuthed && 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);
} }
@ -374,7 +377,7 @@ namespace SharpChat {
return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray(); return Connections.Where(c => c.IsAlive && c.User == user).Select(c => c.RemoteAddress).Distinct().ToArray();
} }
public void ForceChannel(ChatUser user, ChatChannel chan = null) { public void ForceChannel(ChatUser user, ChatChannel? chan = null) {
if(user == null) if(user == null)
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));
@ -384,7 +387,12 @@ namespace SharpChat {
SendTo(user, new UserChannelForceJoinPacket(chan.Name)); SendTo(user, new UserChannelForceJoinPacket(chan.Name));
} }
public void UpdateChannel(ChatChannel channel, bool? temporary = null, int? minRank = null, string password = null) { public void UpdateChannel(
ChatChannel channel,
bool? temporary = null,
int? minRank = null,
string? password = null
) {
if(channel == null) if(channel == null)
throw new ArgumentNullException(nameof(channel)); throw new ArgumentNullException(nameof(channel));
if(!Channels.Contains(channel)) if(!Channels.Contains(channel))
@ -411,7 +419,7 @@ namespace SharpChat {
if(channel == null || !Channels.Any()) if(channel == null || !Channels.Any())
return; return;
ChatChannel defaultChannel = Channels.FirstOrDefault(); ChatChannel? defaultChannel = Channels.FirstOrDefault();
if(defaultChannel == null) if(defaultChannel == null)
return; return;

View file

@ -34,13 +34,13 @@ namespace SharpChat {
public ChatUser( public ChatUser(
long userId, long userId,
string userName, string? userName,
ChatColour colour, ChatColour colour,
int rank, int rank,
ChatUserPermissions perms, ChatUserPermissions perms,
string nickName = null, string? nickName = null,
ChatUserStatus status = ChatUserStatus.Online, ChatUserStatus status = ChatUserStatus.Online,
string statusText = null, string? statusText = null,
bool isSuper = false bool isSuper = false
) { ) {
UserId = userId; UserId = userId;
@ -59,7 +59,7 @@ namespace SharpChat {
return strict ? perms == perm : perms > 0; return strict ? perms == perm : perms > 0;
} }
public bool NameEquals(string name) { public bool NameEquals(string? name) {
return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase) return string.Equals(name, UserName, StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase) || string.Equals(name, NickName, StringComparison.InvariantCultureIgnoreCase)
|| string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase) || string.Equals(name, LegacyName, StringComparison.InvariantCultureIgnoreCase)
@ -70,11 +70,11 @@ namespace SharpChat {
return UserId.GetHashCode(); return UserId.GetHashCode();
} }
public override bool Equals(object obj) { public override bool Equals(object? obj) {
return Equals(obj as ChatUser); return Equals(obj as ChatUser);
} }
public bool Equals(ChatUser other) { public bool Equals(ChatUser? other) {
return UserId == other?.UserId; return UserId == other?.UserId;
} }

View file

@ -11,7 +11,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

@ -25,8 +25,8 @@ namespace SharpChat.Commands {
Task.Run(async () => { Task.Run(async () => {
ctx.Chat.SendTo(ctx.User, new BanListPacket( ctx.Chat.SendTo(ctx.User, new BanListPacket(
(await Misuzu.GetBanListAsync()).Select( (await Misuzu.GetBanListAsync() ?? Array.Empty<MisuzuBanInfo>()).Select(
ban => string.IsNullOrEmpty(ban.UserName) ? ban.RemoteAddress : ban.UserName ban => string.IsNullOrEmpty(ban.UserName) ? (string.IsNullOrEmpty(ban.RemoteAddress) ? string.Empty : ban.RemoteAddress) : ban.UserName
).ToArray() ).ToArray()
)); ));
}).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 == 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,11 +8,11 @@ namespace SharpChat.Commands {
} }
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
string joinChanStr = ctx.Args.FirstOrDefault(); string? joinChanStr = ctx.Args.FirstOrDefault();
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 == 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 ?? string.Empty));
ctx.Chat.ForceChannel(ctx.User); ctx.Chat.ForceChannel(ctx.User);
return; return;
} }

View file

@ -25,13 +25,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 ?? string.Empty));
return; return;
} }
@ -63,9 +63,9 @@ 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.IsBanned && !fbi.HasExpired) { if(fbi != null && 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));
return; return;
} }

View file

@ -14,7 +14,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) {
@ -46,7 +47,7 @@ namespace SharpChat.Commands {
return; return;
} }
string previousName = targetUser == ctx.User ? (targetUser.NickName ?? targetUser.UserName) : null; string? previousName = targetUser == ctx.User ? (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

@ -24,8 +24,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;
} }
@ -33,9 +33,9 @@ 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.IsBanned || banInfo.HasExpired) { if(banInfo?.IsBanned != true || 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));
return; return;
} }

View file

@ -24,13 +24,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);
@ -40,9 +40,9 @@ 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.IsBanned || banInfo.HasExpired) { if(banInfo?.IsBanned != true || 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));
return; return;
} }

View file

@ -15,11 +15,11 @@ namespace SharpChat.Commands {
return; return;
} }
string ipUserStr = ctx.Args.FirstOrDefault(); string? ipUserStr = ctx.Args.FirstOrDefault();
ChatUser ipUser; ChatUser? ipUser;
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 ?? string.Empty));
return; return;
} }

View file

@ -16,11 +16,11 @@ namespace SharpChat.Commands {
return; return;
} }
string whisperUserStr = ctx.Args.FirstOrDefault(); string? whisperUserStr = ctx.Args.FirstOrDefault();
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 ?? string.Empty));
return; return;
} }

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) {
@ -19,9 +19,7 @@ namespace SharpChat.Commands {
if(whoUser == ctx.User) if(whoUser == ctx.User)
whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>'); whoChanSB.AppendFormat(@">{0}</a>, ", whoUser.LegacyName);
whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, ");
} }
if(whoChanSB.Length > 2) if(whoChanSB.Length > 2)
@ -29,7 +27,7 @@ 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 == 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));
@ -47,9 +45,7 @@ namespace SharpChat.Commands {
if(whoUser == ctx.User) if(whoUser == ctx.User)
whoChanSB.Append(@" style=""font-weight: bold;"""); whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>'); whoChanSB.AppendFormat(@">{0}</a>, ", whoUser.LegacyName);
whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, ");
} }
if(whoChanSB.Length > 2) if(whoChanSB.Length > 2)

View file

@ -5,13 +5,13 @@ namespace SharpChat.Config {
private IConfig Config { get; } private IConfig Config { get; }
private string Name { get; } private string Name { get; }
private TimeSpan Lifetime { get; } private TimeSpan Lifetime { get; }
private T Fallback { get; } private T? Fallback { get; }
private object ConfigAccess { get; } = new(); private object ConfigAccess { get; } = new();
private object CurrentValue { get; set; } private object? CurrentValue { get; set; }
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;
@ -21,13 +21,13 @@ 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 CachedValue(IConfig config, string name, TimeSpan lifetime, T fallback) { public CachedValue(IConfig config, string name, TimeSpan lifetime, T? fallback) {
Config = config ?? throw new ArgumentNullException(nameof(config)); Config = config ?? throw new ArgumentNullException(nameof(config));
Name = name ?? throw new ArgumentNullException(nameof(name)); Name = name ?? throw new ArgumentNullException(nameof(name));
Lifetime = lifetime; Lifetime = lifetime;
@ -41,7 +41,7 @@ namespace SharpChat.Config {
} }
public override string ToString() { public override string ToString() {
return Value.ToString(); return Value?.ToString() ?? string.Empty;
} }
} }
} }

View file

@ -14,22 +14,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

@ -18,15 +18,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);
} }
@ -34,7 +34,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

@ -26,14 +26,14 @@ namespace SharpChat.Config {
Lock = new Mutex(); Lock = new Mutex();
} }
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;
@ -55,8 +55,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;
@ -76,7 +76,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) {
@ -88,7 +88,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

@ -5,32 +5,32 @@ namespace SharpChat.EventStorage
public interface IEventStorage { public interface IEventStorage {
void AddEvent( void AddEvent(
long id, string type, long id, string type,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
void AddEvent( void AddEvent(
long id, string type, long id, string type,
string channelName, string? channelName,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
void AddEvent( void AddEvent(
long id, string type, long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms, long senderId, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
void AddEvent( 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, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
); );
long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None); long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, 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

@ -17,7 +17,7 @@ namespace SharpChat.EventStorage
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags); AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
@ -25,8 +25,8 @@ namespace SharpChat.EventStorage
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
string channelName, string? channelName,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags); AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
@ -34,8 +34,8 @@ namespace SharpChat.EventStorage
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms, long senderId, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags); AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
@ -43,9 +43,9 @@ namespace SharpChat.EventStorage
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, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null) if(type == null)
@ -70,7 +70,7 @@ namespace SharpChat.EventStorage
); );
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null) if(type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -92,9 +92,9 @@ namespace SharpChat.EventStorage
return id; return id;
} }
public StoredEventInfo GetEvent(long seqId) { 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`"
@ -104,11 +104,12 @@ namespace SharpChat.EventStorage
new MySqlParameter("id", seqId) new MySqlParameter("id", seqId)
); );
while(reader.Read()) { if(reader != null)
StoredEventInfo evt = ReadEvent(reader); while(reader.Read()) {
if(evt != null) StoredEventInfo evt = ReadEvent(reader);
return evt; if(evt != null)
} return evt;
}
} catch(MySqlException ex) { } catch(MySqlException ex) {
Logger.Write(ex); Logger.Write(ex);
} }
@ -140,7 +141,7 @@ namespace SharpChat.EventStorage
List<StoredEventInfo> events = new(); List<StoredEventInfo> events = new();
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`"
@ -155,11 +156,12 @@ namespace SharpChat.EventStorage
new MySqlParameter("offset", offset) new MySqlParameter("offset", offset)
); );
while(reader.Read()) { if(reader != null)
StoredEventInfo evt = ReadEvent(reader); while(reader.Read()) {
if(evt != null) StoredEventInfo evt = ReadEvent(reader);
events.Add(evt); if(evt != null)
} events.Add(evt);
}
} catch(MySqlException ex) { } catch(MySqlException ex) {
Logger.Write(ex); Logger.Write(ex);
} }

View file

@ -5,10 +5,10 @@ namespace SharpChat.EventStorage {
public partial class MariaDBEventStorage { public partial class MariaDBEventStorage {
public static string BuildConnString(IConfig config) { public static string BuildConnString(IConfig config) {
return BuildConnString( return BuildConnString(
config.ReadValue("host", "localhost"), config.ReadValue("host", "localhost") ?? string.Empty,
config.ReadValue("user", string.Empty), config.ReadValue("user") ?? string.Empty,
config.ReadValue("pass", string.Empty), config.ReadValue("pass") ?? string.Empty,
config.ReadValue("db", "sharpchat") config.ReadValue("db", "sharpchat") ?? string.Empty
); );
} }
@ -48,7 +48,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();
@ -63,7 +63,7 @@ namespace SharpChat.EventStorage {
return null; return null;
} }
private object RunQueryValue(string command, params MySqlParameter[] parameters) { private object? RunQueryValue(string command, params MySqlParameter[] parameters) {
try { try {
using MySqlConnection conn = GetConnection(); using MySqlConnection conn = GetConnection();
using MySqlCommand cmd = conn.CreateCommand(); using MySqlCommand cmd = conn.CreateCommand();

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 = (long?)RunQueryValue(
"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,20 +5,20 @@ namespace SharpChat.EventStorage {
public class StoredEventInfo { public class StoredEventInfo {
public long Id { get; set; } public long Id { get; set; }
public string Type { get; set; } public string Type { get; set; }
public ChatUser Sender { get; set; } public ChatUser? Sender { get; set; }
public DateTimeOffset Created { get; set; } public DateTimeOffset Created { get; set; }
public DateTimeOffset? Deleted { get; set; } public DateTimeOffset? Deleted { get; set; }
public string ChannelName { get; set; } public string? ChannelName { get; set; }
public StoredEventFlags Flags { get; set; } public StoredEventFlags Flags { get; set; }
public JsonDocument Data { get; set; } public JsonDocument Data { get; set; }
public StoredEventInfo( public 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
) { ) {

View file

@ -9,7 +9,7 @@ namespace SharpChat.EventStorage {
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags); AddEvent(id, type, null, 0, null, ChatColour.None, 0, null, 0, data, flags);
@ -17,8 +17,8 @@ namespace SharpChat.EventStorage {
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
string channelName, string? channelName,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags); AddEvent(id, type, channelName, 0, null, ChatColour.None, 0, null, 0, data, flags);
@ -26,8 +26,8 @@ namespace SharpChat.EventStorage {
public void AddEvent( public void AddEvent(
long id, string type, long id, string type,
long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms, long senderId, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags); AddEvent(id, type, null, senderId, senderName, senderColour, senderRank, senderNick, senderPerms, data, flags);
@ -35,9 +35,9 @@ namespace SharpChat.EventStorage {
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, string? senderName, ChatColour senderColour, int senderRank, string? senderNick, ChatUserPermissions senderPerms,
object data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null) if(type == null)
@ -55,7 +55,7 @@ namespace SharpChat.EventStorage {
), DateTimeOffset.Now, null, channelName, hack, flags)); ), DateTimeOffset.Now, null, channelName, hack, flags));
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null) if(type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -77,13 +77,11 @@ namespace SharpChat.EventStorage {
return id; return id;
} }
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) {
if(evt == null)
throw new ArgumentNullException(nameof(evt));
Events.Remove(evt.Id); Events.Remove(evt.Id);
} }

View file

@ -3,15 +3,14 @@
namespace SharpChat.Events { namespace SharpChat.Events {
public class MessageCreateEvent : IChatEvent { public class MessageCreateEvent : IChatEvent {
public long MessageId { get; } public long MessageId { get; }
public string ChannelName { get; } public string? ChannelName { get; }
public long SenderId { get; } public long SenderId { get; }
public string SenderName { get; } public string? SenderName { get; }
public ChatColour SenderColour { get; } public ChatColour SenderColour { get; }
public int SenderRank { get; } public int SenderRank { get; }
public string SenderNickName { get; } public string? SenderNickName { get; }
public ChatUserPermissions SenderPerms { get; } public ChatUserPermissions SenderPerms { get; }
public DateTimeOffset MessageCreated { get; } public DateTimeOffset MessageCreated { get; }
public string MessageChannel { get; }
public string MessageText { get; } public string MessageText { get; }
public bool IsPrivate { get; } public bool IsPrivate { get; }
public bool IsAction { get; } public bool IsAction { get; }
@ -19,12 +18,12 @@ namespace SharpChat.Events {
public MessageCreateEvent( public MessageCreateEvent(
long msgId, long msgId,
string channelName, string? channelName,
long senderId, long senderId,
string senderName, string? senderName,
ChatColour senderColour, ChatColour senderColour,
int senderRank, int senderRank,
string senderNickName, string? senderNickName,
ChatUserPermissions senderPerms, ChatUserPermissions senderPerms,
DateTimeOffset msgCreated, DateTimeOffset msgCreated,
string msgText, string msgText,
@ -49,8 +48,8 @@ namespace SharpChat.Events {
public MessageCreateEvent( public MessageCreateEvent(
long msgId, long msgId,
string channelName, string? channelName,
ChatUser sender, ChatUser? sender,
DateTimeOffset msgCreated, DateTimeOffset msgCreated,
string msgText, string msgText,
bool isPrivate, bool isPrivate,

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

@ -47,11 +47,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.Value ?? string.Empty));
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;
@ -114,7 +114,11 @@ 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;
@ -136,7 +140,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);
@ -170,8 +174,8 @@ namespace SharpChat.Misuzu {
}; };
string target = kind switch { string target = kind switch {
BanRevokeKind.UserId => banInfo.UserId, BanRevokeKind.UserId => banInfo?.UserId ?? string.Empty,
BanRevokeKind.RemoteAddress => banInfo.RemoteAddress, BanRevokeKind.RemoteAddress => banInfo?.RemoteAddress ?? string.Empty,
_ => string.Empty, _ => string.Empty,
}; };

View file

@ -5,6 +5,7 @@ namespace SharpChat.Packet {
AuthInvalid, AuthInvalid,
MaxSessions, MaxSessions,
Banned, Banned,
Null,
} }
public class AuthFailPacket : ServerPacket { public class AuthFailPacket : ServerPacket {

View file

@ -32,15 +32,15 @@ namespace SharpChat.Packet {
} else { } else {
sb.AppendFormat( sb.AppendFormat(
"{0}\t{1}\t{2}\t{3} {4} {5} {6} {7}", "{0}\t{1}\t{2}\t{3} {4} {5} {6} {7}",
Event.Sender.UserId, Event.Sender?.UserId,
Event.Sender.LegacyNameWithStatus, Event.Sender?.LegacyNameWithStatus,
Event.Sender.Colour, Event.Sender?.Colour,
Event.Sender.Rank, Event.Sender?.Rank,
Event.Sender.Can(ChatUserPermissions.KickUser) ? 1 : 0, Event.Sender?.Can(ChatUserPermissions.KickUser) == true ? 1 : 0,
Event.Sender.Can(ChatUserPermissions.ViewLogs) ? 1 : 0, Event.Sender?.Can(ChatUserPermissions.ViewLogs) == true ? 1 : 0,
Event.Sender.Can(ChatUserPermissions.SetOwnNickname) ? 1 : 0, Event.Sender?.Can(ChatUserPermissions.SetOwnNickname) == true ? 1 : 0,
Event.Sender.Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) ? 2 : ( Event.Sender?.Can(ChatUserPermissions.CreateChannel | ChatUserPermissions.SetChannelPermanent, true) == true ? 2 : (
Event.Sender.Can(ChatUserPermissions.CreateChannel) ? 1 : 0 Event.Sender?.Can(ChatUserPermissions.CreateChannel) == true ? 1 : 0
) )
); );
} }
@ -50,7 +50,7 @@ namespace SharpChat.Packet {
if(isBroadcast) if(isBroadcast)
sb.Append("0\fsay\f"); sb.Append("0\fsay\f");
string body = Event.Data.RootElement.GetProperty("text").GetString().Replace("<", "&lt;").Replace(">", "&gt;").Replace("\n", " <br/> ").Replace("\t", " "); string body = Event.Data.RootElement.GetProperty("text").GetString()?.Replace("<", "&lt;").Replace(">", "&gt;").Replace("\n", " <br/> ").Replace("\t", " ") ?? string.Empty;
if(isAction) if(isAction)
body = string.Format("<i>{0}</i>", body); body = string.Format("<i>{0}</i>", body);
@ -59,17 +59,17 @@ namespace SharpChat.Packet {
case "user:connect": case "user:connect":
case "SharpChat.Events.UserConnectEvent": case "SharpChat.Events.UserConnectEvent":
sb.AppendFormat("{0}\t0\fjoin\f{1}", V1_CHATBOT, Event.Sender.LegacyName); sb.AppendFormat("{0}\t0\fjoin\f{1}", V1_CHATBOT, Event.Sender?.LegacyName);
break; break;
case "chan:join": case "chan:join":
case "SharpChat.Events.UserChannelJoinEvent": case "SharpChat.Events.UserChannelJoinEvent":
sb.AppendFormat("{0}\t0\fjchan\f{1}", V1_CHATBOT, Event.Sender.LegacyName); sb.AppendFormat("{0}\t0\fjchan\f{1}", V1_CHATBOT, Event.Sender?.LegacyName);
break; break;
case "chan:leave": case "chan:leave":
case "SharpChat.Events.UserChannelLeaveEvent": case "SharpChat.Events.UserChannelLeaveEvent":
sb.AppendFormat("{0}\t0\flchan\f{1}", V1_CHATBOT, Event.Sender.LegacyName); sb.AppendFormat("{0}\t0\flchan\f{1}", V1_CHATBOT, Event.Sender?.LegacyName);
break; break;
case "user:disconnect": case "user:disconnect":
@ -84,7 +84,7 @@ namespace SharpChat.Packet {
UserDisconnectReason.Leave => "leave", UserDisconnectReason.Leave => "leave",
_ => "leave", _ => "leave",
}, },
Event.Sender.LegacyName Event.Sender?.LegacyName
); );
break; break;
} }

View file

@ -33,14 +33,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();
@ -54,7 +54,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 {
@ -70,6 +70,13 @@ namespace SharpChat.PacketHandlers {
#endif #endif
} }
if(fai == null) {
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: <null>");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Null));
ctx.Connection.Dispose();
return;
}
if(!fai.Success) { if(!fai.Success) {
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}"); Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
@ -77,7 +84,7 @@ namespace SharpChat.PacketHandlers {
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) {
@ -91,6 +98,13 @@ namespace SharpChat.PacketHandlers {
#endif #endif
} }
if(fbi == null) {
Logger.Debug($"<{ctx.Connection.Id}> Ban check fail: <null>");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Null));
ctx.Connection.Dispose();
return;
}
if(fbi.IsBanned && !fbi.HasExpired) { if(fbi.IsBanned && !fbi.HasExpired) {
Logger.Write($"<{ctx.Connection.Id}> User is banned."); Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailPacket(fbi.ExpiresAt)); ctx.Connection.Send(new AuthFailPacket(fbi.ExpiresAt));
@ -100,7 +114,7 @@ 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(
@ -134,7 +148,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

@ -33,10 +33,10 @@ 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;
// No longer concats everything after index 1 with \t, no previous implementation did that either // No longer concats everything after index 1 with \t, no previous implementation did that either
string messageText = args.ElementAtOrDefault(2); 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;
@ -47,8 +47,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)) || !ctx.Chat.IsInChannel(user, channel))
return; return;
if(user.Status != ChatUserStatus.Online) if(user.Status != ChatUserStatus.Online)
@ -67,7 +67,7 @@ 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; IChatCommand? command = null;
foreach(IChatCommand cmd in Commands) foreach(IChatCommand cmd in Commands)
if(cmd.IsMatch(context)) { if(cmd.IsMatch(context)) {

View file

@ -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>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

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

@ -1,6 +1,5 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text;
namespace SharpChat { namespace SharpChat {
public static class SharpInfo { public static class SharpInfo {
@ -19,19 +18,18 @@ 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); if(s != null) {
VersionString = sr.ReadLine().Trim(); using StreamReader sr = new(s);
VersionString = sr.ReadLine()?.Trim() ?? string.Empty;
} else
VersionString = string.Empty;
VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString; VersionStringShort = VersionString.Length > 10 ? VersionString[..10] : VersionString;
} catch { } catch {
VersionStringShort = VersionString = UNKNOWN; VersionStringShort = VersionString = UNKNOWN;
} }
StringBuilder sb = new(); ProgramName = string.Format(@"{0}/{1}", NAME, VersionStringShort);
sb.Append(NAME);
sb.Append('/');
sb.Append(VersionStringShort);
ProgramName = sb.ToString();
} }
} }
} }

View file

@ -51,24 +51,29 @@ namespace SharpChat {
Context = new ChatContext(evtStore); Context = new ChatContext(evtStore);
string[] channelNames = config.ReadValue("channels", new[] { "lounge" }); string[]? channelNames = config.ReadValue("channels", new[] { "lounge" });
foreach(string channelName in channelNames) { if(channelNames != null)
IConfig channelCfg = config.ScopeTo($"channels:{channelName}"); foreach(string channelName in channelNames) {
IConfig channelCfg = config.ScopeTo($"channels:{channelName}");
string name = channelCfg.SafeReadValue("name", string.Empty); string? name = channelCfg.SafeReadValue("name", string.Empty);
if(string.IsNullOrWhiteSpace(name)) if(string.IsNullOrWhiteSpace(name))
name = channelName; name = channelName;
ChatChannel channelInfo = new( ChatChannel channelInfo = new(
name, name,
channelCfg.SafeReadValue("password", string.Empty), channelCfg.SafeReadValue("password", string.Empty),
rank: channelCfg.SafeReadValue("minRank", 0) rank: channelCfg.SafeReadValue("minRank", 0)
); );
Context.Channels.Add(channelInfo); Context.Channels.Add(channelInfo);
DefaultChannel ??= channelInfo; DefaultChannel ??= channelInfo;
} }
DefaultChannel ??= new ChatChannel("Default");
if(!Context.Channels.Any())
Context.Channels.Add(DefaultChannel);
GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections)); GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections));
@ -154,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,
@ -200,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));