Correct chunking of long messages

This commit is contained in:
benbierens 2023-11-09 10:13:34 +01:00
parent 180bf3fd35
commit ef83441b2f
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
4 changed files with 169 additions and 73 deletions

View File

@ -29,7 +29,15 @@ namespace BiblioTech
}
catch (Exception ex)
{
await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true);
if (IsInAdminChannel(command))
{
var msg = "Failed with exception: " + ex;
await command.FollowupAsync(msg.Substring(0, Math.Min(1900, msg.Length)));
}
else
{
await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true);
}
Console.WriteLine(ex);
}
}

View File

@ -109,7 +109,7 @@ namespace BiblioTech.Commands
return;
}
var report = string.Join(Environment.NewLine, Program.UserRepo.GetInteractionReport(user));
var report = Program.UserRepo.GetInteractionReport(user);
await context.Followup(report);
}
}
@ -134,7 +134,7 @@ namespace BiblioTech.Commands
}
var nl = Environment.NewLine;
await context.Followup($"Deployments:{nl}{string.Join(nl, deployments.Select(FormatDeployment))}");
await context.Followup(deployments.Select(FormatDeployment).ToArray());
}
private string FormatDeployment(CodexDeployment deployment)
@ -249,23 +249,31 @@ namespace BiblioTech.Commands
this.ci = ci;
}
protected async Task OnDeployment(CommandContext context, Func<ICodexNodeGroup, string, Task> action)
protected async Task<T?> OnDeployment<T>(CommandContext context, Func<ICodexNodeGroup, string, T> action)
{
var deployment = Program.DeploymentFilesMonitor.GetDeployments().SingleOrDefault();
if (deployment == null)
{
await context.Followup("No deployment found.");
return;
return default;
}
try
{
var group = ci.WrapCodexContainers(deployment.CodexInstances.Select(i => i.Containers).ToArray());
await action(group, deployment.Metadata.Name);
var result = action(group, deployment.Metadata.Name);
return result;
}
catch (Exception ex)
{
await context.Followup("Failed to wrap nodes with exception: " + ex);
var message = new[]
{
"Failed to wrap nodes with exception: "
};
var exceptionMessage = ex.ToString().Split(Environment.NewLine);
await context.Followup(message.Concat(exceptionMessage).ToArray());
return default;
}
}
}
@ -280,32 +288,40 @@ namespace BiblioTech.Commands
protected override async Task onSubCommand(CommandContext context)
{
await OnDeployment(context, async (group, name) =>
var report = await OnDeployment(context, CreateNetInfoReport);
if (report != null && report.Any())
{
var nl = Environment.NewLine;
var content = new List<string>
{
$"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.",
$"Deployment name: '{name}'"
};
await context.Followup(report);
}
}
foreach (var node in group)
private string[] CreateNetInfoReport(ICodexNodeGroup group, string name)
{
var content = new List<string>
{
$"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.",
$"Deployment name: '{name}'."
};
foreach (var node in group)
{
try
{
try
{
var info = node.GetDebugInfo();
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}";
content.Add($"Node '{node.GetName()}' responded with {jsonInsert}");
}
catch (Exception ex)
{
content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
var info = node.GetDebugInfo();
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonLines = json.Split(Environment.NewLine);
content.Add($"Node '{node.GetName()}' responded with:");
content.Add("---");
content.AddRange(jsonLines);
content.Add("---");
}
catch (Exception ex)
{
content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
}
await context.Followup(string.Join(nl, content));
});
return content.ToArray();
}
}
@ -326,33 +342,41 @@ namespace BiblioTech.Commands
var peerId = await peerIdOption.Parse(context);
if (string.IsNullOrEmpty(peerId)) return;
await OnDeployment(context, async (group, name) =>
var report = await OnDeployment(context, (group, name) => CreateDebugPeerReport(group, name, peerId));
if (report != null && report.Any())
{
var nl = Environment.NewLine;
var content = new List<string>
{
$"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.",
$"Deployment name: '{name}'"
};
await context.Followup(report);
}
}
content.Add($"Calling debug/peer for '{peerId}' on {group.Count()} Codex nodes.");
foreach (var node in group)
private string[] CreateDebugPeerReport(ICodexNodeGroup group, string name, string peerId)
{
var content = new List<string>
{
$"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.",
$"Deployment name: '{name}'.",
$"Calling debug/peer for '{peerId}'."
};
foreach (var node in group)
{
try
{
try
{
var info = node.GetDebugPeer(peerId);
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}";
content.Add($"Node '{node.GetName()}' responded with {jsonInsert}");
}
catch (Exception ex)
{
content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
var info = node.GetDebugPeer(peerId);
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonLines = json.Split(Environment.NewLine);
content.Add($"Node '{node.GetName()}' responded with:");
content.Add("---");
content.AddRange(jsonLines);
content.Add("---");
}
catch (Exception ex)
{
content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
}
await context.Followup(string.Join(nl, content));
});
return content.ToArray();
}
}
}

