+ contact requests
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed

This commit is contained in:
Stephan D
2025-11-19 14:42:38 +01:00
parent 717dafc673
commit e08eb742e4
16 changed files with 403 additions and 132 deletions

View File

@@ -85,6 +85,11 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
return nil, err
}
if err := a.Register().Consumer(snotifications.NewContactRequestProcessor(p.logger, p.onContactRequest)); err != nil {
p.logger.Error("Failed to register contact request handler", zap.Error(err))
return nil, err
}
return p, nil
}
@@ -99,3 +104,15 @@ func (a *NotificationAPI) onDemoRequest(ctx context.Context, request *model.Demo
a.logger.Info("Demo request sent via Telegram", zap.String("name", request.Name), zap.String("organization", request.OrganizationName))
return nil
}
func (a *NotificationAPI) onContactRequest(ctx context.Context, request *model.ContactRequest) error {
if a.tg == nil {
return merrors.Internal("telegram client is not configured")
}
if err := a.tg.SendContactRequest(ctx, request); err != nil {
a.logger.Warn("Failed to send contact request via telegram", zap.Error(err))
return err
}
a.logger.Info("Contact request sent via Telegram", zap.String("name", request.Name), zap.String("topic", request.Topic))
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"os"
@@ -24,6 +23,7 @@ const defaultAPIURL = "https://api.telegram.org"
type Client interface {
SendDemoRequest(ctx context.Context, request *model.DemoRequest) error
SendContactRequest(ctx context.Context, request *model.ContactRequest) error
}
type client struct {
@@ -102,15 +102,7 @@ func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest
if request == nil {
return merrors.InvalidArgument("demo request payload is nil", "request")
}
message := buildMessage(request, c.parseMode)
payload := sendMessagePayload{
ChatID: c.chatID,
Text: message,
ParseMode: c.parseMode,
ThreadID: c.threadID,
DisablePreview: true,
}
return c.sendMessage(ctx, payload)
return c.sendForm(ctx, newDemoRequestTemplate(request))
}
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) error {
@@ -162,89 +154,21 @@ func (c *client) endpoint() string {
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
}
func buildMessage(req *model.DemoRequest, parseMode string) string {
var builder strings.Builder
builder.WriteString("New demo request received\n")
builder.WriteString("-----------------------------\n")
formatter := selectValueFormatter(parseMode)
appendMessageField(&builder, "Name", req.Name, formatter)
appendMessageField(&builder, "Organization", req.OrganizationName, formatter)
appendMessageField(&builder, "Phone", req.Phone, formatter)
appendMessageField(&builder, "Work email", req.WorkEmail, formatter)
appendMessageField(&builder, "Payout volume", req.PayoutVolume, formatter)
if strings.TrimSpace(req.Comment) != "" {
appendMessageField(&builder, "Comment", req.Comment, formatter)
func (c *client) SendContactRequest(ctx context.Context, request *model.ContactRequest) error {
if request == nil {
return merrors.InvalidArgument("contact request payload is nil", "request")
}
return builder.String()
return c.sendForm(ctx, newContactRequestTemplate(request))
}
type valueFormatter func(string) string
func appendMessageField(builder *strings.Builder, label, value string, formatter valueFormatter) {
value = strings.TrimSpace(value)
if value == "" {
value = "—"
} else if formatter != nil {
value = formatter(value)
func (c *client) sendForm(ctx context.Context, template messageTemplate) error {
message := template.Format(c.parseMode)
payload := sendMessagePayload{
ChatID: c.chatID,
Text: message,
ParseMode: c.parseMode,
ThreadID: c.threadID,
DisablePreview: true,
}
fmt.Fprintf(builder, "• %s: %s\n", label, value)
}
func selectValueFormatter(parseMode string) valueFormatter {
switch strings.ToLower(parseMode) {
case "markdown":
return func(value string) string {
return fmt.Sprintf("*%s*", escapeMarkdown(value))
}
case "markdownv2":
return func(value string) string {
return fmt.Sprintf("*%s*", escapeMarkdownV2(value))
}
case "html":
return func(value string) string {
return fmt.Sprintf("<b>%s</b>", html.EscapeString(value))
}
default:
return nil
}
}
var markdownEscaper = strings.NewReplacer(
"*", "\\*",
"_", "\\_",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"`", "\\`",
)
var markdownV2Escaper = strings.NewReplacer(
"_", "\\_",
"*", "\\*",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"~", "\\~",
"`", "\\`",
">", "\\>",
"#", "\\#",
"+", "\\+",
"-", "\\-",
"=", "\\=",
"|", "\\|",
"{", "\\{",
"}", "\\}",
".", "\\.",
"!", "\\!",
)
func escapeMarkdown(value string) string {
return markdownEscaper.Replace(value)
}
func escapeMarkdownV2(value string) string {
return markdownV2Escaper.Replace(value)
return c.sendMessage(ctx, payload)
}

View File

@@ -0,0 +1,17 @@
package telegram
import "github.com/tech/sendico/pkg/model"
func newContactRequestTemplate(request *model.ContactRequest) messageTemplate {
return messageTemplate{
title: "New contact request received",
fields: []messageField{
{label: "Name", value: request.Name},
{label: "Email", value: request.Email},
{label: "Phone", value: request.Phone},
{label: "Company", value: request.Company},
{label: "Topic", value: request.Topic},
{label: "Message", value: request.Message},
},
}
}

View File

@@ -0,0 +1,24 @@
package telegram
import (
"strings"
"github.com/tech/sendico/pkg/model"
)
func newDemoRequestTemplate(request *model.DemoRequest) messageTemplate {
fields := []messageField{
{label: "Name", value: request.Name},
{label: "Organization", value: request.OrganizationName},
{label: "Phone", value: request.Phone},
{label: "Work email", value: request.WorkEmail},
{label: "Payout volume", value: request.PayoutVolume},
}
if strings.TrimSpace(request.Comment) != "" {
fields = append(fields, messageField{label: "Comment", value: request.Comment})
}
return messageTemplate{
title: "New demo request received",
fields: fields,
}
}

View File

@@ -0,0 +1,100 @@
package telegram
import (
"fmt"
"html"
"strings"
)
type messageField struct {
label string
value string
}
type messageTemplate struct {
title string
fields []messageField
}
func (mt messageTemplate) Format(parseMode string) string {
var builder strings.Builder
builder.WriteString(mt.title)
builder.WriteString("\n")
builder.WriteString("-----------------------------\n")
formatter := selectValueFormatter(parseMode)
for _, field := range mt.fields {
appendMessageField(&builder, field.label, field.value, formatter)
}
return builder.String()
}
type valueFormatter func(string) string
func appendMessageField(builder *strings.Builder, label, value string, formatter valueFormatter) {
value = strings.TrimSpace(value)
if value == "" {
value = "—"
} else if formatter != nil {
value = formatter(value)
}
fmt.Fprintf(builder, "• %s: %s\n", label, value)
}
func selectValueFormatter(parseMode string) valueFormatter {
switch strings.ToLower(parseMode) {
case "markdown":
return func(value string) string {
return fmt.Sprintf("*%s*", escapeMarkdown(value))
}
case "markdownv2":
return func(value string) string {
return fmt.Sprintf("*%s*", escapeMarkdownV2(value))
}
case "html":
return func(value string) string {
return fmt.Sprintf("<b>%s</b>", html.EscapeString(value))
}
default:
return nil
}
}
var markdownEscaper = strings.NewReplacer(
"*", "\\*",
"_", "\\_",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"`", "\\`",
)
var markdownV2Escaper = strings.NewReplacer(
"_", "\\_",
"*", "\\*",
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"~", "\\~",
"`", "\\`",
">", "\\>",
"#", "\\#",
"+", "\\+",
"-", "\\-",
"=", "\\=",
"|", "\\|",
"{", "\\{",
"}", "\\}",
".", "\\.",
"!", "\\!",
)
func escapeMarkdown(value string) string {
return markdownEscaper.Replace(value)
}
func escapeMarkdownV2(value string) string {
return markdownV2Escaper.Replace(value)
}