Skip to content

Commit

Permalink
feat: Add evaluation commands
Browse files Browse the repository at this point in the history
  • Loading branch information
sabihoshi committed Jan 1, 2022
1 parent 0e4bd3c commit c742913
Show file tree
Hide file tree
Showing 11 changed files with 538 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Zhongli.Bot/Bot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Zhongli.Services.CommandHelp;
using Zhongli.Services.Core;
using Zhongli.Services.Core.Listeners;
using Zhongli.Services.Evaluation;
using Zhongli.Services.Expirable;
using Zhongli.Services.Image;
using Zhongli.Services.Logging;
Expand Down Expand Up @@ -59,6 +60,7 @@ private static ServiceProvider ConfigureServices() =>
.AddSingleton(new InteractiveConfig { DefaultTimeout = TimeSpan.FromMinutes(10) })
.AddSingleton<DiscordSocketListener>()
.AddScoped<AuthorizationService>()
.AddScoped<EvaluationService>()
.AddScoped<ModerationService>()
.AddScoped<ModerationLoggingService>()
.AddScoped<LoggingService>()
Expand Down
19 changes: 19 additions & 0 deletions Zhongli.Bot/Modules/GeneralModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,32 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Humanizer;
using Zhongli.Services.Core.Preconditions.Commands;
using Zhongli.Services.Evaluation;
using Zhongli.Services.Utilities;
using CommandContext = Zhongli.Data.Models.Discord.CommandContext;

namespace Zhongli.Bot.Modules;

