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"
+ ]
+ }
+}