2020-07-29 19:29:30 +00:00
|
|
|
// Package webhook provides means to interact with webhooks directly and not
|
|
|
|
// through the bot API.
|
|
|
|
package webhook
|
|
|
|
|
|
|
|
import (
|
2020-12-16 21:11:11 +00:00
|
|
|
"mime/multipart"
|
2020-07-29 19:29:30 +00:00
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
2020-10-28 22:39:59 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/api"
|
|
|
|
"github.com/diamondburned/arikawa/v2/discord"
|
|
|
|
"github.com/diamondburned/arikawa/v2/utils/httputil"
|
2020-12-16 20:11:11 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/utils/json/option"
|
2020-12-16 21:11:11 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/utils/sendpart"
|
2020-07-29 19:29:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Client is the client used to interact with a webhook.
|
|
|
|
type Client struct {
|
|
|
|
// Client is the httputil.Client used to call Discord's API.
|
|
|
|
*httputil.Client
|
|
|
|
// ID is the id of the webhook.
|
|
|
|
ID discord.WebhookID
|
2020-07-30 02:48:37 +00:00
|
|
|
// Token is the token of the webhook.
|
|
|
|
Token string
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewClient creates a new Client using the passed token and id.
|
2020-07-30 02:48:37 +00:00
|
|
|
func NewClient(id discord.WebhookID, token string) *Client {
|
|
|
|
return NewCustomClient(id, token, httputil.NewClient())
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewCustomClient creates a new Client creates a new Client using the passed
|
|
|
|
// token and id and makes API calls using the passed httputil.Client
|
2020-07-30 02:48:37 +00:00
|
|
|
func NewCustomClient(id discord.WebhookID, token string, c *httputil.Client) *Client {
|
2020-07-29 19:29:30 +00:00
|
|
|
return &Client{
|
|
|
|
Client: c,
|
|
|
|
ID: id,
|
2020-07-30 02:48:37 +00:00
|
|
|
Token: token,
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get gets the webhook.
|
|
|
|
func (c *Client) Get() (*discord.Webhook, error) {
|
|
|
|
var w *discord.Webhook
|
|
|
|
return w, c.RequestJSON(&w, "GET", api.EndpointWebhooks+c.ID.String()+"/"+c.Token)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modify modifies the webhook.
|
|
|
|
func (c *Client) Modify(data api.ModifyWebhookData) (*discord.Webhook, error) {
|
|
|
|
var w *discord.Webhook
|
|
|
|
return w, c.RequestJSON(
|
|
|
|
&w, "PATCH",
|
|
|
|
api.EndpointWebhooks+c.ID.String()+"/"+c.Token,
|
|
|
|
httputil.WithJSONBody(data),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes a webhook permanently.
|
|
|
|
func (c *Client) Delete() error {
|
|
|
|
return c.FastRequest("DELETE", api.EndpointWebhooks+c.ID.String()+"/"+c.Token)
|
|
|
|
}
|
|
|
|
|
2020-12-16 21:11:11 +00:00
|
|
|
// https://discord.com/developers/docs/resources/webhook#execute-webhook-jsonform-params
|
2020-12-16 21:17:56 +00:00
|
|
|
type ExecuteData struct {
|
2020-12-16 21:11:11 +00:00
|
|
|
// Content are the message contents (up to 2000 characters).
|
|
|
|
//
|
|
|
|
// Required: one of content, file, embeds
|
|
|
|
Content string `json:"content,omitempty"`
|
|
|
|
|
|
|
|
// Username overrides the default username of the webhook
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
// AvatarURL overrides the default avatar of the webhook.
|
|
|
|
AvatarURL discord.URL `json:"avatar_url,omitempty"`
|
|
|
|
|
|
|
|
// TTS is true if this is a TTS message.
|
|
|
|
TTS bool `json:"tts,omitempty"`
|
|
|
|
// Embeds contains embedded rich content.
|
|
|
|
//
|
|
|
|
// Required: one of content, file, embeds
|
|
|
|
Embeds []discord.Embed `json:"embeds,omitempty"`
|
|
|
|
|
|
|
|
// Files represents a list of files to upload. This will not be JSON-encoded
|
|
|
|
// and will only be available through WriteMultipart.
|
|
|
|
Files []sendpart.File `json:"-"`
|
|
|
|
|
|
|
|
// AllowedMentions are the allowed mentions for the message.
|
|
|
|
AllowedMentions *api.AllowedMentions `json:"allowed_mentions,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// NeedsMultipart returns true if the ExecuteWebhookData has files.
|
2020-12-16 21:17:56 +00:00
|
|
|
func (data ExecuteData) NeedsMultipart() bool {
|
2020-12-16 21:11:11 +00:00
|
|
|
return len(data.Files) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteMultipart writes the webhook data into the given multipart body. It does
|
|
|
|
// not close body.
|
2020-12-16 21:17:56 +00:00
|
|
|
func (data ExecuteData) WriteMultipart(body *multipart.Writer) error {
|
2020-12-16 21:11:11 +00:00
|
|
|
return sendpart.Write(body, data, data.Files)
|
|
|
|
}
|
|
|
|
|
2020-07-29 19:29:30 +00:00
|
|
|
// Execute sends a message to the webhook, but doesn't wait for the message to
|
|
|
|
// get created. This is generally faster, but only applicable if no further
|
|
|
|
// interaction is required.
|
2020-12-16 21:17:56 +00:00
|
|
|
func (c *Client) Execute(data ExecuteData) (err error) {
|
2020-07-29 19:29:30 +00:00
|
|
|
_, err = c.execute(data, false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecuteAndWait executes the webhook, and waits for the generated
|
|
|
|
// discord.Message to be returned.
|
2020-12-16 21:17:56 +00:00
|
|
|
func (c *Client) ExecuteAndWait(data ExecuteData) (*discord.Message, error) {
|
2020-07-29 19:29:30 +00:00
|
|
|
return c.execute(data, true)
|
|
|
|
}
|
|
|
|
|
2020-12-16 21:17:56 +00:00
|
|
|
func (c *Client) execute(data ExecuteData, wait bool) (*discord.Message, error) {
|
2020-07-29 19:29:30 +00:00
|
|
|
if data.Content == "" && len(data.Embeds) == 0 && len(data.Files) == 0 {
|
|
|
|
return nil, api.ErrEmptyMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.AllowedMentions != nil {
|
|
|
|
if err := data.AllowedMentions.Verify(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "allowedMentions error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, embed := range data.Embeds {
|
|
|
|
if err := embed.Validate(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "embed error at "+strconv.Itoa(i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-16 21:11:11 +00:00
|
|
|
var param url.Values
|
2020-07-29 19:29:30 +00:00
|
|
|
if wait {
|
2020-12-16 21:11:11 +00:00
|
|
|
param = url.Values{"wait": {"true"}}
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var URL = api.EndpointWebhooks + c.ID.String() + "/" + c.Token + "?" + param.Encode()
|
|
|
|
|
2020-12-16 21:11:11 +00:00
|
|
|
var msg *discord.Message
|
|
|
|
var ptr interface{}
|
|
|
|
if wait {
|
|
|
|
ptr = &msg
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 21:11:11 +00:00
|
|
|
return msg, sendpart.POST(c.Client, data, ptr, URL)
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 20:11:11 +00:00
|
|
|
// https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params
|
2020-12-16 21:17:56 +00:00
|
|
|
type EditMessageData struct {
|
2020-12-16 20:11:11 +00:00
|
|
|
// Content are the message contents. They may be up to 2000 characters
|
|
|
|
// characters long.
|
|
|
|
Content option.NullableString `json:"content,omitempty"`
|
|
|
|
// Embeds is an array of up to 10 discord.Embeds.
|
|
|
|
Embeds *[]discord.Embed `json:"embeds,omitempty"`
|
|
|
|
// AllowedMentions are the AllowedMentions for the message.
|
|
|
|
AllowedMentions *api.AllowedMentions `json:"allowed_mentions,omitempty"`
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 20:11:11 +00:00
|
|
|
// EditMessage edits a previously-sent webhook message from the same webhook.
|
2020-12-16 21:17:56 +00:00
|
|
|
func (c *Client) EditMessage(messageID discord.MessageID, data EditMessageData) error {
|
2020-12-16 20:11:11 +00:00
|
|
|
return c.FastRequest("PATCH",
|
|
|
|
api.EndpointWebhooks+c.ID.String()+"/"+c.Token+"/messages/"+messageID.String(),
|
|
|
|
httputil.WithJSONBody(data))
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|
|
|
|
|
2020-12-16 20:11:11 +00:00
|
|
|
// DeleteMessage deletes a message that was previously created by the same
|
|
|
|
// webhook.
|
|
|
|
func (c *Client) DeleteMessage(messageID discord.MessageID) error {
|
|
|
|
return c.FastRequest("DELETE",
|
|
|
|
api.EndpointWebhooks+c.ID.String()+"/"+c.Token+"/messages/"+messageID.String())
|
2020-07-29 19:29:30 +00:00
|
|
|
}
|