Name adjustments and moved some things to the common lib.

This commit is contained in:
flash 2025-04-26 12:51:08 +00:00
commit 0cc5d46ea9
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
50 changed files with 323 additions and 323 deletions

View file

@ -0,0 +1,34 @@
using System.Globalization;
using System.Text;
namespace SharpChat.ClientCommands {
public class AFKClientCommand : ClientCommand {
private const string DEFAULT = "AFK";
public const int MAX_GRAPHEMES = 5;
public const int MAX_BYTES = MAX_GRAPHEMES * 10;
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("afk");
}
public void Dispatch(ClientCommandContext ctx) {
string? statusText = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(statusText))
statusText = DEFAULT;
else {
statusText = statusText.Trim();
StringInfo sti = new(statusText);
if(Encoding.UTF8.GetByteCount(statusText) > MAX_BYTES
|| sti.LengthInTextElements > MAX_GRAPHEMES)
statusText = sti.SubstringByTextElements(0, Math.Min(sti.LengthInTextElements, MAX_GRAPHEMES)).Trim();
}
ctx.Chat.UpdateUser(
ctx.User,
status: UserStatus.Away,
statusText: statusText
);
}
}
}

View file

@ -0,0 +1,33 @@
using SharpChat.Events;
namespace SharpChat.ClientCommands {
public class ActionClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("action")
|| ctx.NameEquals("me");
}
public void Dispatch(ClientCommandContext ctx) {
if(ctx.Args.Length < 1)
return;
string actionStr = string.Join(' ', ctx.Args);
if(string.IsNullOrWhiteSpace(actionStr))
return;
ctx.Chat.DispatchEvent(new MessageCreateEvent(
ctx.Chat.RandomSnowflake.Next(),
ctx.Channel.Name,
ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now,
actionStr,
false, true, false
));
}
}
}

View file

@ -0,0 +1,37 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class BanListClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("bans")
|| ctx.NameEquals("banned");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
Task.Run(async () => {
MisuzuBanInfo[]? mbis = await Misuzu.GetBanListAsync();
if(mbis is null)
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
else
ctx.Chat.SendTo(ctx.User, new BanListS2CPacket(
msgId,
mbis.Where(mbi => mbi.IsBanned && !mbi.HasExpired)
.Select(mbi => new BanListS2CPacket.Entry(
BanListS2CPacket.Type.UserName, // Misuzu currently only does username bans so we can just do this
mbi.UserName ?? $"({mbi.UserId})"
))
));
}).Wait();
}
}
}

View file

@ -0,0 +1,34 @@
using SharpChat.Events;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class BroadcastClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("say")
|| ctx.NameEquals("broadcast");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.Broadcast)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
ctx.Chat.DispatchEvent(new MessageCreateEvent(
msgId,
string.Empty,
ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now,
string.Join(' ', ctx.Args),
false, false, true
));
}
}
}

View file

@ -0,0 +1,62 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class CreateChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("create");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.CreateChannel)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string firstArg = ctx.Args.First();
bool createChanHasHierarchy;
if(ctx.Args.Length < 1 || (createChanHasHierarchy = firstArg.All(char.IsDigit) && ctx.Args.Length < 2)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
int createChanHierarchy = 0;
if(createChanHasHierarchy)
if(!int.TryParse(firstArg, out createChanHierarchy))
createChanHierarchy = 0;
if(createChanHierarchy > ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
return;
}
string createChanName = string.Join('_', ctx.Args.Skip(createChanHasHierarchy ? 1 : 0));
if(!Channel.CheckName(createChanName)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NAME_INVALID));
return;
}
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_ALREADY_EXISTS, true, createChanName));
return;
}
Channel createChan = new(
createChanName,
isTemporary: !ctx.User.Can(UserPermissions.SetChannelPermanent),
rank: createChanHierarchy,
ownerId: ctx.User.UserId
);
ctx.Chat.Channels.Add(createChan);
foreach(User ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank))
ctx.Chat.SendTo(ccu, new ChannelCreateS2CPacket(createChan.Name, createChan.HasPassword, createChan.IsTemporary));
ctx.Chat.SwitchChannel(ctx.User, createChan, createChan.Password);
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_CREATED, false, createChan.Name));
}
}
}

