fix for owpg

This commit is contained in:
onysd 2026-07-21 12:17:50 +03:00
parent d5c51f620c
commit ddd26e51b5
2 changed files with 55 additions and 21 deletions

View file

@ -157,6 +157,14 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
if logger == nil {
logger = zap.NewNop()
}
// links.ValidateBaseURL above guarantees cfg.PublicBaseURL parses with a
// non-empty host; publicHost is the bare "host[:port]" used to build
// owpg://<host>/<path> deep links (the client reinterprets those as
// https://<host>/<path>, see LaunchActivity's "owpg" scheme case).
publicHost := cfg.PublicBaseURL
if u, err := url.Parse(cfg.PublicBaseURL); err == nil {
publicHost = u.Host
}
h := &handler{
stickerSets: cfg.StickerSets,
users: cfg.Users,
@ -166,6 +174,7 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
uniqueGifts: cfg.UniqueGifts,
giftWithdrawals: cfg.GiftWithdrawals,
publicBaseURL: cfg.PublicBaseURL,
publicHost: publicHost,
appScheme: cfg.AppScheme,
webBaseURL: cfg.WebBaseURL,
appName: cfg.AppName,
@ -199,6 +208,7 @@ type handler struct {
uniqueGifts UniqueStarGiftResolver
giftWithdrawals StarGiftWithdrawalResolver
publicBaseURL string
publicHost string
appScheme string
webBaseURL string
appName string
@ -284,7 +294,7 @@ func (h *handler) addList(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
return
}
app := h.appURL("addlist", "slug", slug)
app := h.appURL("addlist", slug)
data := pageData{
AppName: h.appName,
Title: "Shared Folder",
@ -340,7 +350,7 @@ func (h *handler) uniqueGift(w http.ResponseWriter, r *http.Request) {
if unique.AvailabilityIssued > 0 && unique.AvailabilityTotal >= unique.AvailabilityIssued {
subtitle += fmt.Sprintf(" · %s/%s issued", groupedDecimal(unique.AvailabilityIssued), groupedDecimal(unique.AvailabilityTotal))
}
app := h.appURL("nft", "slug", canonicalSlug)
app := h.appURL("nft", canonicalSlug)
data := pageData{
AppName: h.appName,
Title: title,
@ -385,8 +395,10 @@ func (h *handler) usernameLink(w http.ResponseWriter, r *http.Request) {
h.serveUsernameNotFound(w, username)
return
}
// owpg://<host>/<username> — built from params BEFORE "domain" is added
// below, since owpg encodes the username in the path, not as a query key.
app := h.appUsernameURL(peer.username, params)
params.Set("domain", peer.username)
app := schemeURLValues(h.appScheme, "resolve", params)
description := peer.about
if description == "" {
description = peer.fallbackDescription(h.appName)
@ -455,7 +467,7 @@ func (h *handler) inviteLink(w http.ResponseWriter, r *http.Request, hash string
if title == "" {
title = strings.TrimSpace(invite.Title)
}
app := h.appURL("join", "invite", hash)
app := h.appInviteURL(hash)
description := peer.about
if description == "" {
description = peer.fallbackDescription(h.appName)
@ -618,7 +630,7 @@ func (h *handler) serveSet(w http.ResponseWriter, r *http.Request, pathKind stri
if count == 0 {
count = len(docs)
}
app := h.appURL(canonicalKind, "set", set.ShortName)
app := h.appURL(canonicalKind, set.ShortName)
data := pageData{
AppName: h.appName,
Title: fallbackTitle(set),
@ -1169,10 +1181,32 @@ func itemNoun(set domain.StickerSet, count int) string {
return "stickers"
}
func (h *handler) appURL(kind, key, value string) string {
return schemeURL(h.appScheme, kind, key, value)
// appURL/appInviteURL/appUsernameURL build owpg://<host>/<path> deep links —
// mirrors publicURL/publicInviteURL/publicUsernameURL (https://<host>/<path>)
// but with the app scheme instead. The client's "owpg" URI handler reinterprets
// owpg://<host>/<path>?<query> as https://<host>/<path>?<query> and falls
// through to the normal t.me-style path router (LaunchActivity.java), which
// expects a path, not a tg://-style "resolve?domain=" query. Do NOT use
// schemeURL here — that format is only valid for the real tg:// scheme.
func (h *handler) appURL(kind, value string) string {
return h.appScheme + "://" + h.publicHost + "/" + kind + "/" + url.PathEscape(value)
}
func (h *handler) appInviteURL(hash string) string {
return h.appScheme + "://" + h.publicHost + "/+" + url.PathEscape(hash)
}
func (h *handler) appUsernameURL(username string, extraQuery url.Values) string {
u := h.appScheme + "://" + h.publicHost + "/" + url.PathEscape(username)
if len(extraQuery) > 0 {
u += "?" + extraQuery.Encode()
}
return u
}
// schemeURL/schemeURLValues build the real tg:// deep-link format
// (tg://resolve?domain=X) — used only for the legacy-client fallback link
// (LegacyTgURL), never for the app's own owpg:// scheme.
func schemeURL(scheme, kind, key, value string) string {
return scheme + "://" + kind + "?" + key + "=" + url.QueryEscape(value)
}

View file

@ -88,7 +88,7 @@ func TestHandlerServesUniqueGiftLandingPage(t *testing.T) {
for _, want := range []string{
"Official Gift", "Collectible #7", "7/1 000 issued",
"http://127.0.0.1:2401/nft/" + slug,
"telesrv://nft?slug=" + slug,
"telesrv://127.0.0.1:2401/nft/" + slug,
"tg://nft?slug=" + slug,
"Open it in the app to view its current details.",
} {
@ -219,7 +219,7 @@ func TestHandlerServesStickerSetLandingPage(t *testing.T) {
for _, want := range []string{
"Fresh Pack",
"https://telesrv.net/addstickers/fresh_pack",
"telesrv://addstickers?set=fresh_pack",
"telesrv://telesrv.net/addstickers/fresh_pack",
"Files are still fetched by the app through MTProto.",
} {
if !strings.Contains(body, want) {
@ -257,7 +257,7 @@ func TestHandlerServesEmojiLandingPage(t *testing.T) {
for _, want := range []string{
"custom emoji set",
"https://example.test/base/addemoji/emoji_pack",
"telesrv://addemoji?set=emoji_pack",
"telesrv://example.test/addemoji/emoji_pack",
} {
if !strings.Contains(body, want) {
t.Fatalf("body missing %q:\n%s", want, body)
@ -278,7 +278,7 @@ func TestHandlerServesChatlistLandingPage(t *testing.T) {
for _, want := range []string{
"Shared Folder",
"http://127.0.0.1:2401/addlist/zNhytIbwRwjaC2GH",
"telesrv://addlist?slug=zNhytIbwRwjaC2GH",
"telesrv://127.0.0.1:2401/addlist/zNhytIbwRwjaC2GH",
"preview and add this shared folder",
} {
if !strings.Contains(body, want) {
@ -313,11 +313,11 @@ func TestHandlerServesBotUsernameLandingPage(t *testing.T) {
"bot",
"@TetrisBot",
"http://127.0.0.1:2401/TetrisBot",
"telesrv://resolve?domain=TetrisBot",
"telesrv://127.0.0.1:2401/TetrisBot",
"Start Bot",
"Open telesrv to start a chat with this bot.",
`property="og:title" content="Tetris Bot"`,
`property="al:android:url" content="telesrv://resolve?domain=TetrisBot"`,
`property="al:android:url" content="telesrv://127.0.0.1:2401/TetrisBot"`,
} {
if !strings.Contains(body, want) {
t.Fatalf("body missing %q:\n%s", want, body)
@ -353,7 +353,7 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
}
body := rr.Body.String()
for _, want := range []string{
"example-chat://resolve?domain=Alice&amp;start=hello",
"example-chat://links.example.test/Alice?start=hello",
"https://web.example.test/client/#?tgaddr=",
"Example Chat",
"Open Example Chat to send a message to @Alice.",
@ -369,10 +369,10 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
path string
want string
}{
{path: "/addstickers/stickers_pack", want: "example-chat://addstickers?set=stickers_pack"},
{path: "/addemoji/emoji_pack", want: "example-chat://addemoji?set=emoji_pack"},
{path: "/addlist/shared-folder", want: "example-chat://addlist?slug=shared-folder"},
{path: "/nft/gift-1", want: "example-chat://nft?slug=gift-1"},
{path: "/addstickers/stickers_pack", want: "example-chat://links.example.test/addstickers/stickers_pack"},
{path: "/addemoji/emoji_pack", want: "example-chat://links.example.test/addemoji/emoji_pack"},
{path: "/addlist/shared-folder", want: "example-chat://links.example.test/addlist/shared-folder"},
{path: "/nft/gift-1", want: "example-chat://links.example.test/nft/gift-1"},
} {
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, tc.path, nil))
@ -447,14 +447,14 @@ func TestHandlerServesUserChannelAndSupergroupLandingPages(t *testing.T) {
path: "/aLiCe/",
wants: []string{
"Alice Example", "Public bio", "@Alice", "Send Message", "Verified",
"https://telesrv.net/Alice", "telesrv://resolve?domain=Alice",
"https://telesrv.net/Alice", "telesrv://telesrv.net/Alice",
},
},
{
path: "/NewsRoom",
wants: []string{
"News Room", "Public channel description", "12 001 subscribers", "View Channel",
"https://telesrv.net/NewsRoom", "telesrv://resolve?domain=NewsRoom",
"https://telesrv.net/NewsRoom", "telesrv://telesrv.net/NewsRoom",
"/_public/avatar/NewsRoom/301",
},
},
@ -497,7 +497,7 @@ func TestHandlerPreservesBoundedResolveQueryAndOverridesDomain(t *testing.T) {
}
body := rr.Body.String()
for _, want := range []string{
"telesrv://resolve?domain=TetrisBot&amp;ref=campaign&amp;start=hello",
"telesrv://telesrv.net/TetrisBot?ref=campaign&amp;start=hello",
} {
if !strings.Contains(body, want) {
t.Fatalf("body missing sanitized query %q:\n%s", want, body)