package adapter import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" ) const safewAPIBase = "https://api.safew.bot" type SafeWSender struct { apiBase string } type safewConfig struct { Token string `json:"token"` ChatID json.RawMessage `json:"chat_id"` } type safewMessage struct { ChatID string `json:"chat_id"` Text string `json:"text"` ParseMode string `json:"parse_mode"` } type safewAPIResponse struct { OK bool `json:"ok"` Description string `json:"description"` } func (s *SafeWSender) Type() string { return "safew" } func (s *SafeWSender) Send(title, content string, config json.RawMessage) error { var cfg safewConfig if err := json.Unmarshal(config, &cfg); err != nil { return fmt.Errorf("parse safew config: %w", err) } token := strings.TrimSpace(cfg.Token) if token == "" { return fmt.Errorf("safew: token is required") } chatID, err := parseSafewChatID(cfg.ChatID) if err != nil { return err } payload := safewMessage{ ChatID: chatID, Text: "*" + escapeMarkdownV2(title) + "*\n" + escapeMarkdownV2(content), ParseMode: "MarkdownV2", } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("safew marshal: %w", err) } resp, err := http.Post(s.endpoint(token), "application/json", bytes.NewReader(body)) if err != nil { return fmt.Errorf("safew send: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("safew read: %w", err) } if resp.StatusCode >= 400 { desc := safewErrorDescription(respBody) if desc != "" { return fmt.Errorf("safew returned status %d: %s", resp.StatusCode, desc) } return fmt.Errorf("safew returned status %d", resp.StatusCode) } var api safewAPIResponse if err := json.Unmarshal(respBody, &api); err != nil { return fmt.Errorf("safew decode: %w", err) } if !api.OK { desc := api.Description if desc == "" { desc = "ok=false" } return fmt.Errorf("safew: %s", desc) } return nil } func (s *SafeWSender) endpoint(token string) string { base := s.apiBase if base == "" { base = safewAPIBase } return strings.TrimRight(base, "/") + "/bot" + token + "/sendMessage" } func parseSafewChatID(raw json.RawMessage) (string, error) { raw = bytes.TrimSpace(raw) if len(raw) == 0 || string(raw) == "null" { return "", fmt.Errorf("safew: chat_id is required") } if raw[0] == '"' { var id string if err := json.Unmarshal(raw, &id); err != nil { return "", fmt.Errorf("safew: invalid chat_id: %w", err) } id = strings.TrimSpace(id) if id == "" { return "", fmt.Errorf("safew: chat_id is required") } return id, nil } return string(raw), nil } func safewErrorDescription(body []byte) string { var api safewAPIResponse if err := json.Unmarshal(body, &api); err != nil { return strings.TrimSpace(string(body)) } return api.Description } func escapeMarkdownV2(s string) string { var b strings.Builder b.Grow(len(s)) for _, r := range s { switch r { case '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!', '\\': b.WriteByte('\\') } b.WriteRune(r) } return b.String() }