View file

@ -0,0 +1,37 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class DeleteChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delchan") || (
ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == false
);
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 1 || string.IsNullOrWhiteSpace(ctx.Args.FirstOrDefault())) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
string delChanName = string.Join('_', ctx.Args);
Channel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, delChanName));
return;
}
if(!ctx.User.Can(UserPermissions.DeleteChannel) && delChan.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETE_FAILED, true, delChan.Name));
return;
}
ctx.Chat.RemoveChannel(delChan);
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_DELETED, false, delChan.Name));
}
}
}

View file

@ -0,0 +1,41 @@
using SharpChat.EventStorage;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands
{
public class DeleteMessageClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("delmsg") || (
ctx.NameEquals("delete")
&& ctx.Args.FirstOrDefault()?.All(char.IsDigit) == true
);
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
bool deleteAnyMessage = ctx.User.Can(UserPermissions.DeleteAnyMessage);
if(!deleteAnyMessage && !ctx.User.Can(UserPermissions.DeleteOwnMessage)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string? firstArg = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(firstArg) || !firstArg.All(char.IsDigit) || !long.TryParse(firstArg, out long delSeqId)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
StoredEventInfo? delMsg = ctx.Chat.Events.GetEvent(delSeqId);
if(delMsg?.Sender is null || delMsg.Sender.Rank > ctx.User.Rank || (!deleteAnyMessage && delMsg.Sender.UserId != ctx.User.UserId)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.MESSAGE_DELETE_ERROR));
return;
}
ctx.Chat.Events.RemoveEvent(delMsg);
ctx.Chat.Send(new ChatMessageDeleteS2CPacket(delMsg.Id));
}
}
}

View file

@ -0,0 +1,23 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class JoinChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("join");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
string joinChanStr = ctx.Args.FirstOrDefault() ?? "Channel";
Channel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, joinChanStr));
ctx.Chat.ForceChannel(ctx.User);
return;
}
ctx.Chat.SwitchChannel(ctx.User, joinChan, string.Join(' ', ctx.Args.Skip(1)));
}
}
}

View file

@ -0,0 +1,81 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class KickBanClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("kick")
|| ctx.NameEquals("ban");
}
public void Dispatch(ClientCommandContext ctx) {
bool isBanning = ctx.NameEquals("ban");
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(isBanning ? UserPermissions.BanUser : UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string? banUserTarget = ctx.Args.ElementAtOrDefault(0);
string? banDurationStr = ctx.Args.ElementAtOrDefault(1);
int banReasonIndex = 1;
User? banUser = null;
if(banUserTarget == null || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, banUserTarget ?? "User"));
return;
}
if(banUser.Rank >= ctx.User.Rank && banUser != ctx.User) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
return;
}
TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
if(durationSeconds < 0) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
duration = TimeSpan.FromSeconds(durationSeconds);
++banReasonIndex;
}
if(duration <= TimeSpan.Zero) {
ctx.Chat.BanUser(banUser, duration);
return;
}
string banReason = string.Join(' ', ctx.Args.Skip(banReasonIndex));
Task.Run(async () => {
string userId = banUser.UserId.ToString();
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
MisuzuBanInfo? fbi = await Misuzu.CheckBanAsync(userId, userIp);
if(fbi is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
return;
}
if(fbi.IsBanned && !fbi.HasExpired) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.KICK_NOT_ALLOWED, true, banUser.LegacyName));
return;
}
await Misuzu.CreateBanAsync(
userId, userIp,
ctx.User.UserId.ToString(), ctx.Connection.RemoteAddress.ToString(),
duration, banReason
);
ctx.Chat.BanUser(banUser, duration);
}).Wait();
}
}
}

View file

