github.com/starshine-sys/bcr@v0.21.0/context_slash.go (about)

     1  package bcr
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"emperror.dev/errors"
     8  
     9  	"github.com/diamondburned/arikawa/v3/api"
    10  	"github.com/diamondburned/arikawa/v3/discord"
    11  	"github.com/diamondburned/arikawa/v3/gateway"
    12  	"github.com/diamondburned/arikawa/v3/state"
    13  	"github.com/diamondburned/arikawa/v3/utils/json/option"
    14  	"github.com/diamondburned/arikawa/v3/utils/sendpart"
    15  )
    16  
    17  // Contexter is the type passed to (*Command).SlashCommand.
    18  // This includes basic methods implemented by both Context and SlashContext; for all of their respective methods and fields, convert the Contexter to a Context or SlashContext.
    19  // As SlashCommand will be called with a basic *Context if Command is nil, this allows for some code deduplication if the command only uses basic methods.
    20  type Contexter interface {
    21  	Flags
    22  
    23  	// SendX sends a message without returning the created discord.Message
    24  	SendX(string, ...discord.Embed) error
    25  	SendfX(string, ...interface{}) error
    26  
    27  	// Send sends a message, returning the created message.
    28  	Send(string, ...discord.Embed) (*discord.Message, error)
    29  	Sendf(string, ...interface{}) (*discord.Message, error)
    30  
    31  	SendComponents([]discord.Component, string, ...discord.Embed) (*discord.Message, error)
    32  
    33  	// SendFiles sends a message with attachments
    34  	SendFiles(string, ...sendpart.File) error
    35  
    36  	// SendEphemeral sends an ephemeral message (or falls back to a normal message without slash commands)
    37  	SendEphemeral(string, ...discord.Embed) error
    38  
    39  	EditOriginal(api.EditInteractionResponseData) (*discord.Message, error)
    40  
    41  	// Session returns this context's *state.State
    42  	Session() *state.State
    43  	// User returns this context's Author
    44  	User() discord.User
    45  	// GetGuild returns this context's Guild
    46  	GetGuild() *discord.Guild
    47  	// GetChannel returns this context's Channel
    48  	GetChannel() *discord.Channel
    49  	// GetParentChannel returns this context's ParentChannel
    50  	GetParentChannel() *discord.Channel
    51  	// GetMember returns this context's Member
    52  	GetMember() *discord.Member
    53  
    54  	// ButtonPages paginates a slice of embeds using buttons
    55  	ButtonPages(embeds []discord.Embed, timeout time.Duration) (msg *discord.Message, rmFunc func(), err error)
    56  	ButtonPagesWithComponents(embeds []discord.Embed, timeout time.Duration, components []discord.Component) (msg *discord.Message, rmFunc func(), err error)
    57  
    58  	// ConfirmButton confirms a prompt with buttons or "yes"/"no" messages.
    59  	ConfirmButton(userID discord.UserID, data ConfirmData) (yes, timeout bool)
    60  }
    61  
    62  var _ Contexter = (*SlashContext)(nil)
    63  
    64  // SlashContext is the Contexter passed to a slash command function.
    65  type SlashContext struct {
    66  	CommandID      discord.CommandID
    67  	CommandName    string
    68  	CommandOptions []gateway.InteractionOption
    69  
    70  	InteractionID    discord.InteractionID
    71  	InteractionToken string
    72  
    73  	Command *Command
    74  	Router  *Router
    75  	State   *state.State
    76  	Guild   *discord.Guild
    77  
    78  	Author discord.User
    79  	Member *discord.Member
    80  
    81  	Channel       *discord.Channel
    82  	ParentChannel *discord.Channel
    83  
    84  	// Event is the original raw event
    85  	Event *gateway.InteractionCreateEvent
    86  
    87  	AdditionalParams map[string]interface{}
    88  }
    89  
    90  // Session returns this SlashContext's state.
    91  func (ctx *SlashContext) Session() *state.State {
    92  	return ctx.State
    93  }
    94  
    95  // Errors related to slash contexts
    96  var (
    97  	ErrNotCommand = errors.New("not a command interaction")
    98  )
    99  
   100  // NewSlashContext creates a new slash command context.
   101  func (r *Router) NewSlashContext(ic *gateway.InteractionCreateEvent) (*SlashContext, error) {
   102  	var err error
   103  
   104  	if ic.Type != gateway.CommandInteraction {
   105  		return nil, ErrNotCommand
   106  	}
   107  
   108  	sc := &SlashContext{
   109  		Router:           r,
   110  		Event:            ic,
   111  		CommandName:      ic.Data.Name,
   112  		CommandID:        ic.Data.ID,
   113  		CommandOptions:   ic.Data.Options,
   114  		InteractionID:    ic.ID,
   115  		InteractionToken: ic.Token,
   116  		AdditionalParams: map[string]interface{}{},
   117  	}
   118  
   119  	if ic.Member != nil {
   120  		sc.Member = ic.Member
   121  		sc.Author = ic.Member.User
   122  	} else {
   123  		sc.Author = *ic.User
   124  	}
   125  
   126  	state, _ := r.StateFromGuildID(ic.GuildID)
   127  	sc.State = state
   128  
   129  	// get guild
   130  	if ic.GuildID.IsValid() {
   131  		sc.Guild, err = sc.State.Guild(ic.GuildID)
   132  		if err != nil {
   133  			return sc, ErrGuild
   134  		}
   135  		sc.Guild.Roles, err = sc.State.Roles(ic.GuildID)
   136  		if err != nil {
   137  			return sc, ErrGuild
   138  		}
   139  	}
   140  
   141  	// get the channel
   142  	sc.Channel, err = sc.State.Channel(ic.ChannelID)
   143  	if err != nil {
   144  		return sc, ErrChannel
   145  	}
   146  
   147  	if sc.Thread() {
   148  		sc.ParentChannel, err = sc.State.Channel(sc.Channel.ParentID)
   149  		if err != nil {
   150  			return sc, ErrChannel
   151  		}
   152  	}
   153  
   154  	return sc, nil
   155  }
   156  
   157  // Thread returns true if the context is in a thread channel.
   158  // If this function returns true, ctx.ParentChannel will be non-nil.
   159  func (ctx *SlashContext) Thread() bool {
   160  	return ctx.Channel.Type == discord.GuildNewsThread || ctx.Channel.Type == discord.GuildPublicThread || ctx.Channel.Type == discord.GuildPrivateThread
   161  }
   162  
   163  // SendX sends a message without returning the created discord.Message
   164  func (ctx *SlashContext) SendX(content string, embeds ...discord.Embed) (err error) {
   165  	data := api.InteractionResponse{
   166  		Type: api.MessageInteractionWithSource,
   167  		Data: &api.InteractionResponseData{
   168  			AllowedMentions: ctx.Router.DefaultMentions,
   169  		},
   170  	}
   171  
   172  	if len(embeds) != 0 {
   173  		data.Data.Embeds = &embeds
   174  	}
   175  	if content != "" {
   176  		data.Data.Content = option.NewNullableString(content)
   177  	}
   178  
   179  	err = ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, data)
   180  	return
   181  }
   182  
   183  // SendfX ...
   184  func (ctx *SlashContext) SendfX(format string, args ...interface{}) (err error) {
   185  	return ctx.SendX(fmt.Sprintf(format, args...))
   186  }
   187  
   188  // SendFiles sends a message with attachments
   189  func (ctx *SlashContext) SendFiles(content string, files ...sendpart.File) (err error) {
   190  	data := api.InteractionResponse{
   191  		Type: api.MessageInteractionWithSource,
   192  		Data: &api.InteractionResponseData{
   193  			AllowedMentions: ctx.Router.DefaultMentions,
   194  		},
   195  	}
   196  
   197  	if len(files) != 0 {
   198  		data.Data.Files = files
   199  	}
   200  	if content != "" {
   201  		data.Data.Content = option.NewNullableString(content)
   202  	}
   203  
   204  	err = ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, data)
   205  	return
   206  }
   207  
   208  // SendEphemeral sends an ephemeral message.
   209  func (ctx *SlashContext) SendEphemeral(content string, embeds ...discord.Embed) (err error) {
   210  	data := api.InteractionResponse{
   211  		Type: api.MessageInteractionWithSource,
   212  		Data: &api.InteractionResponseData{
   213  			AllowedMentions: ctx.Router.DefaultMentions,
   214  			Flags:           api.EphemeralResponse,
   215  		},
   216  	}
   217  
   218  	if len(embeds) != 0 {
   219  		data.Data.Embeds = &embeds
   220  	}
   221  	if content != "" {
   222  		data.Data.Content = option.NewNullableString(content)
   223  	}
   224  
   225  	err = ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, data)
   226  	return
   227  }
   228  
   229  // Original returns the original response to an interaction, if any.
   230  func (ctx *SlashContext) Original() (msg *discord.Message, err error) {
   231  	url := api.EndpointWebhooks + ctx.Router.Bot.ID.String() + "/" + ctx.InteractionToken + "/messages/@original"
   232  
   233  	return msg, ctx.State.RequestJSON(&msg, "GET", url)
   234  }
   235  
   236  // EditOriginal edits the original response.
   237  func (ctx *SlashContext) EditOriginal(data api.EditInteractionResponseData) (*discord.Message, error) {
   238  	return ctx.State.EditInteractionResponse(discord.AppID(ctx.Router.Bot.ID), ctx.Event.Token, data)
   239  }
   240  
   241  // GetGuild ...
   242  func (ctx *SlashContext) GetGuild() *discord.Guild { return ctx.Guild }
   243  
   244  // GetChannel ...
   245  func (ctx *SlashContext) GetChannel() *discord.Channel { return ctx.Channel }
   246  
   247  // GetParentChannel ...
   248  func (ctx *SlashContext) GetParentChannel() *discord.Channel { return ctx.ParentChannel }
   249  
   250  // User ...
   251  func (ctx *SlashContext) User() discord.User { return ctx.Author }
   252  
   253  // GetMember ...
   254  func (ctx *SlashContext) GetMember() *discord.Member { return ctx.Member }
   255  
   256  // Send ...
   257  func (ctx *SlashContext) Send(content string, embeds ...discord.Embed) (msg *discord.Message, err error) {
   258  	err = ctx.SendX(content, embeds...)
   259  	if err != nil {
   260  		return
   261  	}
   262  
   263  	return ctx.Original()
   264  }
   265  
   266  // Sendf ...
   267  func (ctx *SlashContext) Sendf(tmpl string, args ...interface{}) (msg *discord.Message, err error) {
   268  	err = ctx.SendfX(tmpl, args...)
   269  	if err != nil {
   270  		return
   271  	}
   272  
   273  	return ctx.Original()
   274  }
   275  
   276  // GuildPerms returns the global (guild) permissions of this Context's user.
   277  // If in DMs, it will return the permissions users have in DMs.
   278  func (ctx *SlashContext) GuildPerms() (perms discord.Permissions) {
   279  	if ctx.Guild == nil || ctx.Member == nil {
   280  		return discord.PermissionViewChannel | discord.PermissionSendMessages | discord.PermissionAddReactions | discord.PermissionReadMessageHistory
   281  	}
   282  
   283  	if ctx.Guild.OwnerID == ctx.Author.ID {
   284  		return discord.PermissionAll
   285  	}
   286  
   287  	for _, id := range ctx.Member.RoleIDs {
   288  		for _, r := range ctx.Guild.Roles {
   289  			if id == r.ID {
   290  				if r.Permissions.Has(discord.PermissionAdministrator) {
   291  					return discord.PermissionAll
   292  				}
   293  
   294  				perms |= r.Permissions
   295  				break
   296  			}
   297  		}
   298  	}
   299  
   300  	return perms
   301  }
   302  
   303  // SendComponents sends a message with components
   304  func (ctx *SlashContext) SendComponents(components []discord.Component, content string, embeds ...discord.Embed) (*discord.Message, error) {
   305  	data := api.InteractionResponse{
   306  		Type: api.MessageInteractionWithSource,
   307  		Data: &api.InteractionResponseData{
   308  			AllowedMentions: ctx.Router.DefaultMentions,
   309  			Content:         option.NewNullableString(content),
   310  			Embeds:          &embeds,
   311  			Components:      &components,
   312  		},
   313  	}
   314  
   315  	err := ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, data)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	return ctx.Original()
   321  }