Client.SendMessage(title, raw, targets) creates a private-message topic by posting to /posts.json with target_usernames / target_group_names / target_emails (comma-joined on the wire, as Discourse expects, never JSON arrays) instead of a category. The result reuses the topic ids type, so URL() renders the PM link. Local validation requires title, body and at least one target; undeliverable recipients surface as the typed ErrForbidden class. Fake-server tests assert the wire shape and the recorded recipients. Part of Redmine 507 (Discourse Go client): PM delivery is how the 495 pipeline pings humans that a briefing has landed.
78 lines
2.8 KiB
Go
78 lines
2.8 KiB
Go
// Private messages: sent through the same POST /posts.json endpoint as
|
|
// topics, but with target_usernames / target_group_names / target_emails
|
|
// instead of a category — Discourse then creates a private-message topic
|
|
// (archetype private_message) visible only to sender and recipients.
|
|
package discourse
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// SendMessageRequest sends a private message. Exactly one of the three
|
|
// target lists is the common case; any non-empty one works. At least one
|
|
// target overall is required — Discourse 422s without one.
|
|
type SendMessageRequest struct {
|
|
Title string
|
|
Raw string
|
|
TargetUsernames []string
|
|
TargetGroupNames []string
|
|
TargetEmails []string
|
|
}
|
|
|
|
// sendMessageWire is the POST /posts.json body: Discourse expects the
|
|
// target lists comma-joined strings, not JSON arrays.
|
|
type sendMessageWire struct {
|
|
Title string `json:"title"`
|
|
Raw string `json:"raw"`
|
|
TargetUsernames string `json:"target_usernames,omitempty"`
|
|
TargetGroupNames string `json:"target_group_names,omitempty"`
|
|
TargetEmails string `json:"target_emails,omitempty"`
|
|
}
|
|
|
|
// SendMessageResult is the response: a fresh private-message topic,
|
|
// same shape as a topic creation (URL() renders its link).
|
|
type SendMessageResult = CreateTopicResult
|
|
|
|
// SendMessage posts a private message and returns the new PM topic's
|
|
// ids. Unknown recipients fail server-side as 403 (ErrForbidden) —
|
|
// that is the "not permitted to view the requested resource" case.
|
|
func (c *Client) SendMessage(ctx context.Context, req SendMessageRequest) (*SendMessageResult, error) {
|
|
if strings.TrimSpace(req.Title) == "" {
|
|
return nil, fmt.Errorf("%w: message title is required", ErrInvalidRequest)
|
|
}
|
|
if strings.TrimSpace(req.Raw) == "" {
|
|
return nil, fmt.Errorf("%w: message raw body is required", ErrInvalidRequest)
|
|
}
|
|
if len(req.TargetUsernames) == 0 && len(req.TargetGroupNames) == 0 && len(req.TargetEmails) == 0 {
|
|
return nil, fmt.Errorf("%w: at least one target (username, group or email) is required", ErrInvalidRequest)
|
|
}
|
|
wire := sendMessageWire{
|
|
Title: req.Title,
|
|
Raw: req.Raw,
|
|
TargetUsernames: strings.Join(cleanList(req.TargetUsernames), ","),
|
|
TargetGroupNames: strings.Join(cleanList(req.TargetGroupNames), ","),
|
|
TargetEmails: strings.Join(cleanList(req.TargetEmails), ","),
|
|
}
|
|
var out SendMessageResult
|
|
if err := c.Post(ctx, "/posts.json", wire, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
if out.TopicID == 0 {
|
|
return nil, fmt.Errorf("%w: message sent but response carries no topic_id", ErrMalformedResponse)
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// cleanList trims whitespace and drops empties from a target list.
|
|
func cleanList(in []string) []string {
|
|
out := make([]string, 0, len(in))
|
|
for _, v := range in {
|
|
if v = strings.TrimSpace(v); v != "" {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|