removed all "paid" features - no more stars, gifts, or grams

This commit is contained in:
onysd 2026-08-07 01:50:10 +03:00
parent d4451d753c
commit 21d8e91756
165 changed files with 318 additions and 40948 deletions

View file

@ -33,8 +33,6 @@ type Config struct {
Channels PublicChannelResolver
Privacy AnonymousPrivacyResolver
Photos ProfilePhotoResolver
UniqueGifts UniqueStarGiftResolver
GiftWithdrawals StarGiftWithdrawalResolver
ModerationAppeals ModerationAppealResolver
// TelegramLogin is the optional OIDC/Login HTTP adapter. Public Web owns
// the listener so discovery/auth/token and public links share the exact
@ -69,15 +67,6 @@ type ProfilePhotoResolver interface {
GetFile(ctx context.Context, req domain.FileDownloadRequest) (domain.FileChunk, bool, error)
}
type UniqueStarGiftResolver interface {
UniqueBySlug(ctx context.Context, slug string) (domain.UniqueStarGift, bool, error)
}
type StarGiftWithdrawalResolver interface {
ResolveWithdrawal(ctx context.Context, providerRequestID string) (domain.StarGiftWithdrawal, bool, error)
CompleteWithdrawal(ctx context.Context, providerRequestID string, date int) (domain.StarGiftWithdrawal, error)
}
type ModerationAppealResolver interface {
ResolveAppealLink(ctx context.Context, token string, now time.Time) (domain.ModerationAppealLink, bool, error)
Appeal(ctx context.Context, appealID int64) (domain.ModerationAppeal, bool, error)
@ -186,37 +175,30 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
publicHost = u.Host
}
h := &handler{
stickerSets: cfg.StickerSets,
users: cfg.Users,
channels: cfg.Channels,
privacy: cfg.Privacy,
photos: cfg.Photos,
uniqueGifts: cfg.UniqueGifts,
giftWithdrawals: cfg.GiftWithdrawals,
appeals: cfg.ModerationAppeals,
publicBaseURL: cfg.PublicBaseURL,
publicHost: publicHost,
appScheme: cfg.AppScheme,
appLinks: appLinks,
webBaseURL: cfg.WebBaseURL,
appName: cfg.AppName,
downloadURL: cfg.DownloadURL,
logger: logger,
stickerSets: cfg.StickerSets,
users: cfg.Users,
channels: cfg.Channels,
privacy: cfg.Privacy,
photos: cfg.Photos,
appeals: cfg.ModerationAppeals,
publicBaseURL: cfg.PublicBaseURL,
publicHost: publicHost,
appScheme: cfg.AppScheme,
appLinks: appLinks,
webBaseURL: cfg.WebBaseURL,
appName: cfg.AppName,
downloadURL: cfg.DownloadURL,
logger: logger,
}
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", h.healthz)
mux.HandleFunc("GET /_public/assets/logo.png", h.brandLogo)
mux.HandleFunc("GET /_public/assets/fonts/{file}", h.brandFont)
mux.HandleFunc("GET /payments/dev-stars", h.devStarsCheckout)
mux.HandleFunc("GET /_public/avatar/{username}/{photoID}", h.publicAvatar)
mux.HandleFunc("GET /_public/invite-avatar/{hash}/{photoID}", h.publicInviteAvatar)
mux.HandleFunc("GET /addstickers/{shortName}", h.addStickers)
mux.HandleFunc("GET /addemoji/{shortName}", h.addEmoji)
mux.HandleFunc("GET /addlist/{slug}", h.addList)
mux.HandleFunc("GET /nft/{slug}", h.uniqueGift)
mux.HandleFunc("GET /nft/{slug}/{$}", h.uniqueGift)
mux.HandleFunc("GET /gift-withdrawal/{requestID}", h.starGiftWithdrawal)
mux.HandleFunc("POST /gift-withdrawal/{requestID}", h.completeStarGiftWithdrawal)
if cfg.ModerationAppeals != nil {
mux.HandleFunc("GET /appeal/{token}", h.moderationAppeal)
mux.HandleFunc("POST /appeal/{token}", h.moderationAppeal)
@ -238,22 +220,20 @@ func newHandler(cfg Config, logger *zap.Logger) (http.Handler, error) {
}
type handler struct {
stickerSets StickerSetResolver
users UsernameResolver
channels PublicChannelResolver
privacy AnonymousPrivacyResolver
photos ProfilePhotoResolver
uniqueGifts UniqueStarGiftResolver
giftWithdrawals StarGiftWithdrawalResolver
appeals ModerationAppealResolver
publicBaseURL string
publicHost string
appScheme string
appLinks links.AppLinkBuilder
webBaseURL string
appName string
downloadURL string
logger *zap.Logger
stickerSets StickerSetResolver
users UsernameResolver
channels PublicChannelResolver
privacy AnonymousPrivacyResolver
photos ProfilePhotoResolver
appeals ModerationAppealResolver
publicBaseURL string
publicHost string
appScheme string
appLinks links.AppLinkBuilder
webBaseURL string
appName string
downloadURL string
logger *zap.Logger
}
type moderationAppealPage struct {
@ -268,43 +248,6 @@ type moderationAppealPage struct {
CanSubmit bool
}
type devStarsCheckoutPage struct {
AppName string
FormID string
}
var devStarsCheckoutTemplate = template.Must(template.New("dev-stars-checkout").Parse(`<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="referrer" content="no-referrer"><meta name="robots" content="noindex,nofollow">
<title>Dev Stars checkout · {{.AppName}}</title><style>
body{font:16px/1.5 system-ui,sans-serif;background:#f4f6f8;color:#17212b;margin:0;padding:24px}.card{max-width:520px;margin:9vh auto;background:#fff;border-radius:16px;padding:28px;box-shadow:0 8px 32px #0002}h1{margin-top:0}.note{color:#53606d}.status{min-height:24px;color:#b42318}button{width:100%;border:0;border-radius:10px;padding:13px 18px;background:#2481cc;color:#fff;font:inherit;font-weight:650;cursor:pointer}button:disabled{opacity:.55;cursor:default}
</style></head><body><main class="card"><h1>Complete dev purchase</h1>
<p>This is a local telesrv test checkout. No card, Google Play, App Store, or external payment provider will be charged.</p>
<p class="note">The package and fiat amount shown by the client are bound to form {{.FormID}}.</p>
<button id="complete" type="button">Complete test purchase</button><p id="status" class="status" role="status"></p>
</main><script>
(() => { const button=document.getElementById('complete'), status=document.getElementById('status');
button.addEventListener('click', () => { const proxy=window.TelegramWebviewProxy;
if(!proxy || typeof proxy.postEvent !== 'function'){status.textContent='Open this checkout inside Telegram.';return;}
button.disabled=true;status.textContent='Submitting';
proxy.postEvent('payment_form_submit', JSON.stringify({title:'telesrv dev payment',credentials:{type:'telesrv_dev',form_id:'{{.FormID}}'}}));
}); })();
</script></body></html>`))
func (h *handler) devStarsCheckout(w http.ResponseWriter, r *http.Request) {
raw := strings.TrimSpace(r.URL.Query().Get("form_id"))
formID, err := strconv.ParseInt(raw, 10, 64)
if err != nil || formID == 0 || raw != strconv.FormatInt(formID, 10) {
http.NotFound(w, r)
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := devStarsCheckoutTemplate.Execute(w, devStarsCheckoutPage{AppName: h.appName, FormID: raw}); err != nil {
h.logger.Warn("render dev Stars checkout failed", zap.Error(err))
}
}
var moderationAppealTemplate = template.Must(template.New("moderation-appeal").Parse(`<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="referrer" content="no-referrer"><title>Moderation appeal · {{.AppName}}</title><style>
@ -389,65 +332,6 @@ func (h *handler) moderationAppeal(w http.ResponseWriter, r *http.Request) {
}
}
type starGiftWithdrawalPage struct {
AppName string
Title string
Slug string
Status string
OwnerAddress string
GiftAddress string
ExpiresAt string
CanComplete bool
}
var starGiftWithdrawalTemplate = template.Must(template.New("star-gift-withdrawal").Parse(`<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{.Title}} · {{.AppName}}</title><style>
body{font:16px/1.5 system-ui,sans-serif;background:#f4f6f8;color:#17212b;margin:0;padding:32px}.card{max-width:560px;margin:8vh auto;background:#fff;border-radius:16px;padding:28px;box-shadow:0 8px 32px #0002}h1{margin-top:0}.meta{overflow-wrap:anywhere;color:#53606d}button{border:0;border-radius:10px;padding:12px 18px;background:#2481cc;color:#fff;font-weight:600;cursor:pointer}.done{color:#18864b;font-weight:600}
</style></head><body><main class="card"><h1>{{.Title}}</h1><p class="meta">Collectible: {{.Slug}}</p>
{{if .CanComplete}}<p>This export is handled only by {{.AppName}}'s internal ledger. No external blockchain or wallet is contacted.</p><form method="post"><button type="submit">Complete local export</button></form><p class="meta">Expires: {{.ExpiresAt}}</p>{{else}}<p class="done">Status: {{.Status}}</p>{{if .OwnerAddress}}<p class="meta">Owner address: {{.OwnerAddress}}</p><p class="meta">Gift address: {{.GiftAddress}}</p>{{end}}{{end}}
</main></body></html>`))
func (h *handler) starGiftWithdrawal(w http.ResponseWriter, r *http.Request) {
h.renderStarGiftWithdrawal(w, r, false)
}
func (h *handler) completeStarGiftWithdrawal(w http.ResponseWriter, r *http.Request) {
h.renderStarGiftWithdrawal(w, r, true)
}
func (h *handler) renderStarGiftWithdrawal(w http.ResponseWriter, r *http.Request, complete bool) {
requestID := strings.TrimSpace(r.PathValue("requestID"))
if h.giftWithdrawals == nil || requestID == "" || len(requestID) > 256 {
http.NotFound(w, r)
return
}
var withdrawal domain.StarGiftWithdrawal
var found bool
var err error
if complete {
withdrawal, err = h.giftWithdrawals.CompleteWithdrawal(r.Context(), requestID, int(time.Now().Unix()))
found = err == nil
} else {
withdrawal, found, err = h.giftWithdrawals.ResolveWithdrawal(r.Context(), requestID)
}
if err != nil || !found {
http.NotFound(w, r)
return
}
page := starGiftWithdrawalPage{AppName: h.appName, Title: withdrawal.Gift.Title, Slug: withdrawal.Gift.Slug,
Status: withdrawal.Status, OwnerAddress: withdrawal.Gift.OwnerAddress, GiftAddress: withdrawal.Gift.GiftAddress,
ExpiresAt: time.Unix(int64(withdrawal.ExpiresAt), 0).UTC().Format(time.RFC3339),
CanComplete: withdrawal.Status == "pending" && withdrawal.ExpiresAt > int(time.Now().Unix())}
if page.Title == "" {
page.Title = "Collectible gift export"
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := starGiftWithdrawalTemplate.Execute(w, page); err != nil {
h.logger.Warn("render star gift withdrawal", zap.Error(err))
}
}
func (h *handler) healthz(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("ok\n"))
@ -485,63 +369,6 @@ func (h *handler) addList(w http.ResponseWriter, r *http.Request) {
}
}
func (h *handler) uniqueGift(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("slug")
if h.uniqueGifts == nil || !validStarGiftSlugPath(slug) {
http.NotFound(w, r)
return
}
unique, found, err := h.uniqueGifts.UniqueBySlug(r.Context(), slug)
if err != nil {
h.logger.Error("Public unique star gift lookup failed", zap.String("slug", slug), zap.Error(err))
http.Error(w, "collectible gift lookup failed", http.StatusInternalServerError)
return
}
if !found {
http.NotFound(w, r)
return
}
canonicalSlug := unique.Slug
if unique.ID <= 0 || unique.GiftID <= 0 || unique.Num <= 0 ||
!validStarGiftSlugPath(canonicalSlug) || !strings.EqualFold(slug, canonicalSlug) ||
!utf8.ValidString(unique.Title) || utf8.RuneCountInString(unique.Title) > domain.MaxStarGiftTitleRunes {
h.logger.Error("Public unique star gift resolver returned invalid aggregate",
zap.String("requested_slug", slug), zap.String("resolved_slug", canonicalSlug),
zap.Int64("unique_id", unique.ID), zap.Int64("gift_id", unique.GiftID), zap.Int("num", unique.Num))
http.Error(w, "collectible gift lookup failed", http.StatusInternalServerError)
return
}
if slug != canonicalSlug || strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, h.publicURL("nft", canonicalSlug), http.StatusPermanentRedirect)
return
}
title := strings.TrimSpace(unique.Title)
if title == "" {
title = "Collectible gift"
}
subtitle := fmt.Sprintf("Collectible #%d", unique.Num)
if unique.AvailabilityIssued > 0 && unique.AvailabilityTotal >= unique.AvailabilityIssued {
subtitle += fmt.Sprintf(" · %s/%s issued", groupedDecimal(unique.AvailabilityIssued), groupedDecimal(unique.AvailabilityTotal))
}
app := h.appURL("nft", canonicalSlug)
data := pageData{
AppName: h.appName,
Title: title,
KindLabel: "collectible gift",
Subtitle: subtitle,
Description: "This collectible was created from a gift on " + h.appName + ". Open it in the app to view its current details.",
CanonicalURL: h.publicURL("nft", canonicalSlug),
AppURL: template.URL(app),
LegacyTgURL: template.URL(legacyTgURL("nft", "slug", canonicalSlug)),
}
data.AppURLJS = template.JS(strconv.Quote(app))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=60, must-revalidate")
if err := landingTemplate.Execute(w, data); err != nil {
h.logger.Error("Render public unique star gift page failed", zap.String("slug", canonicalSlug), zap.Error(err))
}
}
func (h *handler) usernameLink(w http.ResponseWriter, r *http.Request) {
raw := strings.TrimSpace(r.PathValue("username"))
if strings.HasPrefix(raw, "+") {
@ -1274,23 +1101,6 @@ func validSlugPath(slug string) bool {
return links.ValidChatlistSlug(slug)
}
func validStarGiftSlugPath(slug string) bool {
if slug == "" || len(slug) > domain.MaxStarGiftSlugBytes {
return false
}
for _, r := range slug {
switch {
case r >= 'a' && r <= 'z':
case r >= 'A' && r <= 'Z':
case r >= '0' && r <= '9':
case r == '.' || r == '_' || r == '-':
default:
return false
}
}
return true
}
func validUsernamePath(username string) bool {
return domain.ValidCollectibleUsername(domain.NormalizeUsername(username))
}

View file

@ -45,44 +45,6 @@ func newTestHandlerWithPublicPeers(
return h
}
func TestDevStarsCheckoutEmitsFormBoundTelegramCredentials(t *testing.T) {
h := newTestHandler(t, fakeResolver{}, "https://links.example.test")
for _, target := range []string{
"/payments/dev-stars",
"/payments/dev-stars?form_id=0",
"/payments/dev-stars?form_id=01",
"/payments/dev-stars?form_id=not-a-number",
} {
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, target, nil))
if rr.Code != http.StatusNotFound {
t.Fatalf("GET %s status = %d, want 404", target, rr.Code)
}
}
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/payments/dev-stars?form_id=-70001", nil))
body := rr.Body.String()
if rr.Code != http.StatusOK || rr.Header().Get("Cache-Control") != "no-store" ||
!strings.Contains(body, "payment_form_submit") ||
!strings.Contains(body, "type:'telesrv_dev',form_id:'-70001'") ||
!strings.Contains(body, "No card, Google Play, App Store, or external payment provider will be charged") {
t.Fatalf("dev checkout status=%d headers=%v body=%q", rr.Code, rr.Header(), body)
}
}
type fakeGiftWithdrawals struct {
value domain.StarGiftWithdrawal
found bool
completeCalls int
}
type fakeUniqueGifts struct {
bySlug map[string]domain.UniqueStarGift
err error
calls int
}
type fakeModerationAppeals struct {
link domain.ModerationAppealLink
found bool
@ -224,146 +186,6 @@ func TestHandlerModerationAppealFailsClosed(t *testing.T) {
}
}
func (f *fakeUniqueGifts) UniqueBySlug(_ context.Context, slug string) (domain.UniqueStarGift, bool, error) {
f.calls++
if f.err != nil {
return domain.UniqueStarGift{}, false, f.err
}
value, ok := f.bySlug[strings.ToLower(slug)]
return value, ok, nil
}
func TestHandlerServesUniqueGiftLandingPage(t *testing.T) {
const slug = "official-5895603153683874485-7"
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
slug: {
ID: 7001, GiftID: 5895603153683874485, Title: "Official Gift", Slug: slug, Num: 7,
AvailabilityIssued: 7, AvailabilityTotal: 1000,
},
}}
handler, err := NewHandler(Config{
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "http://127.0.0.1:2401",
})
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/"+slug, nil))
if rr.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
}
for _, want := range []string{
"Official Gift", "Collectible #7", "7/1 000 issued",
"http://127.0.0.1:2401/nft/" + slug,
"telesrv://127.0.0.1:2401/nft/" + slug,
"tg://nft?slug=" + slug,
"Open it in the app to view its current details.",
} {
if !strings.Contains(rr.Body.String(), want) {
t.Fatalf("body missing %q:\n%s", want, rr.Body.String())
}
}
if strings.Contains(rr.Body.String(), `window.location.href = "tg://`) {
t.Fatalf("landing page must not auto-open tg:// and steal official Telegram:\n%s", rr.Body.String())
}
if got := rr.Header().Get("Cache-Control"); got != "public, max-age=60, must-revalidate" {
t.Fatalf("Cache-Control = %q", got)
}
}
func TestHandlerCanonicalizesUniqueGiftSlug(t *testing.T) {
const canonical = "Official-Gift-7"
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
strings.ToLower(canonical): {ID: 7, GiftID: 70, Slug: canonical, Num: 7},
}}
handler, err := NewHandler(Config{
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "https://telesrv.net",
})
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
for _, path := range []string{"/nft/official-gift-7", "/nft/" + canonical + "/"} {
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil))
if rr.Code != http.StatusPermanentRedirect || rr.Header().Get("Location") != "https://telesrv.net/nft/"+canonical {
t.Fatalf("%s status=%d location=%q", path, rr.Code, rr.Header().Get("Location"))
}
}
}
func TestHandlerRejectsInvalidMissingAndBrokenUniqueGift(t *testing.T) {
resolver := &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
"broken-1": {ID: 1, GiftID: 2, Slug: "other-1", Num: 1},
}}
handler, err := NewHandler(Config{
StickerSets: fakeResolver{}, UniqueGifts: resolver, PublicBaseURL: "https://telesrv.net",
})
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
for _, path := range []string{
"/nft/missing-1", "/nft/bad!slug", "/nft/%E4%B8%AD%E6%96%87", "/nft/" + strings.Repeat("x", domain.MaxStarGiftSlugBytes+1),
} {
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, path, nil))
if rr.Code != http.StatusNotFound {
t.Fatalf("%s status=%d, want 404", path, rr.Code)
}
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/broken-1", nil))
if rr.Code != http.StatusInternalServerError {
t.Fatalf("broken aggregate status=%d, want 500", rr.Code)
}
resolver.err = errors.New("lookup failed")
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/nft/error-1", nil))
if rr.Code != http.StatusInternalServerError {
t.Fatalf("lookup error status=%d, want 500", rr.Code)
}
}
func (f *fakeGiftWithdrawals) ResolveWithdrawal(context.Context, string) (domain.StarGiftWithdrawal, bool, error) {
return f.value, f.found, nil
}
func (f *fakeGiftWithdrawals) CompleteWithdrawal(_ context.Context, _ string, _ int) (domain.StarGiftWithdrawal, error) {
f.completeCalls++
f.value.Status = "completed"
f.value.Gift.OwnerAddress = "telesrv-owner:test"
f.value.Gift.GiftAddress = "telesrv-gift:test"
return f.value, nil
}
func TestHandlerCompletesLocalStarGiftWithdrawal(t *testing.T) {
resolver := &fakeGiftWithdrawals{found: true, value: domain.StarGiftWithdrawal{
ProviderRequestID: "safe-token", Status: "pending", ExpiresAt: int(time.Now().Add(time.Minute).Unix()),
Gift: domain.UniqueStarGift{Title: `<script>alert("x")</script>`, Slug: "gift-1"},
}}
handler, err := NewHandler(Config{StickerSets: fakeResolver{}, GiftWithdrawals: resolver,
PublicBaseURL: "https://telesrv.net", AppName: "telesrv"})
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/gift-withdrawal/safe-token", nil))
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "Complete local export") ||
strings.Contains(rr.Body.String(), `<script>alert("x")</script>`) {
t.Fatalf("withdrawal GET status=%d body=%s", rr.Code, rr.Body.String())
}
if csp := rr.Header().Get("Content-Security-Policy"); !strings.Contains(csp, "form-action 'self'") {
t.Fatalf("withdrawal CSP does not allow its same-origin POST form: %q", csp)
}
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodPost, "/gift-withdrawal/safe-token", strings.NewReader("")))
if rr.Code != http.StatusOK || resolver.completeCalls != 1 || !strings.Contains(rr.Body.String(), "Status: completed") ||
!strings.Contains(rr.Body.String(), "telesrv-owner:test") || !strings.Contains(rr.Body.String(), "telesrv-gift:test") {
t.Fatalf("withdrawal POST calls=%d status=%d body=%s", resolver.completeCalls, rr.Code, rr.Body.String())
}
}
func TestHandlerServesStickerSetLandingPage(t *testing.T) {
resolver := fakeResolver{
"fresh_pack": {
@ -501,10 +323,7 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
"stickers_pack": {ShortName: "stickers_pack", Title: "Stickers", Kind: domain.StickerSetKindStickers},
"emoji_pack": {ShortName: "emoji_pack", Title: "Emoji", Kind: domain.StickerSetKindEmoji, Emojis: true},
},
Users: fakeUsers{"alice": {ID: 2001, Username: "Alice", FirstName: "Alice"}},
UniqueGifts: &fakeUniqueGifts{bySlug: map[string]domain.UniqueStarGift{
"gift-1": {ID: 1, GiftID: 10, Slug: "gift-1", Num: 1},
}},
Users: fakeUsers{"alice": {ID: 2001, Username: "Alice", FirstName: "Alice"}},
PublicBaseURL: "https://links.example.test",
AppScheme: "example-chat",
AppLinkBase: "owpg://tenant.example.test",
@ -540,7 +359,6 @@ func TestHandlerUsesConfiguredClientLinksAndBrand(t *testing.T) {
{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))