public class GeneralModule : ModuleBase<SocketCommandContext>
{
private readonly EvaluationService _evaluation;

public GeneralModule(EvaluationService evaluation) { _evaluation = evaluation; }

[Command("eval")]
[RequireTeamMember]
public async Task EvalAsync([Remainder] string code)
{
var context = new CommandContext(Context);
var result = await _evaluation.EvaluateAsync(context, code);

var embed = EvaluationService.BuildEmbed(context, result);
await ReplyAsync(embed: embed.Build());
}

[Command("ping")]
[Summary("Shows the ping latency of the bot.")]
public async Task PingAsync()
Expand Down
27 changes: 27 additions & 0 deletions Zhongli.Bot/Modules/InteractiveGeneralModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Threading.Tasks;
using Discord.Commands;
using Discord.Interactions;
using Zhongli.Services.Core.Preconditions.Interactions;
using Zhongli.Services.Evaluation;
using InteractionContext = Zhongli.Data.Models.Discord.InteractionContext;

namespace Zhongli.Bot.Modules;

public class InteractiveGeneralModule : InteractionModuleBase<SocketInteractionContext>
{
private readonly EvaluationService _evaluation;

public InteractiveGeneralModule(EvaluationService evaluation) { _evaluation = evaluation; }

[SlashCommand("eval", "Evaluate C# code")]
[RequireTeamMember]
public async Task EvalAsync([Remainder] string code)
{
await DeferAsync(true);
var context = new InteractionContext(Context);
var result = await _evaluation.EvaluateAsync(context, code);

var embed = EvaluationService.BuildEmbed(context, result);
await FollowupAsync(embed: embed.Build(), ephemeral: true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;

namespace Zhongli.Services.Core.Preconditions.Commands;

public class RequireTeamMemberAttribute : PreconditionAttribute
{
public override async Task<PreconditionResult> CheckPermissionsAsync(
ICommandContext context, CommandInfo command, IServiceProvider services)
{
if (context.Client.TokenType is not TokenType.Bot)
{
return PreconditionResult.FromError(
$"{nameof(RequireTeamMemberAttribute)} is not supported by this TokenType.");
}

var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false);

if (context.User.Id == application.Owner.Id
|| application.Team.OwnerUserId == application.Owner.Id
|| application.Team.TeamMembers.Any(t => context.User.Id == t.User.Id))
return PreconditionResult.FromSuccess();

return PreconditionResult.FromError("Command can only be run by team members of the bot.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Interactions;

namespace Zhongli.Services.Core.Preconditions.Interactions;

public class RequireTeamMemberAttribute : PreconditionAttribute
{
public override async Task<PreconditionResult> CheckRequirementsAsync(
IInteractionContext context, ICommandInfo command, IServiceProvider services)
{
if (context.Client.TokenType is not TokenType.Bot)
{
return PreconditionResult.FromError(
$"{nameof(RequireTeamMemberAttribute)} is not supported by this TokenType.");
}

var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false);

if (context.User.Id == application.Owner.Id
|| application.Team.OwnerUserId == application.Owner.Id
|| application.Team.TeamMembers.Any(t => context.User.Id == t.User.Id))
return PreconditionResult.FromSuccess();

return PreconditionResult.FromError("Command can only be run by team members of the bot.");
}
}
65 changes: 65 additions & 0 deletions Zhongli.Services/Evaluation/ConsoleLikeStringWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;

namespace Zhongli.Services.Evaluation;

[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public class ConsoleLikeStringWriter : StringWriter
{
public ConsoleLikeStringWriter(StringBuilder builder) : base(builder) { }

public ConsoleKeyInfo ReadKey() => new('z', ConsoleKey.Z, false, false, false);

public ConsoleKeyInfo ReadKey(bool z)
{
if (z) Write("z");
return ReadKey();
}

public int Read() => 0;

public Stream OpenStandardError() => new MemoryStream();

public Stream OpenStandardError(int a) => new MemoryStream(a);

public Stream OpenStandardInput() => new MemoryStream();

public Stream OpenStandardInput(int a) => new MemoryStream(a);

public Stream OpenStandardOutput() => new MemoryStream();

public Stream OpenStandardOutput(int a) => new MemoryStream(a);

public string ReadLine() => $"{nameof(Zhongli)}{Environment.NewLine}";

public void Beep() { }

public void Beep(int a, int b) { }

public void Clear() { }

public void MoveBufferArea(int a, int b, int c, int d, int e) { }

public void MoveBufferArea(int a, int b, int c, int d, int e, char f, ConsoleColor g, ConsoleColor h) { }

public void ResetColor() { }

public void SetBufferSize(int a, int b) { }

public void SetCursorPosition(int a, int b) { }

public void SetError(TextWriter wr) { }

public void SetIn(TextWriter wr) { }

public void SetOut(TextWriter wr) { }

public void SetWindowPosition(int a, int b) { }

public void SetWindowSize(int a, int b) { }
}
90 changes: 90 additions & 0 deletions Zhongli.Services/Evaluation/EvaluationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Scripting;

namespace Zhongli.Services.Evaluation;

public class EvaluationResult
{
public static readonly JsonSerializerOptions SerializerOptions = new()
{
MaxDepth = 10240,
IncludeFields = true,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};

public EvaluationResult() { }

public EvaluationResult(string code, ScriptState<object?> state, string consoleOut, TimeSpan executionTime,
TimeSpan compileTime)
{
state = state ?? throw new ArgumentNullException(nameof(state));

ReturnValue = state.ReturnValue;
var type = state.ReturnValue?.GetType();

if (type?.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IEnumerator)) ?? false)
{
var genericParams = type.GetGenericArguments();

if (genericParams.Length == 2)
{
type = typeof(List<>).MakeGenericType(genericParams[1]);

ReturnValue = Activator.CreateInstance(type, ReturnValue);
}
}

ReturnTypeName = type?.ParseGenericArgs();
ExecutionTime = executionTime;
CompileTime = compileTime;
ConsoleOut = consoleOut;
Code = code;
Exception = state.Exception?.Message;
ExceptionType = state.Exception?.GetType().Name;
}

public object? ReturnValue { get; set; }

public string Code { get; set; } = null!;

public string ConsoleOut { get; set; } = null!;

public string? Exception { get; set; }

public string? ExceptionType { get; set; }

public string? ReturnTypeName { get; set; }

public TimeSpan CompileTime { get; set; }

public TimeSpan ExecutionTime { get; set; }

public static EvaluationResult CreateErrorResult(string code, string consoleOut, TimeSpan compileTime,
ImmutableArray<Diagnostic> compileErrors)
{
var ex = new CompilationErrorException(string.Join("\n", compileErrors.Select(a => a.GetMessage())),
compileErrors);
var errorResult = new EvaluationResult
{
Code = code,
CompileTime = compileTime,
ConsoleOut = consoleOut,
Exception = ex.Message,
ExceptionType = ex.GetType().Name,
ExecutionTime = TimeSpan.FromMilliseconds(0),
ReturnValue = null,
ReturnTypeName = null
};
return errorResult;
}
}
Loading

0 comments on commit c742913

Please sign in to comment.