Implement safety features related to custom command LimitedContext #patch

This commit is contained in:
Daniel_I_Am 2020-09-27 15:45:00 +02:00
commit 83464d280d
No known key found for this signature in database
GPG Key ID: 80C428FCC9743E84
4 changed files with 394 additions and 6 deletions

View File

@ -3,8 +3,8 @@ using Discord.Commands;
namespace ChaosBot.Services.ProgrammingLanguageInterpreter namespace ChaosBot.Services.ProgrammingLanguageInterpreter
{ {
public interface IProgrammingLanguageInterpreter internal interface IProgrammingLanguageInterpreter
{ {
string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command); string Interpret(CancellationToken ct, LimitedSocketCommandContext context, string content, string command);
} }
} }

View File

@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
// Since these need to be accessed, dynamically, from Lua, rider does not know how to handle it.
// For the inconsistent naming, I am copying over from discord.net 1-to-1. Blame them.
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
namespace ChaosBot.Services.ProgrammingLanguageInterpreter
{
internal class LimitedSocketCommandContext
{
public LimitedISocketMessageChannel Channel { get; }
public LimitedDiscordSocketClient Client { get; }
public LimitedSocketGuild Guild { get; }
public bool IsPrivate { get; }
public LimitedSocketUserMessage Message { get; }
public LimitedSocketUser User { get; }
public LimitedSocketCommandContext(SocketCommandContext context)
{
Channel = new LimitedISocketMessageChannel(context.Channel);
Client = new LimitedDiscordSocketClient(context.Client);
Guild = new LimitedSocketGuild(context.Guild);
IsPrivate = context.IsPrivate;
Message = new LimitedSocketUserMessage(context.Message);
User = new LimitedSocketUser(context.User);
}
}
internal class LimitedISocketMessageChannel {
public string Name { get; }
public ulong Id { get; }
public DateTimeOffset CreatedAt { get; }
public LimitedISocketMessageChannel(ISocketMessageChannel channel)
{
Name = channel.Name;
Id = channel.Id;
CreatedAt = channel.CreatedAt;
}
}
internal class LimitedDiscordSocketClient
{
public IActivity Activity { get; }
public int Latency { get; }
public int ShardId { get; }
public UserStatus Status { get; }
public LimitedDiscordSocketClient(DiscordSocketClient client)
{
Activity = client.Activity;
Latency = client.Latency;
ShardId = client.ShardId;
Status = client.Status;
}
}
internal class LimitedSocketGuild
{
public ulong Id { get; }
public LimitedSocketVoiceChannel AFKChannel { get; }
public int AFKTimeout { get; }
public ulong? ApplicationId { get; }
public IReadOnlyCollection<LimitedSocketCategoryChannel> CategoryChannels { get; }
public IReadOnlyCollection<LimitedSocketGuildChannel> Channels { get; }
public DateTimeOffset CreatedAt { get; }
public LimitedSocketTextChannel DefaultChannel { get; }
public int DownloadedMemberCount { get; }
public IReadOnlyCollection<LimitedGuildEmote> Emotes { get; }
public LimitedSocketRole EveryoneRole { get; }
public ExplicitContentFilterLevel ExplicitContentFilter { get; }
public bool HasAllMembers { get; }
public string IconId { get; }
public string IconUrl { get; }
public bool IsConnected { get; }
public bool IsEmbeddable { get; }
public bool IsSynced { get; }
public int MemberCount { get; }
public MfaLevel MfaLevel { get; }
public string Name { get; }
public LimitedSocketGuildUser Owner { get; }
public ulong OwnerId { get; }
public IReadOnlyCollection<LimitedSocketRole> Roles { get; }
public string SplashId { get; }
public string SplashUrl { get; }
public LimitedSocketTextChannel SystemChannel { get; }
public IReadOnlyCollection<LimitedSocketTextChannel> TextChannels { get; }
public IReadOnlyCollection<LimitedSocketGuildUser> Users { get; }
public VerificationLevel VerificationLevel { get; }
public IReadOnlyCollection<LimitedSocketVoiceChannel> VoiceChannels { get; }
public string VoiceRegionId { get; }
public LimitedSocketGuild(SocketGuild guild)
{
Id = guild.Id;
AFKChannel = guild.AFKChannel == null ? null : new LimitedSocketVoiceChannel(guild.AFKChannel);
AFKTimeout = guild.AFKTimeout;
ApplicationId = guild.ApplicationId;
CategoryChannels = guild.CategoryChannels.Select(cc => new LimitedSocketCategoryChannel(cc)).ToList();
Channels = guild.Channels.Select(c => new LimitedSocketGuildChannel(c)).ToList();
CreatedAt = guild.CreatedAt;
DefaultChannel = new LimitedSocketTextChannel(guild.DefaultChannel);
DownloadedMemberCount = guild.DownloadedMemberCount;
Emotes = guild.Emotes.Select(e => new LimitedGuildEmote(e)).ToList();
EveryoneRole = new LimitedSocketRole(guild.EveryoneRole);
ExplicitContentFilter = guild.ExplicitContentFilter;
HasAllMembers = guild.HasAllMembers;
IconId = guild.IconId;
IconUrl = guild.IconUrl;
IsConnected = guild.IsConnected;
IsEmbeddable = guild.IsEmbeddable;
IsSynced = guild.IsSynced;
MemberCount = guild.MemberCount;
MfaLevel = guild.MfaLevel;
Name = guild.Name;
Owner = new LimitedSocketGuildUser(guild.Owner);
OwnerId = guild.OwnerId;
Roles = guild.Roles.Select(r => new LimitedSocketRole(r)).ToList();
SplashId = guild.SplashId;
SplashUrl = guild.SplashUrl;
SystemChannel = guild.SystemChannel == null ? null : new LimitedSocketTextChannel(guild.SystemChannel);
TextChannels = guild.TextChannels.Select(tc => new LimitedSocketTextChannel(tc)).ToList();
Users = guild.Users.Select(u => new LimitedSocketGuildUser(u)).ToList();
VerificationLevel = guild.VerificationLevel;
VoiceChannels = guild.VoiceChannels.Select(vc => new LimitedSocketVoiceChannel(vc)).ToList();
VoiceRegionId = guild.VoiceRegionId;
}
}
internal class LimitedSocketUserMessage
{
public LimitedSocketUser Author { get; }
public MessageSource Source { get; }
public string Content { get; }
public DateTimeOffset CreatedAt { get; }
public DateTimeOffset Timestamp { get; }
public DateTimeOffset? EditedTimestamp { get; }
public ulong Id { get; }
public bool IsPinned { get; }
public bool IsTTS { get; }
public IReadOnlyCollection<LimitedSocketGuildChannel> MentionedChannels { get; }
public IReadOnlyCollection<LimitedSocketRole> MentionedRoles { get; }
public IReadOnlyCollection<LimitedSocketUser> MentionedUsers { get; }
public LimitedSocketUserMessage(SocketUserMessage message)
{
Author = new LimitedSocketUser(message.Author);
Source = message.Source;
Content = message.Content;
CreatedAt = message.CreatedAt;
Timestamp = message.Timestamp;
EditedTimestamp = message.EditedTimestamp;
Id = message.Id;
IsPinned = message.IsPinned;
IsTTS = message.IsTTS;
MentionedChannels = message.MentionedChannels.Select(mc => new LimitedSocketGuildChannel(mc)).ToList();
MentionedRoles = message.MentionedRoles.Select(mr => new LimitedSocketRole(mr)).ToList();
MentionedUsers = message.MentionedUsers.Select(mu => new LimitedSocketUser(mu)).ToList();
}
}
internal class LimitedSocketUser
{
private readonly SocketUser originalUser;
public ulong Id { get; }
public IActivity Activity { get; }
public string AvatarId { get; }
public DateTimeOffset CreatedAt { get; }
public string Discriminator { get; }
public ushort DiscriminatorValue { get; }
public bool IsBot { get; }
public bool IsWebhook { get; }
public string Mention { get; }
public UserStatus Status { get; }
public string Username { get; }
public string GetAvatarUrl => originalUser.GetAvatarUrl();
public LimitedSocketUser(SocketUser user)
{
originalUser = user;
Id = user.Id;
Activity = user.Activity;
AvatarId = user.AvatarId;
CreatedAt = user.CreatedAt;
Discriminator = user.Discriminator;
DiscriminatorValue = user.DiscriminatorValue;
IsBot = user.IsBot;
IsWebhook = user.IsWebhook;
Mention = user.Mention;
Status = user.Status;
Username = user.Username;
}
}
internal class LimitedSocketGuildChannel
{
public string Name { get; }
public int Position { get; }
public IReadOnlyCollection<Overwrite> PermissionOverwrites { get; }
public DateTimeOffset CreatedAt { get; }
public ulong Id { get; }
public IReadOnlyCollection<LimitedSocketGuildUser> Users { get; }
public LimitedSocketGuildChannel(SocketGuildChannel channel)
{
Name = channel.Name;
Position = channel.Position;
PermissionOverwrites = channel.PermissionOverwrites;
CreatedAt = channel.CreatedAt;
Id = channel.Id;
Users = channel.Users.Select(u => new LimitedSocketGuildUser(u)).ToList();
}
}
internal class LimitedSocketCategoryChannel: LimitedSocketGuildChannel
{
public IReadOnlyCollection<LimitedSocketGuildChannel> Channels { get; }
public LimitedSocketCategoryChannel(SocketCategoryChannel category) : base(category)
{
Channels = category.Channels.Select(c => new LimitedSocketGuildChannel(c)).ToList();
}
}
internal class LimitedSocketVoiceChannel : LimitedSocketGuildChannel
{
public int Bitrate { get; }
public LimitedICategoryChannel Category { get; }
public ulong? CategoryId { get; }
public int? UserLimit { get; }
public LimitedSocketVoiceChannel(SocketVoiceChannel channel) : base(channel)
{
Bitrate = channel.Bitrate;
Category = new LimitedICategoryChannel(channel.Category);
CategoryId = channel.CategoryId;
UserLimit = channel.UserLimit;
}
}
internal class LimitedSocketTextChannel : LimitedSocketGuildChannel
{
public LimitedICategoryChannel Category { get; }
public ulong? CategoryId { get; }
public bool IsNsfw { get; }
public string Mention { get; }
public int SlowModeInterval { get; }
public string Topic { get; }
public LimitedSocketTextChannel(SocketTextChannel channel) : base(channel)
{
Category = new LimitedICategoryChannel(channel.Category);
CategoryId = channel.CategoryId;
IsNsfw = channel.IsNsfw;
Mention = channel.Mention;
SlowModeInterval = channel.SlowModeInterval;
Topic = channel.Topic;
}
}
internal class LimitedSocketGuildUser : LimitedSocketUser
{
public GuildPermissions GuildPermissions { get; }
public int Hierarchy { get; }
public bool IsDeafened { get; }
public bool IsMuted { get; }
public bool IsSelfDeafened { get; }
public bool IsSelfMuted { get; }
public bool IsSuppressed { get; }
public DateTimeOffset? JoinedAt { get; }
public string Nickname { get; }
public IReadOnlyCollection<LimitedSocketRole> Roles { get; }
public LimitedSocketVoiceChannel VoiceChannel { get; }
public string VoiceSessionId { get; }
public SocketVoiceState? VoiceState { get; }
public LimitedSocketGuildUser(SocketGuildUser user) : base(user)
{
GuildPermissions = user.GuildPermissions;
Hierarchy = user.Hierarchy;
IsDeafened = user.IsDeafened;
IsMuted = user.IsMuted;
IsSelfDeafened = user.IsSelfDeafened;
IsSelfMuted = user.IsSelfMuted;
IsSuppressed = user.IsSuppressed;
JoinedAt = user.JoinedAt;
Nickname = user.Nickname;
Roles = user.Roles.Select(r => new LimitedSocketRole(r)).ToList();
VoiceChannel = user.VoiceChannel == null ? null : new LimitedSocketVoiceChannel(user.VoiceChannel);
VoiceSessionId = user.VoiceSessionId;
VoiceState = user.VoiceState;
}
}
internal class LimitedSocketRole
{
public ulong Id { get; }
public Color Color { get; }
public DateTimeOffset CreatedAt { get; }
public bool IsEveryone { get; }
public bool IsHoisted { get; }
public bool IsManaged { get; }
public bool IsMentionable { get; }
public string Mention { get; }
public string Name { get; }
public GuildPermissions Permissions { get; }
public int Position { get; }
public LimitedSocketRole(SocketRole role)
{
Id = role.Id;
Color = role.Color;
CreatedAt = role.CreatedAt;
IsEveryone = role.IsEveryone;
IsHoisted = role.IsHoisted;
IsManaged = role.IsManaged;
IsMentionable = role.IsMentionable;
Mention = role.Mention;
Name = role.Name;
Permissions = role.Permissions;
Position = role.Position;
}
}
internal class LimitedGuildEmote
{
public string Name { get; }
public ulong Id { get; }
public bool Animated { get; }
public DateTimeOffset CreatedAt { get; }
public string Url { get; }
public ulong? CreatorId { get; }
public bool IsManaged { get; }
public bool RequireColons { get; }
public IReadOnlyList<ulong> RoleIds { get; }
public LimitedGuildEmote(GuildEmote emote)
{
Name = emote.Name;
Id = emote.Id;
Animated = emote.Animated;
CreatedAt = emote.CreatedAt;
Url = emote.Url;
CreatorId = emote.CreatorId;
IsManaged = emote.IsManaged;
RequireColons = emote.RequireColons;
RoleIds = emote.RoleIds;
}
}
internal class LimitedICategoryChannel
{
public int Position { get; }
public ulong GuildId { get; }
public IReadOnlyCollection<Overwrite> PermissionOverwrites { get; }
public string Name { get; }
public DateTimeOffset CreatedAt { get; }
public ulong Id { get; }
public LimitedICategoryChannel(ICategoryChannel category)
{
Position = category.Position;
GuildId = category.GuildId;
PermissionOverwrites = category.PermissionOverwrites;
Name = category.Name;
CreatedAt = category.CreatedAt;
Id = category.Id;
}
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json;
using System.Threading; using System.Threading;
using Discord.Commands; using Discord.Commands;
using NLua; using NLua;
@ -13,7 +15,7 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter
private CancellationToken _ct; private CancellationToken _ct;
private Lua _state; private Lua _state;
public string Interpret(CancellationToken ct, SocketCommandContext context, string content, string command) public string Interpret(CancellationToken ct, LimitedSocketCommandContext context, string content, string command)
{ {
LoggingFacade.Debug($"Interpreting code for {command} using Lua"); LoggingFacade.Debug($"Interpreting code for {command} using Lua");
LoggingFacade.Trace($"Using CancellationToken: {ct}"); LoggingFacade.Trace($"Using CancellationToken: {ct}");

View File

@ -1,4 +1,6 @@
using System; using System;
using System.IO;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ChaosBot.Models; using ChaosBot.Models;
@ -22,10 +24,11 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter
try try
{ {
CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationTokenSource tokenSource = new CancellationTokenSource();
LimitedSocketCommandContext limitedContext = new LimitedSocketCommandContext(context);
Task<string> task = Task.Run(() => Task<string> task = Task.Run(() =>
{ {
Thread.CurrentThread.Priority = ThreadPriority.Lowest; Thread.CurrentThread.Priority = ThreadPriority.Lowest;
return interpreter.Interpret(tokenSource.Token, context, customCommand.Content, customCommand.Command); return interpreter.Interpret(tokenSource.Token, limitedContext, customCommand.Content, customCommand.Command);
}, tokenSource.Token); }, tokenSource.Token);
const int timeout = 250; const int timeout = 250;
@ -39,7 +42,16 @@ namespace ChaosBot.Services.ProgrammingLanguageInterpreter
string output = task.Result; string output = task.Result;
if (output.Length > 0) if (output.Length > 2000)
{
context.Channel.SendMessageAsync($"Command '{customCommand.Command}' has {output.Length} characters of output. That is more than the 2000 limit. Packaging into file...").GetAwaiter().GetResult();
Stream stream = new MemoryStream(Encoding.ASCII.GetBytes(output));
if (stream.Length > 8 * 1024 * 1024)
context.Channel.SendMessageAsync($"Packaged filesize of {stream.Length / 1024 / 1024} MB is too big.");
else
context.Channel.SendFileAsync(stream, "command-output.txt");
}
else if (output.Length > 0)
context.Channel.SendMessageAsync(output); context.Channel.SendMessageAsync(output);
else else
context.Channel.SendMessageAsync($"Command '{customCommand.Command}' had no output."); context.Channel.SendMessageAsync($"Command '{customCommand.Command}' had no output.");