diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index e73e21a4d..c7509f4b4 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -779,20 +779,21 @@ export interface GenMtprotoLinkInput { address: string; port?: number; clientSecret?: string; - remark?: string; } // Builds a per-client Telegram proxy deep link for an mtproto inbound from the -// client's own FakeTLS secret. +// client's own FakeTLS secret. No remark fragment is added: Telegram proxy deep +// links have no name field, and a trailing "#remark" gets folded into the last +// query value by lenient parsers, breaking the server address. The panel shows +// the remark separately from the link. export function genMtprotoLink(input: GenMtprotoLinkInput): string { - const { inbound, address, port = inbound.port, clientSecret = '', remark = '' } = input; + const { inbound, address, port = inbound.port, clientSecret = '' } = input; if (inbound.protocol !== 'mtproto') return ''; if (clientSecret.length === 0) return ''; const url = new URL('tg://proxy'); url.searchParams.set('server', address); url.searchParams.set('port', String(port)); url.searchParams.set('secret', clientSecret); - if (remark) url.hash = encodeURIComponent(remark); return url.toString(); } @@ -1130,7 +1131,7 @@ export function genLink(input: GenLinkInput): string { externalProxy, }); case 'mtproto': - return genMtprotoLink({ inbound, address, port, clientSecret: client.secret ?? '', remark }); + return genMtprotoLink({ inbound, address, port, clientSecret: client.secret ?? '' }); default: return ''; } diff --git a/frontend/src/test/mtproto-clients-link.test.ts b/frontend/src/test/mtproto-clients-link.test.ts index 2dfa706c0..8446ec204 100644 --- a/frontend/src/test/mtproto-clients-link.test.ts +++ b/frontend/src/test/mtproto-clients-link.test.ts @@ -37,5 +37,7 @@ describe('mtproto multi-client link fan-out', () => { expect(links[0]).toContain('tg://proxy'); expect(links[0]).toContain('secret=ee0123456789abcdef0123456789abcdef7777772e636c6f7564666c6172652e636f6d'); expect(links[1]).toContain('secret=eeabcdefabcdefabcdefabcdefabcdef01676f6f676c652e636f6d'); + expect(links[0]).not.toContain('#'); + expect(links[1]).not.toContain('#'); }); }); diff --git a/internal/sub/service.go b/internal/sub/service.go index 4951a717b..625e55d22 100644 --- a/internal/sub/service.go +++ b/internal/sub/service.go @@ -663,8 +663,11 @@ func (s *SubService) genWireguardLink(inbound *model.Inbound, email string) stri } // genMtprotoLink builds a per-client Telegram proxy deep link for an mtproto -// inbound: the server/port pair plus the client's own FakeTLS secret. Returns "" -// when the client has no secret. +// inbound: the server/port pair plus the client's own FakeTLS secret. The link +// carries no remark fragment — Telegram proxy deep links have no name field, and +// a trailing "#remark" is appended to the last query value by lenient parsers, +// corrupting the server address. The remark is shown separately in the panel UI. +// Returns "" when the client has no secret. func (s *SubService) genMtprotoLink(inbound *model.Inbound, email string) string { if inbound.Protocol != model.MTProto { return "" @@ -678,7 +681,7 @@ func (s *SubService) genMtprotoLink(inbound *model.Inbound, email string) string "port": fmt.Sprintf("%d", inbound.Port), "secret": resolved.Secret, } - return buildLinkWithParams("tg://proxy", params, s.genRemark(inbound, email, "", "")) + return buildLinkWithParams("tg://proxy", params, "") } // Protocol link generators are intentionally ordered as: diff --git a/internal/sub/service_mtproto_test.go b/internal/sub/service_mtproto_test.go index ba4de3c14..2e17db4fb 100644 --- a/internal/sub/service_mtproto_test.go +++ b/internal/sub/service_mtproto_test.go @@ -40,6 +40,9 @@ func TestGenMtprotoLinkFields(t *testing.T) { if q.Get("secret") != mtprotoTestSecret { t.Fatalf("secret = %q, want the client's FakeTLS secret", q.Get("secret")) } + if u.Fragment != "" { + t.Fatalf("link carries a #%s fragment; tg://proxy links must have no remark fragment", u.Fragment) + } } func TestGenMtprotoLinkWrongProtocol(t *testing.T) {