@ -0,0 +1,63 @@
using SharpChat.S2CPackets;
using System.Globalization;
using System.Text;
namespace SharpChat.ClientCommands {
public class NickClientCommand : ClientCommand {
private const int MAX_GRAPHEMES = 16;
private const int MAX_BYTES = MAX_GRAPHEMES * 10;
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("nick");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
bool setOthersNick = ctx.User.Can(UserPermissions.SetOthersNickname);
if(!setOthersNick && !ctx.User.Can(UserPermissions.SetOwnNickname)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
User? targetUser = null;
int offset = 0;
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId);
++offset;
}
targetUser ??= ctx.User;
if(ctx.Args.Length < offset) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
string nickStr = string.Join('_', ctx.Args.Skip(offset))
.Replace("\n", string.Empty).Replace("\r", string.Empty)
.Replace("\f", string.Empty).Replace("\t", string.Empty)
.Replace(' ', '_').Trim();
if(nickStr == targetUser.UserName)
nickStr = string.Empty;
else if(string.IsNullOrEmpty(nickStr))
nickStr = string.Empty;
else {
StringInfo nsi = new(nickStr);
if(Encoding.UTF8.GetByteCount(nickStr) > MAX_BYTES
|| nsi.LengthInTextElements > MAX_GRAPHEMES)
nickStr = nsi.SubstringByTextElements(0, Math.Min(nsi.LengthInTextElements, MAX_GRAPHEMES)).Trim();
}
if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.NAME_IN_USE, true, nickStr));
return;
}
string? previousName = targetUser.UserId == ctx.User.UserId ? (targetUser.NickName ?? targetUser.UserName) : null;
ctx.Chat.UpdateUser(targetUser, nickName: nickStr, silent: previousName == null);
}
}
}

View file

@ -0,0 +1,50 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
using System.Net;
namespace SharpChat.ClientCommands {
public class PardonAddressClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardonip")
|| ctx.NameEquals("unbanip");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string? unbanAddrTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress? unbanAddr)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
unbanAddrTarget = unbanAddr.ToString();
Task.Run(async () => {
MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(ipAddr: unbanAddrTarget);
if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
return;
}
if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
return;
}
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.RemoteAddress);
if(wasBanned)
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanAddrTarget));
else
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanAddrTarget));
}).Wait();
}
}
}

View file

@ -0,0 +1,57 @@
using SharpChat.Misuzu;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class PardonUserClientCommand(MisuzuClient msz) : ClientCommand {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pardon")
|| ctx.NameEquals("unban");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.BanUser | UserPermissions.KickUser)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
bool unbanUserTargetIsName = true;
string? unbanUserTarget = ctx.Args.FirstOrDefault();
if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
User? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
unbanUserTargetIsName = false;
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId);
}
if(unbanUser != null)
unbanUserTarget = unbanUser.UserId.ToString();
Task.Run(async () => {
MisuzuBanInfo? banInfo = await Misuzu.CheckBanAsync(unbanUserTarget, userIdIsName: unbanUserTargetIsName);
if(banInfo is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.GENERIC_ERROR, true));
return;
}
if(!banInfo.IsBanned || banInfo.HasExpired) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserTarget));
return;
}
bool wasBanned = await Misuzu.RevokeBanAsync(banInfo, MisuzuClient.BanRevokeKind.UserId);
if(wasBanned)
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_UNBANNED, false, unbanUserTarget));
else
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_BANNED, true, unbanUserTarget));
}).Wait();
}
}
}

View file

@ -0,0 +1,27 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class PasswordChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("pwd")
|| ctx.NameEquals("password");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.SetChannelPassword) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
string chanPass = string.Join(' ', ctx.Args).Trim();
if(string.IsNullOrWhiteSpace(chanPass))
chanPass = string.Empty;
ctx.Chat.UpdateChannel(ctx.Channel, password: chanPass);
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_PASSWORD_CHANGED, false));
}
}
}

View file

@ -0,0 +1,28 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class RankChannelClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("rank")
|| ctx.NameEquals("privilege")
|| ctx.NameEquals("priv");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.SetChannelHierarchy) || ctx.Channel.IsOwner(ctx.User)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
if(ctx.Args.Length < 1 || !int.TryParse(ctx.Args.First(), out int chanHierarchy) || chanHierarchy > ctx.User.Rank) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.INSUFFICIENT_HIERARCHY));
return;
}
ctx.Chat.UpdateChannel(ctx.Channel, hierarchy: chanHierarchy);
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_HIERARCHY_CHANGED, false));
}
}
}

