diff --git a/frontend/src/lib/remark/remarkVariables.ts b/frontend/src/lib/remark/remarkVariables.ts index 9f44a9297..66233625e 100644 --- a/frontend/src/lib/remark/remarkVariables.ts +++ b/frontend/src/lib/remark/remarkVariables.ts @@ -39,7 +39,7 @@ export const REMARK_VARIABLES: RemarkVar[] = [ { token: 'STATUS_EMOJI', group: 'time', sample: '✅' }, { token: 'DAYS_LEFT', group: 'time', sample: '12' }, { token: 'TIME_LEFT', group: 'time', sample: '12d 4h 30m' }, - { token: 'USAGE_PERCENTAGE', group: 'time', sample: '52.3%' }, + { token: 'USAGE_PERCENTAGE', group: 'time', sample: '52.3%' }, { token: 'EXPIRE_DATE', group: 'time', sample: '2026-09-01' }, { token: 'JALALI_EXPIRE_DATE', group: 'time', sample: '1405/06/10' }, { token: 'EXPIRE_UNIX', group: 'time', sample: '1788300000' }, diff --git a/internal/sub/remark_vars.go b/internal/sub/remark_vars.go index 2052ad71b..a77f1ff05 100644 --- a/internal/sub/remark_vars.go +++ b/internal/sub/remark_vars.go @@ -350,8 +350,8 @@ func statusEmoji(st xray.ClientTraffic) string { } } -// usagePercentage computes the traffic usage as a percentage string (e.g. "52.3%"). -// Returns "" when the client has no traffic limit. +// usagePercentage computes the traffic usage as a percentage string (e.g. "52.3%"). +// Uses U+FF05: an ASCII percent encodes to %25, which Happ rejects, dropping the remark. func usagePercentage(st xray.ClientTraffic) string { if st.Total <= 0 { return "" @@ -361,7 +361,7 @@ func usagePercentage(st xray.ClientTraffic) string { if pct > 100 { pct = 100 // clamp over-quota usage, consistent with TRAFFIC_LEFT } - return fmt.Sprintf("%.1f%%", pct) + return fmt.Sprintf("%.1f%", pct) } // timeLeftLabel renders remaining time as "Xd Xh Xm" (or shorter when days/hours diff --git a/internal/sub/remark_vars_test.go b/internal/sub/remark_vars_test.go index 23d396c68..55bfa7dab 100644 --- a/internal/sub/remark_vars_test.go +++ b/internal/sub/remark_vars_test.go @@ -1,6 +1,7 @@ package sub import ( + "net/url" "strings" "testing" @@ -480,18 +481,33 @@ func TestStatusEmoji(t *testing.T) { } func TestUsagePercentage(t *testing.T) { - if got := usagePercentage(xray.ClientTraffic{Total: 100 * gb, Up: 25 * gb, Down: 25 * gb}); got != "50.0%" { + if got := usagePercentage(xray.ClientTraffic{Total: 100 * gb, Up: 25 * gb, Down: 25 * gb}); got != "50.0%" { t.Errorf("usagePercentage 50%% = %q", got) } if got := usagePercentage(xray.ClientTraffic{Total: 0}); got != "" { t.Errorf("usagePercentage unlimited = %q, want empty", got) } - if got := usagePercentage(xray.ClientTraffic{Total: 10 * gb, Up: 10 * gb}); got != "100.0%" { + if got := usagePercentage(xray.ClientTraffic{Total: 10 * gb, Up: 10 * gb}); got != "100.0%" { t.Errorf("usagePercentage 100%% = %q", got) } // Over-quota usage clamps to 100%, consistent with TRAFFIC_LEFT. - if got := usagePercentage(xray.ClientTraffic{Total: 10 * gb, Up: 25 * gb}); got != "100.0%" { - t.Errorf("usagePercentage over-quota = %q, want 100.0%%", got) + if got := usagePercentage(xray.ClientTraffic{Total: 10 * gb, Up: 25 * gb}); got != "100.0%" { + t.Errorf("usagePercentage over-quota = %q, want 100.0%", got) + } +} + +func TestUsagePercentageSurvivesFragmentEncoding(t *testing.T) { + remark := "node " + usagePercentage(xray.ClientTraffic{Total: 100 * gb, Up: 50 * gb}) + link := buildLinkWithParams("vless://id@example.test:443", nil, remark) + if strings.Contains(link, "%25") { + t.Fatalf("encoded remark contains %%25, Happ drops such remarks: %s", link) + } + u, err := url.Parse(link) + if err != nil { + t.Fatalf("parse: %v", err) + } + if u.Fragment != remark { + t.Fatalf("fragment = %q, want %q", u.Fragment, remark) } } @@ -546,7 +562,7 @@ func TestExpandNewTokensInTemplate(t *testing.T) { cases := []struct{ tmpl, want string }{ {"{{STATUS_EMOJI}}", "✅"}, - {"{{USAGE_PERCENTAGE}}", "50.0%"}, + {"{{USAGE_PERCENTAGE}}", "50.0%"}, {"{{PROTOCOL}}", "VLESS"}, {"{{TRANSPORT}}", "ws"}, {"{{SECURITY}}", "REALITY"}, @@ -619,7 +635,7 @@ func TestExpandRemarkVars_SingleBracketUI(t *testing.T) { {"{DATA_USAGE}", "50.00GB"}, {"{DATA_LIMIT}", "100.00GB"}, {"{STATUS_EMOJI}", "✅"}, - {"{USAGE_PERCENTAGE}", "50.0%"}, + {"{USAGE_PERCENTAGE}", "50.0%"}, {"{PROTOCOL}", "VLESS"}, {"{TRANSPORT}", "ws"}, {"{SECURITY}", "TLS"},