View File

@ -4,36 +4,99 @@ namespace BiblioTech.Options
{
public class CommandContext
{
private const string AttachmentFolder = "attachments";
public CommandContext(SocketSlashCommand command, IReadOnlyCollection<SocketSlashCommandDataOption> options)
{
Command = command;
Options = options;
var attachmentPath = Path.Combine(Program.Config.DataPath, AttachmentFolder);
if (Directory.Exists(attachmentPath))
{
Directory.CreateDirectory(attachmentPath);
}
}
public SocketSlashCommand Command { get; }
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; }
public async Task Followup(string message)
public async Task Followup(string line)
{
if (message.Length > 1900)
{
await FollowupWithAttachement("codexdiscordbot_file.txt", message);
return;
}
var array = line.Split(Environment.NewLine);
await Followup(array);
}
public async Task Followup(string[] lines)
{
var chunker = new LineChunker(lines);
var chunks = chunker.GetChunks();
if (!chunks.Any()) return;
// First chunk is a modification of the original message.
// Everything after that, we must create a new message.
var first = chunks.First();
chunks.RemoveAt(0);
await Command.ModifyOriginalResponseAsync(m =>
{
m.Content = message;
m.Content = FormatChunk(first);
});
foreach (var remaining in chunks)
{
await Command.FollowupAsync(FormatChunk(remaining));
}
}
private async Task FollowupWithAttachement(string filename, string content)
private string FormatChunk(string[] chunk)
{
using var fileStream = new MemoryStream();
using var streamWriter = new StreamWriter(fileStream);
await streamWriter.WriteAsync(content);
return string.Join(Environment.NewLine, chunk);
}
}
await Command.FollowupWithFileAsync(fileStream, filename);
public class LineChunker
{
private readonly List<string> input;
private readonly int maxCharacters;
public LineChunker(string[] input, int maxCharacters = 1950)
{
this.input = input.ToList();
this.maxCharacters = maxCharacters;
}
public List<string[]> GetChunks()
{
var result = new List<string[]>();
while (input.Any())
{
result.Add(GetChunk());
}
return result;
}
private string[] GetChunk()
{
var totalLength = 0;
var result = new List<string>();
while (input.Any())
{
var nextLine = input[0];
var nextLength = totalLength + nextLine.Length;
if (nextLength > maxCharacters)
{
return result.ToArray();
}
input.RemoveAt(0);
result.Add(nextLine);
totalLength += nextLine.Length;
}
return result.ToArray();
}
}
}

View File

@ -76,17 +76,17 @@ namespace BiblioTech
return result.ToArray();
}
public string GetUserReport(IUser user)
public string[] GetUserReport(IUser user)
{
var userData = GetUserData(user);
if (userData == null) return "User has not joined the test net.";
if (userData == null) return new[] { "User has not joined the test net." };
return userData.CreateOverview();
}
public string GetUserReport(EthAddress ethAddress)
public string[] GetUserReport(EthAddress ethAddress)
{
var userData = GetUserDataForAddress(ethAddress);
if (userData == null) return "No user is using this eth address.";
if (userData == null) return new[] { "No user is using this eth address." };
return userData.CreateOverview();
}
@ -196,14 +196,15 @@ namespace BiblioTech
public List<UserAssociateAddressEvent> AssociateEvents { get; }
public List<UserMintEvent> MintEvents { get; }
public string CreateOverview()
public string[] CreateOverview()
{
var nl = Environment.NewLine;
return
$"name: '{Name}' - id:{DiscordId}{nl}" +
$"joined: {CreatedUtc.ToString("o")}{nl}" +
$"current address: {CurrentAddress}{nl}" +
$"{AssociateEvents.Count + MintEvents.Count} total bot events.";
return new[]
{
$"name: '{Name}' - id:{DiscordId}",
$"joined: {CreatedUtc.ToString("o")}",
$"current address: {CurrentAddress}",
$"{AssociateEvents.Count + MintEvents.Count} total bot events."
};
}
}