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

     1  package bcr
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	"github.com/diamondburned/arikawa/v3/api"
     8  	"github.com/diamondburned/arikawa/v3/bot/extras/shellwords"
     9  	"github.com/diamondburned/arikawa/v3/discord"
    10  	"github.com/diamondburned/arikawa/v3/gateway"
    11  	"github.com/diamondburned/arikawa/v3/state"
    12  	"github.com/spf13/pflag"
    13  )
    14  
    15  // Errors related to getting the context
    16  var (
    17  	ErrChannel   = errors.New("context: couldn't get channel")
    18  	ErrGuild     = errors.New("context: couldn't get guild")
    19  	ErrNoBotUser = errors.New("context: couldn't get bot user")
    20  
    21  	ErrEmptyMessage = errors.New("context: message was empty")
    22  )
    23  
    24  // Prefixer returns the prefix used and the length. If the message doesn't start with a valid prefix, it returns -1.
    25  // Note that this function should still use the built-in r.Prefixes for mention prefixes
    26  type Prefixer func(m discord.Message) int
    27  
    28  // DefaultPrefixer ...
    29  func (r *Router) DefaultPrefixer(m discord.Message) int {
    30  	for _, p := range r.Prefixes {
    31  		if strings.HasPrefix(strings.ToLower(m.Content), p) {
    32  			return len(p)
    33  		}
    34  	}
    35  	return -1
    36  }
    37  
    38  var _ Contexter = (*Context)(nil)
    39  
    40  // Context is a command context
    41  type Context struct {
    42  	// Command and Prefix contain the invoked command's name and prefix, respectively.
    43  	// Note that Command won't be accurate if the invoked command was a subcommand, use FullCommandPath for that.
    44  	Command string
    45  	Prefix  string
    46  
    47  	FullCommandPath []string
    48  
    49  	Args    []string
    50  	RawArgs string
    51  
    52  	Flags *pflag.FlagSet
    53  
    54  	InternalArgs []string
    55  	pos          int
    56  
    57  	State   *state.State
    58  	ShardID int
    59  
    60  	Bot *discord.User
    61  
    62  	// Info about the message
    63  	Message discord.Message
    64  	Channel *discord.Channel
    65  	Guild   *discord.Guild
    66  	Author  discord.User
    67  
    68  	// ParentChannel is only filled if ctx.Channel is a thread
    69  	ParentChannel *discord.Channel
    70  
    71  	// Note: Member is nil for non-guild messages
    72  	Member *discord.Member
    73  
    74  	// The command and the router used
    75  	Cmd    *Command
    76  	Router *Router
    77  
    78  	AdditionalParams map[string]interface{}
    79  
    80  	// Internal use for the Get* methods.
    81  	// Not intended to be changed by the end user, exported so it can be created if context is not made through NewContext.
    82  	FlagMap map[string]interface{}
    83  
    84  	origMessage *discord.Message
    85  }
    86  
    87  // NewContext returns a new message context
    88  func (r *Router) NewContext(m *gateway.MessageCreateEvent) (ctx *Context, err error) {
    89  	messageContent := m.Content
    90  
    91  	var p int
    92  	if p = r.Prefixer(m.Message); p != -1 {
    93  		messageContent = messageContent[p:]
    94  	} else {
    95  		return nil, ErrEmptyMessage
    96  	}
    97  	messageContent = strings.TrimSpace(messageContent)
    98  
    99  	message, err := shellwords.Parse(messageContent)
   100  	if err != nil {
   101  		message = strings.Split(messageContent, " ")
   102  	}
   103  	if len(message) == 0 {
   104  		return nil, ErrEmptyMessage
   105  	}
   106  	command := strings.ToLower(message[0])
   107  	args := []string{}
   108  	if len(message) > 1 {
   109  		args = message[1:]
   110  	}
   111  
   112  	raw := TrimPrefixesSpace(messageContent, message[0])
   113  
   114  	// create the context
   115  	ctx = &Context{
   116  		Command: command,
   117  		Prefix:  m.Content[:p],
   118  
   119  		InternalArgs:     args,
   120  		Args:             args,
   121  		Message:          m.Message,
   122  		Author:           m.Author,
   123  		Member:           m.Member,
   124  		RawArgs:          raw,
   125  		Router:           r,
   126  		Bot:              r.Bot,
   127  		AdditionalParams: make(map[string]interface{}),
   128  		FlagMap:          make(map[string]interface{}),
   129  	}
   130  
   131  	ctx.State, ctx.ShardID = r.StateFromGuildID(m.GuildID)
   132  
   133  	// get the channel
   134  	ctx.Channel, err = ctx.State.Channel(m.ChannelID)
   135  	if err != nil {
   136  		return ctx, ErrChannel
   137  	}
   138  
   139  	if ctx.Thread() {
   140  		ctx.ParentChannel, err = ctx.State.Channel(ctx.Channel.ParentID)
   141  		if err != nil {
   142  			return ctx, ErrChannel
   143  		}
   144  	}
   145  
   146  	// get guild
   147  	if m.GuildID.IsValid() {
   148  		ctx.Guild, err = ctx.State.Guild(m.GuildID)
   149  		if err != nil {
   150  			return ctx, ErrGuild
   151  		}
   152  		ctx.Guild.Roles, err = ctx.State.Roles(m.GuildID)
   153  		if err != nil {
   154  			return ctx, ErrGuild
   155  		}
   156  	}
   157  
   158  	return ctx, err
   159  }
   160  
   161  // DisplayName returns the context user's displayed name (either username without discriminator, or nickname)
   162  func (ctx *Context) DisplayName() string {
   163  	if ctx.Member == nil {
   164  		return ctx.Author.Username
   165  	}
   166  	if ctx.Member.Nick == "" {
   167  		return ctx.Author.Username
   168  	}
   169  	return ctx.Member.Nick
   170  }
   171  
   172  // Thread returns true if the context is in a thread channel.
   173  // If this function returns true, ctx.ParentChannel will be non-nil.
   174  func (ctx *Context) Thread() bool {
   175  	return ctx.Channel.Type == discord.GuildNewsThread || ctx.Channel.Type == discord.GuildPublicThread || ctx.Channel.Type == discord.GuildPrivateThread
   176  }
   177  
   178  // Session returns this context's state.
   179  func (ctx *Context) Session() *state.State {
   180  	return ctx.State
   181  }
   182  
   183  // GetGuild ...
   184  func (ctx *Context) GetGuild() *discord.Guild { return ctx.Guild }
   185  
   186  // GetChannel ...
   187  func (ctx *Context) GetChannel() *discord.Channel { return ctx.Channel }
   188  
   189  // GetParentChannel ...
   190  func (ctx *Context) GetParentChannel() *discord.Channel { return ctx.ParentChannel }
   191  
   192  // User ...
   193  func (ctx *Context) User() discord.User { return ctx.Author }
   194  
   195  // GetMember ...
   196  func (ctx *Context) GetMember() *discord.Member { return ctx.Member }
   197  
   198  // EditOriginal edits the original response message.
   199  func (ctx *Context) EditOriginal(data api.EditInteractionResponseData) (*discord.Message, error) {
   200  	if ctx.origMessage == nil {
   201  		return nil, errors.New("no original message to edit")
   202  	}
   203  
   204  	emd := api.EditMessageData{
   205  		Content:         data.Content,
   206  		Embeds:          data.Embeds,
   207  		Components:      data.Components,
   208  		AllowedMentions: data.AllowedMentions,
   209  		Attachments:     data.Attachments,
   210  	}
   211  
   212  	return ctx.State.EditMessageComplex(ctx.origMessage.ChannelID, ctx.origMessage.ID, emd)
   213  }