diff --git a/go.mod b/go.mod index aa3c824..9747700 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/ice-blockchain/eskimo v1.371.0 github.com/ice-blockchain/freezer v1.488.0 github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb - github.com/ice-blockchain/wintr v1.144.0 + github.com/ice-blockchain/wintr v1.145.0 github.com/imroc/req/v3 v3.43.7 github.com/pkg/errors v0.9.1 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 @@ -137,7 +137,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/quic-go v0.45.1 // indirect - github.com/redis/go-redis/v9 v9.5.3 // indirect + github.com/redis/go-redis/v9 v9.5.4 // indirect github.com/refraction-networking/utls v1.6.7 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect diff --git a/go.sum b/go.sum index 2036504..4664f77 100644 --- a/go.sum +++ b/go.sum @@ -288,8 +288,8 @@ github.com/ice-blockchain/freezer v1.488.0 h1:Q3XGS16eDQ4zeSTqfYLLwAt+H3TbDda9hI github.com/ice-blockchain/freezer v1.488.0/go.mod h1:bSwFbBC95BBF+/WrPJp+l9qyrGruim0pnTpAmkkWoLQ= github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb h1:8TnFP3mc7O+tc44kv2e0/TpZKnEVUaKH+UstwfBwRkk= github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb/go.mod h1:ZsQU7i3mxhgBBu43Oev7WPFbIjP4TniN/b1UPNGbrq8= -github.com/ice-blockchain/wintr v1.144.0 h1:YQE0olkPdSI6AOlw7r/j5jGI6uLciZQrvXFIkN4C4l4= -github.com/ice-blockchain/wintr v1.144.0/go.mod h1:3HAl5nodsetqQN30q3gUvsxgfq2B7F86Os/II7/5GPQ= +github.com/ice-blockchain/wintr v1.145.0 h1:ObAgnS2Mqc2tbSM1pRpmi4dGUtkD/T+lacpaq+Ri8Mw= +github.com/ice-blockchain/wintr v1.145.0/go.mod h1:CgbY1UEyIx//+tM57Hz72Jd28UOs5tpOlJOByyQhi2c= github.com/imroc/req/v3 v3.43.7 h1:dOcNb9n0X83N5/5/AOkiU+cLhzx8QFXjv5MhikazzQA= github.com/imroc/req/v3 v3.43.7/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8ToyQc2xA= github.com/ip2location/ip2location-go/v9 v9.7.0 h1:ipwl67HOWcrw+6GOChkEXcreRQR37NabqBd2ayYa4Q0= @@ -403,8 +403,8 @@ github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjC github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= -github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.5.4 h1:vOFYDKKVgrI5u++QvnMT7DksSMYg7Aw/Np4vLJLKLwY= +github.com/redis/go-redis/v9 v9.5.4/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= diff --git a/notifications/contract.go b/notifications/contract.go index ab7ca9a..8d3ebf8 100644 --- a/notifications/contract.go +++ b/notifications/contract.go @@ -73,6 +73,12 @@ const ( WeeklyStatsNotificationType NotificationType = "weekly_stats" ReplyNotificationType NotificationType = "reply" SocialsNotificationType NotificationType = "socials" + StartCommandNotificationType NotificationType = "start" +) + +const ( + StartTelegramCommand TelegramCommand = "/start" + SocialsTelegramCommand TelegramCommand = "/socials" ) var ( @@ -129,6 +135,7 @@ var ( NewReferralNotificationType, SocialsNotificationType, ReplyNotificationType, + StartCommandNotificationType, } //nolint:gochecknoglobals // It's just for more descriptive validation messages. AllNotificationDomains = map[NotificationChannel][]NotificationDomain{ @@ -160,6 +167,7 @@ type ( NotificationChannel string NotificationDomain string NotificationType string + TelegramCommand string NotificationChannels struct { NotificationChannels *users.Enum[NotificationChannel] `json:"notificationChannels,omitempty" swaggertype:"array,string" enums:"inapp,sms,email,push,push||email"` //nolint:lll // . } @@ -238,6 +246,11 @@ var ( internationalizedEmailDisplayNames = map[string]string{ "en": "ice: Decentralized Future", } + //nolint:gochecknoglobals // It's just for more descriptive commands names. + availableBotCommands = []TelegramCommand{ + StartTelegramCommand, + SocialsTelegramCommand, + } ) type ( @@ -359,6 +372,7 @@ type ( } telegramUserInfo struct { UserID string `json:"userId,omitempty"` + Username string `json:"username,omitempty"` TelegramUserID string `json:"telegramUserId,omitempty"` Language string `json:"language,omitempty"` } diff --git a/notifications/notifications.go b/notifications/notifications.go index fdd1068..075a906 100644 --- a/notifications/notifications.go +++ b/notifications/notifications.go @@ -85,6 +85,7 @@ func StartProcessor(ctx context.Context, cancel context.CancelFunc) Processor { prc.shutdown = closeAll(mbConsumer, prc.mb, prc.db, prc.pushNotificationsClient.Close, prc.freezerDB.Close) go prc.startOldSentNotificationsCleaner(ctx) go prc.startOldSentAnnouncementsCleaner(ctx) + go prc.startGetUpdatesTelegramLongPolling(ctx) return prc } diff --git a/notifications/push_notifications.go b/notifications/push_notifications.go index 4a49f51..d811772 100644 --- a/notifications/push_notifications.go +++ b/notifications/push_notifications.go @@ -168,25 +168,6 @@ func (r *repository) sendPushNotification(ctx context.Context, pn *pushNotificat return nil } -func (r *repository) sendTelegramNotification(ctx context.Context, tn *telegramNotification) error { - if ctx.Err() != nil { - return errors.Wrap(ctx.Err(), "unexpected deadline") - } - if err := r.insertSentNotification(ctx, tn.sn); err != nil { - return errors.Wrapf(err, "failed to insert %#v", tn.sn) - } - responder := make(chan error, 1) - defer close(responder) - if err := r.telegramNotificationsClient.Send(ctx, tn.tn); err != nil { - return multierror.Append( //nolint:wrapcheck // Not needed. - errors.Wrapf(err, "failed to send telegram notification:%#v, desired to be sent:%#v", tn.sn, tn.sn), - errors.Wrapf(r.deleteSentNotification(ctx, tn.sn), "failed to delete SENT_NOTIFICATIONS as a rollback for %#v", tn.sn), - ).ErrorOrNil() - } - - return nil -} - func (r *repository) clearInvalidPushNotificationToken(ctx context.Context, userID string, token push.DeviceToken) error { if ctx.Err() != nil { return errors.Wrap(ctx.Err(), "unexpected deadline") diff --git a/notifications/scheduler.go b/notifications/scheduler.go index f8e34e6..0c8a4e2 100644 --- a/notifications/scheduler.go +++ b/notifications/scheduler.go @@ -43,9 +43,6 @@ func MustStartScheduler(ctx context.Context, cancel context.CancelFunc) *Schedul schedulerTelegramNotificationsMX: &sync.Mutex{}, } go sh.startWeeklyStatsUpdater(ctx) - if false { - go sh.startGetUpdatesTelegramLongPolling(ctx) - } sh.wg = new(sync.WaitGroup) sh.wg.Add(3 * int(schedulerWorkersCount)) //nolint:gomnd,mnd // . sh.cancel = cancel diff --git a/notifications/scheduler_push_notifications.go b/notifications/scheduler_push_notifications.go index ced147e..09cda8b 100644 --- a/notifications/scheduler_push_notifications.go +++ b/notifications/scheduler_push_notifications.go @@ -123,6 +123,8 @@ func (s *Scheduler) runPushNotificationsProcessor(ctx context.Context, workerNum ******************************************************************************************************************************************************/ if eErr := runConcurrentlyBatch(ctx, s.sendPushNotification, toSendPushNotifications, func(arg *pushNotification, err error) { if errors.Is(err, push.ErrInvalidDeviceToken) { + log.Error(errors.Wrapf(err, "invalid token:%v for:%v", arg.pn.Target, arg.sn.UserID)) + s.schedulerPushNotificationsMX.Lock() invalidTokens = append(invalidTokens, &invalidToken{ UserID: arg.sn.UserID, @@ -130,7 +132,7 @@ func (s *Scheduler) runPushNotificationsProcessor(ctx context.Context, workerNum }) s.schedulerPushNotificationsMX.Unlock() } else { - log.Error(errors.Wrapf(err, "can't send notification for:%v", arg.sn.UserID)) + log.Error(errors.Wrapf(err, "can't send notification:%+v", arg)) } }, func(arg *pushNotification) { s.schedulerPushNotificationsMX.Lock() @@ -193,7 +195,7 @@ func (s *Scheduler) sendPushNotification(ctx context.Context, pn *pushNotificati defer close(responder) s.pushNotificationsClient.Send(ctx, pn.pn, responder) - return errors.Wrapf(<-responder, "can't send push notification for pn:%#v", pn) + return errors.Wrapf(<-responder, "can't send push notification for pn:%+v", pn) } func (s *Scheduler) clearInvalidPushNotificationTokens(ctx context.Context, pnInvalidTokens []*invalidToken) error { diff --git a/notifications/telegram_notifications.go b/notifications/telegram_notifications.go index 9e865a8..fb0d6d6 100644 --- a/notifications/telegram_notifications.go +++ b/notifications/telegram_notifications.go @@ -9,10 +9,12 @@ import ( "fmt" "net/url" "strconv" + "strings" "sync" "text/template" stdlibtime "time" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/ice-blockchain/eskimo/users" @@ -143,25 +145,16 @@ func getTelegramDeeplink(nt NotificationType, cfg *config, username, inviteText return "" } -func prepareTelegramButtonsForSocialNotificationType(cfg *config, buttonTexts []string) []struct { - Text string `json:"text,omitempty"` - URL string `json:"url,omitempty"` -} { +func prepareTelegramButtonsForSocialNotificationType(cfg *config, buttonTexts []string) []telegram.Button { urls := getSocialsMapURL(cfg) if len(urls) != len(buttonTexts) { log.Error(errors.New("socials cfg/translation misconfiguration")) return nil } - res := make([]struct { - Text string `json:"text,omitempty"` - URL string `json:"url,omitempty"` - }, 0, len(buttonTexts)) + res := make([]telegram.Button, 0, len(buttonTexts)) for ix, text := range buttonTexts { - res = append(res, struct { - Text string `json:"text,omitempty"` - URL string `json:"url,omitempty"` - }{ + res = append(res, telegram.Button{ Text: text, URL: urls[ix], }) @@ -182,22 +175,24 @@ func getSocialsMapURL(cfg *config) map[int]string { return urls } -func (s *Scheduler) startGetUpdatesTelegramLongPolling(ctx context.Context) { - ticker := stdlibtime.NewTicker(30 * stdlibtime.Second) //nolint:gomnd,mnd // . +func (r *repository) startGetUpdatesTelegramLongPolling(ctx context.Context) { + ticker := stdlibtime.NewTicker(1 * stdlibtime.Second) defer ticker.Stop() for { select { case <-ticker.C: wg := new(sync.WaitGroup) - wg.Add(len(s.cfg.TelegramBots)) - go func() { - defer wg.Done() - reqCtx, cancel := context.WithTimeout(ctx, 1*stdlibtime.Minute) - if err := s.getTelegramLongPollingUpdates(reqCtx); err != nil { - log.Error(errors.Wrap(err, "failed to get updates long polling")) - } - cancel() - }() + wg.Add(len(r.cfg.TelegramBots)) + for botName, bot := range r.cfg.TelegramBots { + go func(botName, botToken string) { + defer wg.Done() + reqCtx, cancel := context.WithTimeout(ctx, 1*stdlibtime.Minute) + if err := r.getTelegramLongPollingUpdates(reqCtx, botName, botToken); err != nil { + log.Error(errors.Wrapf(err, "failed to get updates long polling for botName:%v", botName)) + } + cancel() + }(botName, bot.BotToken) + } wg.Wait() case <-ctx.Done(): return @@ -205,84 +200,184 @@ func (s *Scheduler) startGetUpdatesTelegramLongPolling(ctx context.Context) { } } -func (s *Scheduler) getTelegramLongPollingUpdates(ctx context.Context) (err error) { - for _, bot := range s.cfg.TelegramBots { - nextOffset := int64(0) - for { - upd, gErr := s.telegramNotificationsClient.GetUpdates(ctx, &telegram.GetUpdatesArg{ - BotToken: bot.BotToken, - AllowedUpdates: []string{"message", "callback_query"}, - Limit: telegramLongPollingLimit, - Offset: nextOffset, - }) - if gErr != nil { - return errors.Wrapf(gErr, "can't get updates for offset: %v", nextOffset) - } - if len(upd) == 0 { - break - } - nextOffset, err = s.handleTelegramUpdates(ctx, upd) - if err != nil { - return errors.Wrapf(err, "can't handle telegram updates for:%v", upd) - } +func (r *repository) getTelegramLongPollingUpdates(ctx context.Context, botName, botToken string) error { + nextOffset := int64(0) + for { + upd, gErr := r.telegramNotificationsClient.GetUpdates(ctx, &telegram.GetUpdatesArg{ + BotToken: botToken, + AllowedUpdates: []string{"message"}, + Limit: telegramLongPollingLimit, + Offset: nextOffset, + }) + if gErr != nil { + return errors.Wrapf(gErr, "can't get updates for bot:%v offset: %v", botName, nextOffset) + } + if len(upd) == 0 { + break + } + offset, notifications, err := r.handleTelegramUpdates(ctx, upd, botToken, botName) + if err != nil { + return errors.Wrapf(err, "can't handle telegram updates for bot:%v for:%v", botName, upd) + } + nextOffset = offset + + if rErr := runConcurrentlyBatch(ctx, r.sendTelegramNotification, notifications, func(arg *telegramNotification, err error) { + log.Error(errors.Wrapf(err, "can't send telegram command notification for bot:%v for:%+v", botName, arg)) + }, func(_ *telegramNotification) {}); rErr != nil { + return errors.Wrapf(rErr, "can't send telegram command notification batch for bot:%v, for:%+v", botName, notifications) } } return nil } -//nolint:funlen // . -func (s *Scheduler) handleTelegramUpdates(ctx context.Context, updates []*telegram.Update) (nextOffset int64, err error) { +//nolint:funlen,gocognit,revive,cyclop // . +func (r *repository) handleTelegramUpdates( + ctx context.Context, updates []*telegram.Update, botToken, botName string, +) (nextOffset int64, notif []*telegramNotification, err error) { var ( maxUpdateID = int64(0) now = time.Now() - scheduled = make([]*scheduledNotification, 0, len(updates)) uniquenessTime = fmt.Sprintf("%v:%02d:%02d %02d:%02d:%02d", now.Year(), int(now.Month()), now.Day(), now.Hour(), now.Minute(), now.Second()) + notifications = make([]*telegramNotification, 0, len(updates)) ) - userInfoMap, err := s.getReplyUserInfo(ctx, updates) + userInfoMap, err := r.getTelegramReplyUserInfo(ctx, updates) if err != nil { - return 0, errors.Wrapf(err, "can't get user ids by telegram user ids for:%#v", updates) + return 0, nil, errors.Wrapf(err, "can't get user ids by telegram user ids for bot:%v for:%#v", botName, updates) } if len(userInfoMap) == 0 { - return 0, nil + return 0, nil, nil } for _, upd := range updates { - if upd.Message.From.IsBot { - log.Warn("The message are sent through the bot", upd) - - continue - } if upd.UpdateID > maxUpdateID { maxUpdateID = upd.UpdateID } + if upd.Message.From.IsBot || !isCommand(upd) || !isAllowedCommand(TelegramCommand(upd.Message.Text)) { + log.Warn("The message is sent through the bot or is not a command", upd) + + continue + } idStr := strconv.FormatInt(upd.Message.From.ID, 10) + if userInfoMap[idStr].Username == userInfoMap[idStr].UserID || userInfoMap[idStr].Username == "" { + log.Error(errors.Wrapf(errors.New("no username"), "no username for userID:%v, telegram user id:%v", userInfoMap[idStr].UserID, upd.Message.From.ID)) + + continue + } if _, ok := userInfoMap[idStr]; !ok { - log.Warn("no such telegram user id", upd.Message.From.ID) + log.Error(errors.New("no such telegram user id"), upd.Message.From.ID) continue } - scheduled = append(scheduled, &scheduledNotification{ - ScheduledAt: now, - ScheduledFor: time.New(now.Add(1 * stdlibtime.Minute)), - Data: &users.JSON{ - "Username": upd.Message.From.Username, + notificationType := getNotificationTypeByStartCommandArg(upd.Message.Text) + tmpl, found := allTelegramNotificationTemplates[notificationType][userInfoMap[idStr].Language] + if !found { + log.Warn(fmt.Sprintf("language `%v` was not found in the `%v` telegram notifications config", userInfoMap[idStr].Language, notificationType)) + tmpl, found = allTelegramNotificationTemplates[notificationType][defaultLanguage] + if !found { + log.Panic(fmt.Sprintf("no default translations provided for notification, lang:%v, notificationType:%v", userInfoMap[idStr].Language, notificationType)) //nolint:lll // . + } + } + notification := &telegramNotification{ + tn: &telegram.Notification{ + ChatID: userInfoMap[idStr].TelegramUserID, + Text: tmpl.getBody(users.JSON{ + "TenantName": r.cfg.TenantName, + "TokenName": r.cfg.TokenName, + "Username": userInfoMap[idStr].Username, + }), + BotToken: botToken, }, - Language: userInfoMap[idStr].Language, - UserID: userInfoMap[idStr].UserID, - Uniqueness: fmt.Sprintf("%v_%v_%v", ReplyNotificationType, upd.Message.MessageID, uniquenessTime), - NotificationType: string(ReplyNotificationType), - NotificationChannel: string(TelegramNotificationChannel), - NotificationChannelValue: strconv.FormatInt(upd.Message.MessageID, 10), - }) + sn: &sentNotification{ + SentAt: now, + Language: userInfoMap[idStr].Language, + sentNotificationPK: sentNotificationPK{ + UserID: userInfoMap[idStr].UserID, + Uniqueness: fmt.Sprintf("command_%v_%v_%v", notificationType, upd.Message.MessageID, uniquenessTime), + NotificationType: notificationType, + NotificationChannel: TelegramNotificationChannel, + NotificationChannelValue: strconv.FormatInt(upd.Message.MessageID, 10), + }, + }, + } + switch notificationType { //nolint:exhaustive // We know for sure the commands to here. + case StartCommandNotificationType: + notification.tn.Buttons = append(notification.tn.Buttons, prepareTelegramButtonsForStartCommandNotificationType(r.cfg, botName, tmpl.ButtonText)...) + case SocialsNotificationType: + notification.tn.Buttons = append(notification.tn.Buttons, prepareTelegramButtonsForSocialNotificationType(r.cfg, tmpl.ButtonText)...) + default: + continue + } + notifications = append(notifications, notification) } - if iErr := insertScheduledNotifications(ctx, s.db, scheduled); iErr != nil { - return 0, errors.Wrapf(iErr, "can't insert scheduled notifications for:%#v", updates) + + return maxUpdateID + 1, notifications, nil +} + +func getNotificationTypeByStartCommandArg(text string) NotificationType { + splitted := strings.Split(text, " ") + if len(splitted) > 1 && strings.HasPrefix(text, string(StartTelegramCommand)) { + if splitted[1] == string(SocialsNotificationType) { + return SocialsNotificationType + } } - return maxUpdateID + 1, nil + return notificationTypeByCommand(TelegramCommand(text)) +} + +func prepareTelegramButtonsForStartCommandNotificationType(cfg *config, botName string, buttonTexts []string) []telegram.Button { + res := make([]telegram.Button, 0, len(buttonTexts)) + res = append(res, telegram.Button{ + Text: buttonTexts[0], + URL: cfg.WebAppLink, + }, telegram.Button{ + Text: buttonTexts[1], + URL: fmt.Sprintf("https://t.me/%v?start=socials", botName), + }, telegram.Button{ + Text: buttonTexts[2], + URL: fmt.Sprintf("%v/%v", cfg.WebSiteURL, "knowledge-base"), + }) + + return res } -func (s *Scheduler) getReplyUserInfo(ctx context.Context, updates []*telegram.Update) (res map[telegramUserID]*telegramUserInfo, err error) { +func notificationTypeByCommand(command TelegramCommand) NotificationType { + switch command { + case StartTelegramCommand: + return StartCommandNotificationType + case SocialsTelegramCommand: + return SocialsNotificationType + default: + log.Panic("wrong command ", command) + } + + return "" +} + +func isCommand(upd *telegram.Update) bool { + isCommand := false + for _, entity := range upd.Message.Entities { + if entity.Type == telegram.BotCommandType { + isCommand = true + } + } + + return isCommand +} + +func isAllowedCommand(command TelegramCommand) bool { + available := false + for _, cmd := range availableBotCommands { + if strings.Contains(string(command), string(cmd)) { + available = true + + break + } + } + + return available +} + +func (r *repository) getTelegramReplyUserInfo(ctx context.Context, updates []*telegram.Update) (res map[telegramUserID]*telegramUserInfo, err error) { if len(updates) == 0 { return } @@ -292,11 +387,12 @@ func (s *Scheduler) getReplyUserInfo(ctx context.Context, updates []*telegram.Up } sql := `SELECT user_id, + username, COALESCE(telegram_user_id, '') AS telegram_user_id, language FROM users WHERE telegram_user_id = ANY($1)` - result, err := storage.Select[telegramUserInfo](ctx, s.db, sql, telegramUserIDs) + result, err := storage.Select[telegramUserInfo](ctx, r.db, sql, telegramUserIDs) if err != nil { return nil, errors.Wrapf(err, "failed to get user ids for telegram user ids:%#v", telegramUserIDs) } @@ -310,3 +406,22 @@ func (s *Scheduler) getReplyUserInfo(ctx context.Context, updates []*telegram.Up return } + +func (r *repository) sendTelegramNotification(ctx context.Context, tn *telegramNotification) error { + if ctx.Err() != nil { + return errors.Wrap(ctx.Err(), "unexpected deadline") + } + if err := r.insertSentNotification(ctx, tn.sn); err != nil { + return errors.Wrapf(err, "failed to insert %#v", tn.sn) + } + responder := make(chan error, 1) + defer close(responder) + if err := r.telegramNotificationsClient.Send(ctx, tn.tn); err != nil { + return multierror.Append( //nolint:wrapcheck // Not needed. + errors.Wrapf(err, "failed to send telegram notification:%#v, desired to be sent:%#v", tn.sn, tn.sn), + errors.Wrapf(r.deleteSentNotification(ctx, tn.sn), "failed to delete SENT_NOTIFICATIONS as a rollback for %#v", tn.sn), + ).ErrorOrNil() + } + + return nil +} diff --git a/notifications/translations/telegram/start.txt b/notifications/translations/telegram/start.txt new file mode 100644 index 0000000..a46d88e --- /dev/null +++ b/notifications/translations/telegram/start.txt @@ -0,0 +1,10 @@ +{ + "en": { + "body": "Hi, @{{.Username}}! Welcome to {{.TenantName}}\n\n\n{{.TenantName}} Token ({{.TokenName}}) is set to transform the festival experience through blockchain technology.\nOur app ensures a seamless experience without using any of your phone's resources, allowing you to participate in the {{.TenantName}} ecosystem and earn {{.TokenName}} Tokens for free using your phone.\n\nJoin the {{.TenantName}} community and earn {{.TokenName}} Tokens effortlessly with our blockchain-powered app. Start earning today!", + "buttonText": [ + "⭐️ Launch App", + "🙌 Join our community", + "💡 Help" + ] + } +}