mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:35:42 +00:00
Post Discord bug reports as plain text with log attachment
Discord-bound bug reports now put the description and metadata in the message content and attach the full recent logs as recent_logs.txt, instead of shipping everything inside a webhook embed. Webhook-embed bodies aren't surfaced by the Claude Code Discord plugin's fetch_messages/push path, so reports couldn't be auto-processed; plain-text content and a file attachment both flow through cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+58
-45
@@ -296,15 +296,16 @@ namespace common.GUIUtils {
|
||||
webhookUrl.Contains("discord.com") || webhookUrl.Contains("discordapp.com");
|
||||
bool isSlack = webhookUrl.Contains("slack.com");
|
||||
|
||||
// Use multipart form for Discord with screenshot
|
||||
if (isDiscord && _capturedScreenshot != null) {
|
||||
return await SendDiscordWithScreenshot(webhookUrl, report, _capturedScreenshot);
|
||||
// Discord always uses multipart: logs go as a file attachment so
|
||||
// the full text is preserved, and the main content is plain text
|
||||
// (not an embed) so the Claude Code Discord plugin can read it
|
||||
// from msg.content. Screenshot is included if the user captured one.
|
||||
if (isDiscord) {
|
||||
return await SendDiscordMultipart(webhookUrl, report, _capturedScreenshot);
|
||||
}
|
||||
|
||||
string json;
|
||||
if (isDiscord) {
|
||||
json = BuildDiscordPayload(report);
|
||||
} else if (isSlack) {
|
||||
if (isSlack) {
|
||||
json = BuildSlackPayload(report);
|
||||
} else {
|
||||
// Generic webhook - use simple JSON
|
||||
@@ -331,13 +332,27 @@ namespace common.GUIUtils {
|
||||
}
|
||||
|
||||
private async Task<bool>
|
||||
SendDiscordWithScreenshot(string webhookUrl, BugReport report, byte[] screenshot) {
|
||||
SendDiscordMultipart(string webhookUrl, BugReport report, byte[] screenshot) {
|
||||
var form = new List<IMultipartFormSection>();
|
||||
|
||||
// Logs as a file attachment so we don't lose content to Discord's
|
||||
// 2000-character message-content cap.
|
||||
var logsText =
|
||||
string.IsNullOrEmpty(report.RecentLogs) ? "(no logs)" : report.RecentLogs;
|
||||
form.Add(new MultipartFormFileSection(
|
||||
"file",
|
||||
screenshot,
|
||||
"screenshot.png",
|
||||
"image/png"));
|
||||
"logs",
|
||||
Encoding.UTF8.GetBytes(logsText),
|
||||
"recent_logs.txt",
|
||||
"text/plain"));
|
||||
|
||||
if (screenshot != null) {
|
||||
form.Add(new MultipartFormFileSection(
|
||||
"screenshot",
|
||||
screenshot,
|
||||
"screenshot.png",
|
||||
"image/png"));
|
||||
}
|
||||
|
||||
form.Add(new MultipartFormDataSection("payload_json", BuildDiscordPayload(report)));
|
||||
|
||||
using var request = UnityWebRequest.Post(webhookUrl, form);
|
||||
@@ -346,7 +361,7 @@ namespace common.GUIUtils {
|
||||
while (!operation.isDone) { await Task.Yield(); }
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success) {
|
||||
Debug.LogError($"Discord screenshot upload failed: {request.error}");
|
||||
Debug.LogError($"Discord bug report upload failed: {request.error}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -354,44 +369,42 @@ namespace common.GUIUtils {
|
||||
}
|
||||
|
||||
private string BuildDiscordPayload(BugReport report) {
|
||||
// Discord webhook format with embeds for nice formatting
|
||||
var truncatedLogs = TruncateForDiscord(report.RecentLogs, 1000);
|
||||
// Plain-text content (not an embed) so the Claude Code Discord plugin
|
||||
// can read it from msg.content. Full logs ride along as a file
|
||||
// attachment, not inline, so content stays inside Discord's 2000-char
|
||||
// cap even on long reports.
|
||||
var content = new StringBuilder();
|
||||
content.Append("**Bug Report** from Eagle0 client\n\n");
|
||||
content.Append($"**Description:** {report.Description}\n\n");
|
||||
content.Append($"**Build:** {report.BuildVersion} ({report.BuildTimestamp})\n");
|
||||
content.Append($"**Platform:** {report.Platform}\n");
|
||||
content.Append($"**Time:** {report.Timestamp:yyyy-MM-dd HH:mm:ss} UTC\n\n");
|
||||
|
||||
var gameInfo = new StringBuilder();
|
||||
if (report.GameId.HasValue) gameInfo.AppendLine($"Game ID: {report.GameId}");
|
||||
if (report.FactionId.HasValue) gameInfo.AppendLine($"Faction ID: {report.FactionId}");
|
||||
content.Append("**Game State:**\n");
|
||||
if (report.GameId.HasValue) content.Append($"- Game ID: {report.GameId}\n");
|
||||
if (report.FactionId.HasValue) content.Append($"- Faction ID: {report.FactionId}\n");
|
||||
if (!string.IsNullOrEmpty(report.CurrentDate))
|
||||
gameInfo.AppendLine($"Game Date: {report.CurrentDate}");
|
||||
content.Append($"- Game Date: {report.CurrentDate}\n");
|
||||
if (report.ActiveBattles?.Any() == true)
|
||||
gameInfo.AppendLine($"Active Battles: {string.Join(", ", report.ActiveBattles)}");
|
||||
content.Append($"- Active Battles: {string.Join(", ", report.ActiveBattles)}\n");
|
||||
if (!report.GameId.HasValue) content.Append("- Not in a game\n");
|
||||
|
||||
var gameInfoStr = gameInfo.Length > 0 ? gameInfo.ToString() : "Not in a game";
|
||||
content.Append($"\n**System Info:**\n```\n{report.SystemInfo}```\n");
|
||||
content.Append("\nFull logs attached.");
|
||||
|
||||
// Build JSON using StringBuilder to avoid clang-format issues with verbatim strings
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("{");
|
||||
sb.Append("\"content\": \"**Bug Report** from Eagle0 client\",");
|
||||
sb.Append("\"embeds\": [{");
|
||||
sb.Append("\"title\": \"Bug Report\",");
|
||||
sb.Append($"\"description\": \"{EscapeJson(report.Description)}\",");
|
||||
sb.Append("\"color\": 15158332,");
|
||||
sb.Append("\"fields\": [");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"Build\", \"value\": \"{EscapeJson(report.BuildVersion)} ({EscapeJson(report.BuildTimestamp)})\", \"inline\": true}},");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"Platform\", \"value\": \"{EscapeJson(report.Platform)}\", \"inline\": true}},");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"Timestamp\", \"value\": \"{report.Timestamp:yyyy-MM-dd HH:mm:ss} UTC\", \"inline\": true}},");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"Game State\", \"value\": \"{EscapeJson(gameInfoStr)}\", \"inline\": false}},");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"System Info\", \"value\": \"```{EscapeJson(report.SystemInfo)}```\", \"inline\": false}},");
|
||||
sb.Append(
|
||||
$"{{\"name\": \"Recent Logs\", \"value\": \"```{EscapeJson(truncatedLogs)}```\", \"inline\": false}}");
|
||||
sb.Append("],");
|
||||
sb.Append($"\"timestamp\": \"{report.Timestamp:yyyy-MM-ddTHH:mm:ssZ}\"");
|
||||
sb.Append("}]}");
|
||||
return sb.ToString();
|
||||
// Discord message content cap is 2000 chars — truncate the tail if
|
||||
// the description + metadata somehow blow past that.
|
||||
const int MaxContent = 1990;
|
||||
var contentStr = content.ToString();
|
||||
if (contentStr.Length > MaxContent) {
|
||||
contentStr = contentStr.Substring(0, MaxContent - 3) + "...";
|
||||
}
|
||||
|
||||
var json = new StringBuilder();
|
||||
json.Append("{\"content\": \"");
|
||||
json.Append(EscapeJson(contentStr));
|
||||
json.Append("\"}");
|
||||
return json.ToString();
|
||||
}
|
||||
|
||||
private string BuildSlackPayload(BugReport report) {
|
||||
|
||||
Reference in New Issue
Block a user