mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-13 23:01:00 +00:00
feat(sub): add dynamic remark variables with Jalali date, transport, and status tokens (#5430)
* feat(sub): implement dynamic single-bracket remark variables with timezone-aware inline Jalali conversion
* Update .gitignore
* Update .gitignore
* merge: bring in origin/main commits to resolve conflict base
* fix(sub): address review issues in dynamic remark variables
- Add TIME_LEFT to unlimitedDropTokens so segments containing only
{TIME_LEFT} are dropped for unlimited clients (same as DAYS_LEFT)
- Remove dead uiSingleBraceRe variable (translateUISingleBrackets uses
a character scanner, not this regex)
- Change expireDateLabel to use time.Local instead of UTC, consistent
with jalaliExpireDateLabel
Co-authored-by: Sanaei <MHSanaei@users.noreply.github.com>
* fix
* fix
---------
Co-authored-by: MHSanaei <MHSanaei@users.noreply.github.com>
This commit is contained in:
+16
-15
@@ -532,10 +532,10 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
return s.buildVmessExternalProxyLinks(externalProxies, obj, inbound, email)
|
||||
return s.buildVmessExternalProxyLinks(externalProxies, obj, inbound, email, network)
|
||||
}
|
||||
|
||||
obj["ps"] = s.genRemark(inbound, email, "")
|
||||
obj["ps"] = s.genRemark(inbound, email, "", network)
|
||||
return buildVmessLink(obj)
|
||||
}
|
||||
|
||||
@@ -619,13 +619,13 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
return fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(dest, port))
|
||||
},
|
||||
func(ep map[string]any) string {
|
||||
return s.endpointRemark(inbound, email, ep)
|
||||
return s.endpointRemark(inbound, email, ep, streamNetwork)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
link := fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(address, port))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
|
||||
}
|
||||
|
||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
||||
@@ -670,13 +670,13 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
return fmt.Sprintf("trojan://%s@%s", password, joinHostPort(dest, port))
|
||||
},
|
||||
func(ep map[string]any) string {
|
||||
return s.endpointRemark(inbound, email, ep)
|
||||
return s.endpointRemark(inbound, email, ep, streamNetwork)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
link := fmt.Sprintf("trojan://%s@%s", password, joinHostPort(address, port))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
|
||||
}
|
||||
|
||||
// encodeUserinfo percent-encodes a userinfo (password/auth) value so it
|
||||
@@ -763,13 +763,13 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
return fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(dest, port))
|
||||
},
|
||||
func(ep map[string]any) string {
|
||||
return s.endpointRemark(inbound, email, ep)
|
||||
return s.endpointRemark(inbound, email, ep, streamNetwork)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
link := fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(address, inbound.Port))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
|
||||
}
|
||||
|
||||
func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
|
||||
@@ -872,7 +872,7 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
|
||||
applyExternalProxyHysteriaParams(ep, epParams)
|
||||
|
||||
link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(dest, int(portF)))
|
||||
links = append(links, buildLinkWithParams(link, epParams, s.endpointRemark(inbound, email, ep)))
|
||||
links = append(links, buildLinkWithParams(link, epParams, s.endpointRemark(inbound, email, ep, "quic")))
|
||||
}
|
||||
return strings.Join(links, "\n")
|
||||
}
|
||||
@@ -883,7 +883,7 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
|
||||
params["mport"] = hopPorts
|
||||
}
|
||||
link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, ""))
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", "quic"))
|
||||
}
|
||||
|
||||
// hysteriaHopPorts returns the configured Hysteria2 UDP port-hopping range
|
||||
@@ -1494,14 +1494,15 @@ func joinAnyStrings(items []any) string {
|
||||
|
||||
// buildVmessExternalProxyLinks is a thin adapter: it maps the legacy
|
||||
// externalProxy entries to []ShareEndpoint and renders them through the unified
|
||||
// endpoint path. Kept so genVmessLink's call site is unchanged.
|
||||
func (s *SubService) buildVmessExternalProxyLinks(externalProxies []any, baseObj map[string]any, inbound *model.Inbound, email string) string {
|
||||
// endpoint path. Kept as a thin shim over the unified endpoint builder so
|
||||
// genVmessLink keeps calling one helper (now threading transport through).
|
||||
func (s *SubService) buildVmessExternalProxyLinks(externalProxies []any, baseObj map[string]any, inbound *model.Inbound, email string, transport string) string {
|
||||
eps := make([]ShareEndpoint, 0, len(externalProxies))
|
||||
for _, externalProxy := range externalProxies {
|
||||
ep, _ := externalProxy.(map[string]any)
|
||||
eps = append(eps, externalProxyToEndpoint(ep))
|
||||
}
|
||||
return s.buildEndpointVmessLinks(eps, baseObj, inbound, email)
|
||||
return s.buildEndpointVmessLinks(eps, baseObj, inbound, email, transport)
|
||||
}
|
||||
|
||||
// buildLinkWithParams appends ?query and #fragment to a pre-built
|
||||
@@ -1588,9 +1589,9 @@ func cloneStringMap(source map[string]string) map[string]string {
|
||||
// externalProxy / synthetic JSON-Clash entry). In the subscription body a set
|
||||
// remark template takes over; otherwise (and in every display context) the
|
||||
// remark is just the config name (inbound remark, then extra).
|
||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string, transport string) string {
|
||||
if s.remarkTemplate != "" && s.subscriptionBody {
|
||||
return s.genTemplatedRemark(inbound, s.lookupClient(inbound, email), extra)
|
||||
return s.genTemplatedRemark(inbound, s.lookupClient(inbound, email), extra, transport)
|
||||
}
|
||||
// Sub info page + panel link/QR displays: just the config name (no template,
|
||||
// so no per-client email/usage leaks into the shown remark).
|
||||
|
||||
Reference in New Issue
Block a user