View file

@ -0,0 +1,31 @@
using SharpChat.S2CPackets;
using System.Net;
namespace SharpChat.ClientCommands {
public class RemoteAddressClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("ip")
|| ctx.NameEquals("whois");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(!ctx.User.Can(UserPermissions.SeeIPAddress)) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, "/ip"));
return;
}
string? ipUserStr = ctx.Args.FirstOrDefault();
User? ipUser = null;
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, ipUserStr ?? "User"));
return;
}
foreach(IPAddress ip in ctx.Chat.GetRemoteAddresses(ipUser))
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.IP_ADDRESS, false, ipUser.UserName, ip));
}
}
}

View file

@ -0,0 +1,31 @@
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class ShutdownRestartClientCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) : ClientCommand {
private readonly ManualResetEvent WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle));
private readonly Func<bool> ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck));
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("shutdown")
|| ctx.NameEquals("restart");
}
public void Dispatch(ClientCommandContext ctx) {
if(ctx.User.UserId != 1) {
long msgId = ctx.Chat.RandomSnowflake.Next();
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_NOT_ALLOWED, true, $"/{ctx.Name}"));
return;
}
if(!ShutdownCheck())
return;
if(ctx.NameEquals("restart"))
foreach(Connection conn in ctx.Chat.Connections)
conn.PrepareForRestart();
ctx.Chat.Update();
WaitHandle?.Set();
}
}
}

View file

@ -0,0 +1,45 @@
using SharpChat.Events;
using SharpChat.S2CPackets;
namespace SharpChat.ClientCommands {
public class WhisperClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("whisper")
|| ctx.NameEquals("msg");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
if(ctx.Args.Length < 2) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.COMMAND_FORMAT_ERROR));
return;
}
string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
User? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USER_NOT_FOUND, true, whisperUserStr));
return;
}
if(whisperUser == ctx.User)
return;
ctx.Chat.DispatchEvent(new MessageCreateEvent(
msgId,
User.GetDMChannelName(ctx.User, whisperUser),
ctx.User.UserId,
ctx.User.UserName,
ctx.User.Colour,
ctx.User.Rank,
ctx.User.NickName,
ctx.User.Permissions,
DateTimeOffset.Now,
string.Join(' ', ctx.Args.Skip(1)),
true, false, false
));
}
}
}

View file

@ -0,0 +1,62 @@
using SharpChat.S2CPackets;
using System.Text;
namespace SharpChat.ClientCommands {
public class WhoClientCommand : ClientCommand {
public bool IsMatch(ClientCommandContext ctx) {
return ctx.NameEquals("who");
}
public void Dispatch(ClientCommandContext ctx) {
long msgId = ctx.Chat.RandomSnowflake.Next();
StringBuilder whoChanSB = new();
string? whoChanStr = ctx.Args.FirstOrDefault();
if(string.IsNullOrEmpty(whoChanStr)) {
foreach(User whoUser in ctx.Chat.Users) {
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
if(whoUser == ctx.User)
whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>');
whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, ");
}
if(whoChanSB.Length > 2)
whoChanSB.Length -= 2;
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_SERVER, false, whoChanSB));
} else {
Channel? whoChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(whoChanStr));
if(whoChan is null) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.CHANNEL_NOT_FOUND, true, whoChanStr));
return;
}
if(whoChan.Rank > ctx.User.Rank || (whoChan.HasPassword && !ctx.User.Can(UserPermissions.JoinAnyChannel))) {
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_ERROR, true, whoChanStr));
return;
}
foreach(User whoUser in ctx.Chat.GetChannelUsers(whoChan)) {
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
if(whoUser == ctx.User)
whoChanSB.Append(@" style=""font-weight: bold;""");
whoChanSB.Append('>');
whoChanSB.Append(whoUser.LegacyName);
whoChanSB.Append("</a>, ");
}
if(whoChanSB.Length > 2)
whoChanSB.Length -= 2;
ctx.Chat.SendTo(ctx.User, new CommandResponseS2CPacket(msgId, LCR.USERS_LISTING_CHANNEL, false, whoChan.Name, whoChanSB));
}
}
}
}