From ea15629fce9d4c55ee66f1a3026dc65e6b1b4163 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Wed, 21 Jan 2026 12:46:34 -0500 Subject: [PATCH] fix: normalize discord agent newlines Purpose: - Backport the Discord notification newline normalization from 7.3 to 7.2. - Keep Pujit Mehrotra credited as the original author of the fix. Before: - Discord status report payloads kept literal \n, \r\n, and \r sequences in DESCRIPTION and CONTENT values. Why that was a problem: - Multi-line Discord reports rendered escaped newline text instead of real line breaks. What the new change accomplishes: - Converts literal newline escape sequences before the Discord payload is generated. - Adds a regression test that verifies generated Discord field text contains real line breaks. How it works: - Normalizes DESCRIPTION and CONTENT shell variables before link handling and payload construction. - Extracts the Discord agent CDATA into a temporary script and stubs curl/date for deterministic test assertions. Original PR: https://github.com/unraid/webgui/pull/2527 (cherry picked from commit 309ae17179486ffccac3af4df8513de035e28cbb) --- emhttp/plugins/dynamix/agents/Discord.xml | 12 ++++ .../agents/tests/discord_newline_test.sh | 72 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 emhttp/plugins/dynamix/agents/tests/discord_newline_test.sh diff --git a/emhttp/plugins/dynamix/agents/Discord.xml b/emhttp/plugins/dynamix/agents/Discord.xml index f086531fcc..1fc39b16cd 100644 --- a/emhttp/plugins/dynamix/agents/Discord.xml +++ b/emhttp/plugins/dynamix/agents/Discord.xml @@ -50,6 +50,18 @@ LINK="${LINK:-}" HOSTNAME="${HOSTNAME:-$(hostname)}" TIMESTAMP="${TIMESTAMP:-$(date +%s)}" +# Convert literal \n sequences into real line breaks for reports. +if [[ -n "${DESCRIPTION}" ]]; then + DESCRIPTION=${DESCRIPTION//\\r\\n/$'\n'} + DESCRIPTION=${DESCRIPTION//\\n/$'\n'} + DESCRIPTION=${DESCRIPTION//\\r/$'\n'} +fi +if [[ -n "${CONTENT}" ]]; then + CONTENT=${CONTENT//\\r\\n/$'\n'} + CONTENT=${CONTENT//\\n/$'\n'} + CONTENT=${CONTENT//\\r/$'\n'} +fi + # ensure link has a host if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then if [[ -r /usr/local/emhttp/state/nginx.ini ]]; then diff --git a/emhttp/plugins/dynamix/agents/tests/discord_newline_test.sh b/emhttp/plugins/dynamix/agents/tests/discord_newline_test.sh new file mode 100644 index 0000000000..3fa4cce347 --- /dev/null +++ b/emhttp/plugins/dynamix/agents/tests/discord_newline_test.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +agent_xml="${script_dir}/../Discord.xml" + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +stub_dir="${tmp_dir}/bin" +payload="${tmp_dir}/payload.json" +script_path="${tmp_dir}/Discord.sh" + +mkdir -p "$stub_dir" + +cat > "${stub_dir}/curl" <<'EOF' +#!/bin/bash +out="${DISCORD_TEST_OUT:-/tmp/discord_payload.json}" +for ((i=1; i<=$#; i++)); do + if [[ "${!i}" == "--data-binary" ]]; then + j=$((i+1)) + printf '%s' "${!j}" > "$out" + exit 0 + fi +done +exit 0 +EOF +chmod +x "${stub_dir}/curl" + +cat > "${stub_dir}/date" <<'EOF' +#!/bin/bash +if [[ "$*" == *"-d "* ]]; then + printf '%s\n' "1970-01-01T00:00:00.000Z" + exit 0 +fi +exec /bin/date "$@" +EOF +chmod +x "${stub_dir}/date" + +awk ' + index($0, "") { in_block=0; next } + in_block { print } +' "$agent_xml" | sed 's/{0}//' > "$script_path" +chmod +x "$script_path" + +run_case() { + local desc="$1" + local content="$2" + local expected="$3" + + rm -f "$payload" + DISCORD_TEST_OUT="$payload" \ + WEBH_URL="http://example.invalid" \ + DESCRIPTION="$desc" \ + CONTENT="$content" \ + PATH="$stub_dir:$PATH" \ + bash "$script_path" >/dev/null + + actual="$(jq -r '.embeds[0].fields[0].value' "$payload")" + if [[ "$actual" != "$expected" ]]; then + echo "Discord newline test failed." >&2 + printf 'Expected:\n%s\n' "$expected" >&2 + printf 'Actual:\n%s\n' "$actual" >&2 + exit 1 + fi +} + +run_case "Line1\\nLine2" "Line3\\nLine4" $'Line1\nLine2\n\nLine3\nLine4' +run_case "Line1\\nLine2" "" $'Line1\nLine2' + +echo "OK"