{{.Title}}
This page opens the app so you can preview and install the set. Files are still fetched by the app through MTProto.
package stickerlinks import ( "context" "errors" "fmt" "html/template" "net" "net/http" "net/url" "strconv" "strings" "time" "go.uber.org/zap" "telesrv/internal/domain" ) const defaultPublicBaseURL = "https://telesrv.net" type Config struct { Addr string PublicBaseURL string } type Resolver interface { ResolveStickerSet(ctx context.Context, ref domain.StickerSetRef) (domain.StickerSet, []domain.Document, bool, error) } func Start(ctx context.Context, cfg Config, resolver Resolver, logger *zap.Logger) (*http.Server, error) { addr := strings.TrimSpace(cfg.Addr) if addr == "" { return nil, nil } if resolver == nil { return nil, fmt.Errorf("sticker links resolver is nil") } if logger == nil { logger = zap.NewNop() } handler := NewHandler(resolver, cfg.PublicBaseURL) srv := &http.Server{ Addr: addr, Handler: handler, ReadHeaderTimeout: 5 * time.Second, } ln, err := net.Listen("tcp", addr) if err != nil { return nil, err } go func() { logger.Info("Sticker link Web endpoint enabled", zap.String("addr", addr), zap.String("public_base_url", normalizePublicBaseURL(cfg.PublicBaseURL))) if err := srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Warn("Sticker link Web endpoint exited", zap.Error(err)) } }() go func() { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = srv.Shutdown(shutdownCtx) }() return srv, nil } func NewHandler(resolver Resolver, publicBaseURL string) http.Handler { h := &handler{ resolver: resolver, publicBaseURL: normalizePublicBaseURL(publicBaseURL), } mux := http.NewServeMux() mux.HandleFunc("GET /healthz", h.healthz) mux.HandleFunc("GET /addstickers/{shortName}", h.addStickers) mux.HandleFunc("GET /addemoji/{shortName}", h.addEmoji) return mux } type handler struct { resolver Resolver publicBaseURL string } func (h *handler) healthz(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("ok\n")) } func (h *handler) addStickers(w http.ResponseWriter, r *http.Request) { h.serveSet(w, r, "addstickers") } func (h *handler) addEmoji(w http.ResponseWriter, r *http.Request) { h.serveSet(w, r, "addemoji") } func (h *handler) serveSet(w http.ResponseWriter, r *http.Request, pathKind string) { shortName := strings.TrimSpace(r.PathValue("shortName")) if !validShortNamePath(shortName) { http.NotFound(w, r) return } set, docs, found, err := h.resolver.ResolveStickerSet(r.Context(), domain.StickerSetRef{ Kind: domain.StickerSetRefByShortName, ShortName: shortName, }) if err != nil { http.Error(w, "sticker set lookup failed", http.StatusInternalServerError) return } if !found || set.Deleted { http.NotFound(w, r) return } canonicalKind := linkKind(set) if canonicalKind != pathKind { http.Redirect(w, r, h.setURL(canonicalKind, set.ShortName), http.StatusPermanentRedirect) return } count := set.Count if count == 0 { count = len(docs) } app := appURL(canonicalKind, set.ShortName) data := pageData{ Title: fallbackTitle(set), ShortName: set.ShortName, Count: count, KindLabel: kindLabel(set), ItemNoun: itemNoun(set, count), CanonicalURL: h.setURL(canonicalKind, set.ShortName), AppURL: template.URL(app), LegacyTgURL: template.URL(legacyTgURL(canonicalKind, set.ShortName)), } 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") if err := landingTemplate.Execute(w, data); err != nil { http.Error(w, "render sticker set page failed", http.StatusInternalServerError) } } func (h *handler) setURL(kind, shortName string) string { return h.publicBaseURL + "/" + kind + "/" + url.PathEscape(shortName) } func normalizePublicBaseURL(raw string) string { raw = strings.TrimRight(strings.TrimSpace(raw), "/") if raw == "" { return defaultPublicBaseURL } u, err := url.Parse(raw) if err != nil || u.Scheme == "" || u.Host == "" { return defaultPublicBaseURL } u.Path = strings.TrimRight(u.Path, "/") u.RawQuery = "" u.Fragment = "" return strings.TrimRight(u.String(), "/") } func validShortNamePath(shortName string) bool { if shortName == "" || len(shortName) > 64 { return false } for _, r := range shortName { switch { case r >= 'a' && r <= 'z': case r >= 'A' && r <= 'Z': case r >= '0' && r <= '9': case r == '_' || r == '.': default: return false } } return true } func linkKind(set domain.StickerSet) string { if set.Kind == domain.StickerSetKindEmoji || set.Emojis { return "addemoji" } return "addstickers" } func fallbackTitle(set domain.StickerSet) string { if title := strings.TrimSpace(set.Title); title != "" { return title } return set.ShortName } func kindLabel(set domain.StickerSet) string { switch { case set.Kind == domain.StickerSetKindEmoji || set.Emojis: return "custom emoji set" case set.Kind == domain.StickerSetKindMasks || set.Masks: return "mask set" default: return "sticker set" } } func itemNoun(set domain.StickerSet, count int) string { if set.Kind == domain.StickerSetKindEmoji || set.Emojis { if count == 1 { return "custom emoji" } return "custom emoji" } if count == 1 { return "sticker" } return "stickers" } func appURL(kind, shortName string) string { return "telesrv://" + kind + "?set=" + url.QueryEscape(shortName) } func legacyTgURL(kind, shortName string) string { return "tg://" + kind + "?set=" + url.QueryEscape(shortName) } type pageData struct { Title string ShortName string Count int KindLabel string ItemNoun string CanonicalURL string AppURL template.URL LegacyTgURL template.URL AppURLJS template.JS } var landingTemplate = template.Must(template.New("landing").Parse(`
This page opens the app so you can preview and install the set. Files are still fetched by the app through MTProto.