Files
remark42/backend/app/notify/telegram.go
T
d01b738741 Implement function to prune string keeping HTML closing tags (#1870)
* Implement function to prune string keeping HTML closing tags

Fixes #1587

* change const name

remove unneeded comment

* move pruneHTML to separated file

* move const back to telegram.go

* Add unit tests for string array manipulation and HTML pruning

Introduce comprehensive test cases for stringArr methods (Push, Pop, Unshift, Shift, String) to ensure correct behavior and state management. Additionally, add tests for HTML pruning functions (pruneHTML, pruneStringToWord) to validate handling of length constraints and formatting scenarios.

* Improve behavior

* Fix pruneHTML to count visible text only, add parent text pruning

- Fix bug where HTML tags were counted toward the character limit
  instead of only visible text content
- Add pruning for parent comment text in Telegram notifications
- Simplify pruneStringToWord using strings.LastIndex
- Remove unused stringArr type and its tests
- Consolidate and simplify test cases

---------

Co-authored-by: Umputun <umputun@gmail.com>
Co-authored-by: Dmitry Verkhoturov <paskal.07@gmail.com>
2025-12-04 11:11:10 -06:00

118 lines
3.7 KiB
Go

package notify
import (
"context"
"fmt"
"time"
log "github.com/go-pkgz/lgr"
ntf "github.com/go-pkgz/notify"
"github.com/hashicorp/go-multierror"
)
const commentTextLengthLimit = 100
// TelegramParams contain settings for telegram notifications
type TelegramParams struct {
AdminChannelID string // unique identifier for the target chat or username of the target channel (in the format @channelusername)
Token string // token for telegram bot API interactions
Timeout time.Duration // http client timeout
UserNotifications bool // flag which enables user notifications
ErrorMsg, SuccessMsg string // messages for successful and unsuccessful subscription requests to bot
}
// Telegram implements notify.Destination for telegram
type Telegram struct {
*ntf.Telegram
AdminChannelID string // unique identifier for the target chat or username of the target channel (in the format @channelusername)
UserNotifications bool // flag which enables user notifications
}
// NewTelegram makes telegram bot for notifications
func NewTelegram(params TelegramParams) (*Telegram, error) {
client, err := ntf.NewTelegram(ntf.TelegramParams{
Token: params.Token,
Timeout: params.Timeout,
ErrorMsg: params.ErrorMsg,
SuccessMsg: params.SuccessMsg,
})
if err != nil {
return nil, err
}
return &Telegram{Telegram: client, AdminChannelID: params.AdminChannelID, UserNotifications: params.UserNotifications}, nil
}
// Send to telegram recipients
func (t *Telegram) Send(ctx context.Context, req Request) error {
log.Printf("[DEBUG] send telegram notification for comment ID %s", req.Comment.ID)
result := new(multierror.Error)
msg := t.buildMessage(req)
if t.AdminChannelID != "" {
err := t.Telegram.Send(ctx, fmt.Sprintf("telegram:%s?parseMode=HTML", t.AdminChannelID), msg)
if err != nil {
result = multierror.Append(result,
fmt.Errorf("problem sending admin telegram notification about comment ID %s to %s: %w",
req.Comment.ID, t.AdminChannelID, err,
),
)
}
}
if t.UserNotifications {
for _, user := range req.Telegrams {
err := t.Telegram.Send(ctx, fmt.Sprintf("telegram:%s?parseMode=HTML", user), msg)
if err != nil {
result = multierror.Append(result,
fmt.Errorf("problem sending user telegram notification about comment ID %s to %q: %w",
req.Comment.ID, user, err,
),
)
}
}
}
return result.ErrorOrNil()
}
// buildMessage generates message for generic notification about new comment
func (t *Telegram) buildMessage(req Request) string {
commentURLPrefix := req.Comment.Locator.URL + uiNav
msg := fmt.Sprintf(`<a href=%q>%s</a>`, commentURLPrefix+req.Comment.ID, ntf.EscapeTelegramText(req.Comment.User.Name))
if req.Comment.ParentID != "" {
msg += fmt.Sprintf(" -> <a href=%q>%s</a>", commentURLPrefix+req.parent.ID, ntf.EscapeTelegramText(req.parent.User.Name))
}
msg += fmt.Sprintf("\n\n%s", pruneHTML(ntf.TelegramSupportedHTML(req.Comment.Text), commentTextLengthLimit))
if req.Comment.ParentID != "" {
msg += fmt.Sprintf("\n\n\"<i>%s</i>\"", pruneHTML(ntf.TelegramSupportedHTML(req.parent.Text), commentTextLengthLimit))
}
if req.Comment.PostTitle != "" {
msg += fmt.Sprintf("\n\n↦ <a href=%q>%s</a>", req.Comment.Locator.URL, ntf.EscapeTelegramText(req.Comment.PostTitle))
}
return msg
}
// SendVerification is not needed for telegram
func (t *Telegram) SendVerification(_ context.Context, _ VerificationRequest) error {
return nil
}
func (t *Telegram) String() string {
result := t.Telegram.String()
if t.AdminChannelID != "" {
result += " with admin notifications to " + t.AdminChannelID
}
if t.UserNotifications {
result += " with user notifications enabled"
}
return result
}