From aaf17bcfdc566c9eb5ed6bbcd40d9d7c17d3793c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 02:56:42 +0200 Subject: [PATCH] fix(sub): drop empty remark segments instead of leaving a stray separator expandSegment dropped a "|" segment only when its tokens rendered the unlimited mark, so a segment whose only token resolved to the empty string (a client with no comment, an unlimited client's expiry date) was kept as bare decoration, leaving a trailing "|" or a dangling emoji on every share link's remark. Drop a token-bearing segment whenever none of its tokens produce a real value, while still keeping pure-literal segments. --- internal/sub/remark_vars.go | 18 +++++++++--------- internal/sub/remark_vars_test.go | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/internal/sub/remark_vars.go b/internal/sub/remark_vars.go index 1a2ef3c42..91eba9a71 100644 --- a/internal/sub/remark_vars.go +++ b/internal/sub/remark_vars.go @@ -124,23 +124,23 @@ func expandRemarkVars(template string, ctx remarkContext) string { } // expandSegment expands one "|" segment and reports whether it should be dropped. -// It drops only when the segment carries an unlimited (∞) quota/expiry token and -// no other token in it resolves to a non-empty value — so a segment mixing, say, -// {{EMAIL}} with {{TRAFFIC_LEFT}} is always kept. +// A segment that contains tokens is dropped when none of them resolve to a real +// value — whether because they render the unlimited (∞) mark or the empty string +// — so it leaves no stray "|" separator or dangling decoration. A segment mixing, +// say, {{EMAIL}} with {{TRAFFIC_LEFT}} is kept, and a pure-literal segment (no +// tokens) is always kept. func expandSegment(seg string, ctx remarkContext) (string, bool) { - hasUnlimited, hasOtherValue := false, false + hasToken, hasOtherValue := false, false out := remarkVarRe.ReplaceAllStringFunc(seg, func(m string) string { + hasToken = true token := m[2 : len(m)-2] val := remarkVarValue(token, ctx) - switch { - case unlimitedDropTokens[token] && val == unlimitedMark: - hasUnlimited = true - case val != "": + if val != "" && !(unlimitedDropTokens[token] && val == unlimitedMark) { hasOtherValue = true } return val }) - return out, hasUnlimited && !hasOtherValue + return out, hasToken && !hasOtherValue } func remarkVarValue(token string, ctx remarkContext) string { diff --git a/internal/sub/remark_vars_test.go b/internal/sub/remark_vars_test.go index eb254e957..32276b6a6 100644 --- a/internal/sub/remark_vars_test.go +++ b/internal/sub/remark_vars_test.go @@ -128,6 +128,23 @@ func TestExpandRemarkVars_DropUnlimitedSegments(t *testing.T) { } } +func TestExpandRemarkVars_DropEmptySegments(t *testing.T) { + inbound := &model.Inbound{Remark: "host"} + + // A client with no comment: the {{COMMENT}} segment resolves to empty and + // must be dropped, not left as a trailing "|". + noComment := expandCtx(model.Client{}, xray.ClientTraffic{Enable: true}, inbound) + if got := expandRemarkVars("{{INBOUND}}|{{COMMENT}}", noComment); got != "host" { + t.Errorf("empty comment segment = %q, want %q (no trailing pipe)", got, "host") + } + + // A decorated empty segment (emoji + empty token) drops whole, not leaving + // a dangling emoji. + if got := expandRemarkVars("{{INBOUND}}|📅{{EXPIRE_DATE}}", noComment); got != "host" { + t.Errorf("decorated empty segment = %q, want %q", got, "host") + } +} + func TestClientStatus(t *testing.T) { cases := []struct { name string