74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
package fa
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
|
|
"git.anthrove.art/public/go-fa-api/internal/urls"
|
|
)
|
|
|
|
// SendNote sends a new private message (note) to a user. Requires login.
|
|
//
|
|
// FA's note form posts to /msg/send/ with a per-form CSRF key that must
|
|
// be scraped from the notes inbox (or any page that renders the send
|
|
// form). This method does the scrape + post in one call.
|
|
//
|
|
// Returns nil on success. ErrUnauthorized when not logged in.
|
|
// *SystemMessageError when FA rejects the send (recipient blocked, rate
|
|
// limited, etc.).
|
|
func (c *Client) SendNote(ctx context.Context, to string, subject string, body string, opts ...Option) error {
|
|
to = strings.TrimSpace(to)
|
|
if to == "" {
|
|
return fmt.Errorf("fa: SendNote: empty recipient")
|
|
}
|
|
if subject == "" {
|
|
return fmt.Errorf("fa: SendNote: empty subject")
|
|
}
|
|
if body == "" {
|
|
return fmt.Errorf("fa: SendNote: empty body")
|
|
}
|
|
|
|
// Scrape the form key from /msg/pms/. The notes inbox renders the same
|
|
// CSRF key in any note-related form on the page; we don't actually need
|
|
// to be replying to anything to harvest it.
|
|
var key string
|
|
err := c.fetch(ctx, urls.MsgPMs(), func(doc *goquery.Document) error {
|
|
key = findNoteKey(doc)
|
|
if key == "" {
|
|
return fmt.Errorf("%w: SendNote: could not locate form key on /msg/pms/", ErrUnauthorized)
|
|
}
|
|
return nil
|
|
}, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v := url.Values{}
|
|
v.Set("key", key)
|
|
v.Set("to", to)
|
|
v.Set("subject", subject)
|
|
v.Set("message", body)
|
|
v.Set("send", "Send Note")
|
|
_, err = c.postForm(ctx, urls.Host+"/msg/send/", v, opts...)
|
|
return err
|
|
}
|
|
|
|
// findNoteKey looks for a hidden input named "key" inside any form that
|
|
// targets the notes subsystem (/msg/send/ or /msg/pms/). FA reuses the
|
|
// same key value across these forms within a session.
|
|
func findNoteKey(doc *goquery.Document) string {
|
|
var key string
|
|
doc.Find("form[action='/msg/send/'] input[name='key'], form[action='/msg/pms/'] input[name='key'], form[action^='/msg/'] input[name='key']").EachWithBreak(func(_ int, sel *goquery.Selection) bool {
|
|
if v := strings.TrimSpace(trimAttr(sel, "value")); v != "" {
|
|
key = v
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return key
|
|
}
|