fix(telegram): support card — text_mention name, no command/dead link
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
The topic info card built the profile link as `tg://user?id=`, which clients render as dead text for a user they don't already know (the test-vs-prod difference an operator saw), and it put the raw display name through HTML — so a name starting with "/" was auto-detected as a tappable bot command that fired when tapped. Render the card with message entities instead: the display name is covered by a `text_mention` entity (the reliable way to mention a username-less user the bot has already seen — it messaged the bot), which links the profile AND, because the name sits inside an entity, suppresses the "/command" and "@mention" auto-detection on it. Drop the HTML parse mode and the tg:// link. Entity offsets are UTF-16.
This commit is contained in:
@@ -3,11 +3,11 @@ package bot
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
|
||||
tgbot "github.com/go-telegram/bot"
|
||||
"github.com/go-telegram/bot/models"
|
||||
@@ -270,11 +270,12 @@ func (t *Bot) openSupportTopic(ctx context.Context, u *models.User) (int, error)
|
||||
return 0, fmt.Errorf("create forum topic: %w", err)
|
||||
}
|
||||
headerID := 0
|
||||
cardText, cardEntities := supportCard(u)
|
||||
header, err := t.api.SendMessage(ctx, &tgbot.SendMessageParams{
|
||||
ChatID: t.supportChatID,
|
||||
MessageThreadID: topic.MessageThreadID,
|
||||
Text: supportCardText(u),
|
||||
ParseMode: models.ParseModeHTML,
|
||||
Text: cardText,
|
||||
Entities: cardEntities,
|
||||
ReplyMarkup: supportCardMarkup(u.ID, false),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -396,16 +397,22 @@ func supportTopicName(u *models.User) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// supportCardText renders the info card describing the user. User-controlled fields
|
||||
// are HTML-escaped because the card uses HTML parse mode for the profile link.
|
||||
func supportCardText(u *models.User) string {
|
||||
name := html.EscapeString(strings.TrimSpace(u.FirstName + " " + u.LastName))
|
||||
// supportCard renders the topic info card describing the user and the message
|
||||
// entities for it. The display name is covered by a text_mention entity: it links to
|
||||
// the user's profile (the reliable way to mention a user who has no public username —
|
||||
// the bot has seen them, since they messaged it) and, because the name sits inside an
|
||||
// entity, Telegram does not auto-detect a leading "/" as a bot command or a stray
|
||||
// "@handle" inside the name as a mention. The remaining lines are plain text; a public
|
||||
// @username, when present, is left for Telegram to auto-link. Entity offsets are in
|
||||
// UTF-16 code units, as the Bot API requires; the name is at offset 0.
|
||||
func supportCard(u *models.User) (string, []models.MessageEntity) {
|
||||
name := strings.TrimSpace(u.FirstName + " " + u.LastName)
|
||||
if name == "" {
|
||||
name = "—"
|
||||
name = "user " + strconv.FormatInt(u.ID, 10)
|
||||
}
|
||||
username := "—"
|
||||
if u.Username != "" {
|
||||
username = "@" + html.EscapeString(u.Username)
|
||||
username = "@" + u.Username
|
||||
}
|
||||
lang := u.LanguageCode
|
||||
if lang == "" {
|
||||
@@ -416,12 +423,17 @@ func supportCardText(u *models.User) string {
|
||||
premium = "да"
|
||||
}
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "<b>%s</b>\n", name)
|
||||
fmt.Fprintf(&b, "Username: %s\n", username)
|
||||
fmt.Fprintf(&b, "ID: <code>%d</code>\n", u.ID)
|
||||
fmt.Fprintf(&b, "Язык: %s · Premium: %s\n", html.EscapeString(lang), premium)
|
||||
fmt.Fprintf(&b, `<a href="tg://user?id=%d">Открыть профиль</a>`, u.ID)
|
||||
return b.String()
|
||||
b.WriteString(name)
|
||||
fmt.Fprintf(&b, "\nUsername: %s", username)
|
||||
fmt.Fprintf(&b, "\nID: %d", u.ID)
|
||||
fmt.Fprintf(&b, "\nЯзык: %s · Premium: %s", lang, premium)
|
||||
entities := []models.MessageEntity{{
|
||||
Type: models.MessageEntityTypeTextMention,
|
||||
Offset: 0,
|
||||
Length: len(utf16.Encode([]rune(name))),
|
||||
User: &models.User{ID: u.ID},
|
||||
}}
|
||||
return b.String(), entities
|
||||
}
|
||||
|
||||
// isTopicMissingErr reports whether err is Telegram's deleted/absent forum-topic
|
||||
|
||||
Reference in New Issue
Block a user