diff --git a/backend/app/notify/telegram.go b/backend/app/notify/telegram.go index 54f57877..9a5f923e 100644 --- a/backend/app/notify/telegram.go +++ b/backend/app/notify/telegram.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "golang.org/x/net/html" "io" "net/http" neturl "net/url" @@ -171,11 +172,12 @@ func buildMessage(req Request) ([]byte, error) { // returns HTML with only tags allowed in Telegram HTML message payload, also trims ending newlines // https://core.telegram.org/bots/api#html-style func telegramSupportedHTML(htmlText string) string { + adjustedHTMLText := adjustHTMLTags(htmlText) p := bluemonday.NewPolicy() p.AllowElements("b", "strong", "i", "em", "u", "ins", "s", "strike", "del", "a", "code", "pre") p.AllowAttrs("href").OnElements("a") p.AllowAttrs("class").OnElements("code") - return strings.TrimRight(p.Sanitize(htmlText), "\n") + return strings.TrimRight(p.Sanitize(adjustedHTMLText), "\n") } // returns text sanitized of symbols not allowed inside other HTML tags in Telegram HTML message payload @@ -188,6 +190,42 @@ func escapeTelegramText(text string) string { return text } +// telegram not allow h1-h6 tags +// replace these tags with a combination of and for visual distinction +func adjustHTMLTags(htmlText string) string { + buff := strings.Builder{} + tokenizer := html.NewTokenizer(strings.NewReader(htmlText)) + for { + if tokenizer.Next() == html.ErrorToken { + return buff.String() + } + token := tokenizer.Token() + switch token.Type { + case html.StartTagToken, html.EndTagToken: + switch token.Data { + case "h1", "h2", "h3": + if token.Type == html.StartTagToken { + buff.WriteString("") + } + if token.Type == html.EndTagToken { + buff.WriteString("") + } + case "h4", "h5", "h6": + if token.Type == html.StartTagToken { + buff.WriteString("") + } + if token.Type == html.EndTagToken { + buff.WriteString("") + } + default: + buff.WriteString(token.String()) + } + default: + buff.WriteString(token.String()) + } + } +} + // SendVerification is not needed for telegram func (t *Telegram) SendVerification(_ context.Context, _ VerificationRequest) error { return nil diff --git a/backend/app/notify/telegram_test.go b/backend/app/notify/telegram_test.go index c9e149f0..fbca73d2 100644 --- a/backend/app/notify/telegram_test.go +++ b/backend/app/notify/telegram_test.go @@ -166,6 +166,16 @@ func TestTelegram_Send(t *testing.T) { `\"\u003ci\u003esome parent text with a \u003ca href=\"http://example.org\"\u003elink\u003c/a\u003e and special text:\u0026amp; \u0026lt; \u0026gt; \u0026amp;\u003c/i\u003e\"\n\n`+ `↦ \u003ca href=\"http://example.org/\"\u003e[test title]\u003c/a\u003e","parse_mode":"HTML"}`, string(res)) + + // special case for text with h1-h6 header + ch := store.Comment{Text: "

Hello

World
", ID: "555", Locator: store.Locator{URL: "http://example.org/"}} + ch.User.Name = "from" + res, err = buildMessage(Request{Comment: ch}) + assert.NoError(t, err) + assert.Equal(t, `{"text":"\u003ca href=\"http://example.org/#remark42__comment-555\"\u003efrom\u003c/a\u003e\n\n`+ + `\u003cb\u003eHello\u003c/b\u003e\u003ci\u003e\u003cb\u003eWorld\u003c/b\u003e\u003c/i\u003e`+ + `","parse_mode":"HTML"}`, + string(res)) } func TestTelegram_SendVerification(t *testing.T) {