TG settlement service
This commit is contained in:
@@ -25,6 +25,7 @@ type Client interface {
|
||||
SendDemoRequest(ctx context.Context, request *model.DemoRequest) error
|
||||
SendContactRequest(ctx context.Context, request *model.ContactRequest) error
|
||||
SendCallRequest(ctx context.Context, request *model.CallRequest) error
|
||||
SendText(ctx context.Context, chatID string, text string, replyToMessageID string) (*model.TelegramMessage, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
@@ -38,13 +39,14 @@ type client struct {
|
||||
}
|
||||
|
||||
type sendMessagePayload struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
ThreadID *int64 `json:"message_thread_id,omitempty"`
|
||||
DisablePreview bool `json:"disable_web_page_preview,omitempty"`
|
||||
DisableNotify bool `json:"disable_notification,omitempty"`
|
||||
ProtectContent bool `json:"protect_content,omitempty"`
|
||||
ChatID string `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
ThreadID *int64 `json:"message_thread_id,omitempty"`
|
||||
ReplyToMessageID *int64 `json:"reply_to_message_id,omitempty"`
|
||||
DisablePreview bool `json:"disable_web_page_preview,omitempty"`
|
||||
DisableNotify bool `json:"disable_notification,omitempty"`
|
||||
ProtectContent bool `json:"protect_content,omitempty"`
|
||||
}
|
||||
|
||||
func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
|
||||
@@ -106,16 +108,40 @@ func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest
|
||||
return c.sendForm(ctx, newDemoRequestTemplate(request))
|
||||
}
|
||||
|
||||
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) error {
|
||||
type sendMessageResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
Result *messageResponse `json:"result,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type messageResponse struct {
|
||||
MessageID int64 `json:"message_id"`
|
||||
Date int64 `json:"date"`
|
||||
Chat messageChat `json:"chat"`
|
||||
From *messageUser `json:"from,omitempty"`
|
||||
Text string `json:"text"`
|
||||
ReplyToMessage *messageResponse `json:"reply_to_message,omitempty"`
|
||||
}
|
||||
|
||||
type messageChat struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type messageUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) (*model.TelegramMessage, error) {
|
||||
body, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to marshal telegram payload", zap.Error(err))
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to create telegram request", zap.Error(err))
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@@ -129,26 +155,41 @@ func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) er
|
||||
if payload.ThreadID != nil {
|
||||
fields = append(fields, zap.Int64("thread_id", *payload.ThreadID))
|
||||
}
|
||||
if payload.ReplyToMessageID != nil {
|
||||
fields = append(fields, zap.Int64("reply_to_message_id", *payload.ReplyToMessageID))
|
||||
}
|
||||
c.logger.Debug("Sending Telegram message", fields...)
|
||||
start := time.Now()
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Warn("Telegram request failed", zap.Error(err))
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<10))
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
var parsed sendMessageResponse
|
||||
if err := json.Unmarshal(respBody, &parsed); err != nil {
|
||||
c.logger.Warn("Failed to decode telegram response", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if !parsed.OK || parsed.Result == nil {
|
||||
msg := "telegram sendMessage response missing result"
|
||||
if parsed.Description != "" {
|
||||
msg = parsed.Description
|
||||
}
|
||||
return nil, merrors.Internal(msg)
|
||||
}
|
||||
c.logger.Debug("Telegram message sent", zap.Int("status_code", resp.StatusCode), zap.Duration("latency", time.Since(start)))
|
||||
return nil
|
||||
return toTelegramMessage(parsed.Result), nil
|
||||
}
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
|
||||
c.logger.Warn("Telegram API returned non-success status",
|
||||
zap.Int("status_code", resp.StatusCode),
|
||||
zap.ByteString("response_body", respBody),
|
||||
zap.String("chat_id", c.chatID))
|
||||
return merrors.Internal(fmt.Sprintf("telegram sendMessage failed with status %d: %s", resp.StatusCode, string(respBody)))
|
||||
return nil, merrors.Internal(fmt.Sprintf("telegram sendMessage failed with status %d: %s", resp.StatusCode, string(respBody)))
|
||||
}
|
||||
|
||||
func (c *client) endpoint() string {
|
||||
@@ -178,5 +219,51 @@ func (c *client) sendForm(ctx context.Context, template messageTemplate) error {
|
||||
ThreadID: c.threadID,
|
||||
DisablePreview: true,
|
||||
}
|
||||
_, err := c.sendMessage(ctx, payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) SendText(ctx context.Context, chatID string, text string, replyToMessageID string) (*model.TelegramMessage, error) {
|
||||
chatID = strings.TrimSpace(chatID)
|
||||
if chatID == "" {
|
||||
chatID = c.chatID
|
||||
}
|
||||
if chatID == "" {
|
||||
return nil, merrors.InvalidArgument("telegram chat id is empty", "chat_id")
|
||||
}
|
||||
payload := sendMessagePayload{
|
||||
ChatID: chatID,
|
||||
Text: text,
|
||||
ParseMode: c.parseMode.String(),
|
||||
ThreadID: c.threadID,
|
||||
DisablePreview: true,
|
||||
}
|
||||
if replyToMessageID != "" {
|
||||
val, err := strconv.ParseInt(replyToMessageID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, merrors.InvalidArgumentWrap(err, "invalid reply_to_message_id", "reply_to_message_id")
|
||||
}
|
||||
payload.ReplyToMessageID = &val
|
||||
}
|
||||
return c.sendMessage(ctx, payload)
|
||||
}
|
||||
|
||||
func toTelegramMessage(msg *messageResponse) *model.TelegramMessage {
|
||||
if msg == nil {
|
||||
return nil
|
||||
}
|
||||
result := &model.TelegramMessage{
|
||||
ChatID: strconv.FormatInt(msg.Chat.ID, 10),
|
||||
MessageID: strconv.FormatInt(msg.MessageID, 10),
|
||||
Text: msg.Text,
|
||||
SentAt: msg.Date,
|
||||
}
|
||||
if msg.From != nil {
|
||||
result.FromUserID = strconv.FormatInt(msg.From.ID, 10)
|
||||
result.FromUsername = msg.From.Username
|
||||
}
|
||||
if msg.ReplyToMessage != nil {
|
||||
result.ReplyToMessageID = strconv.FormatInt(msg.ReplyToMessage.MessageID, 10)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user