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 }