github.com/starshine-sys/bcr@v0.21.0/ctx_buttons_slash.go (about) 1 package bcr 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "emperror.dev/errors" 9 10 "github.com/diamondburned/arikawa/v3/api" 11 "github.com/diamondburned/arikawa/v3/discord" 12 "github.com/diamondburned/arikawa/v3/gateway" 13 "github.com/diamondburned/arikawa/v3/utils/json/option" 14 ) 15 16 type slashButtonInfo struct { 17 ctx *SlashContext 18 fn func(*SlashContext, *gateway.InteractionCreateEvent) 19 delete bool 20 } 21 22 // AddButtonHandler adds a handler for the given message ID, user ID, and custom ID 23 func (ctx *SlashContext) AddButtonHandler( 24 msg discord.MessageID, 25 user discord.UserID, 26 customID string, 27 del bool, 28 fn func(*SlashContext, *gateway.InteractionCreateEvent), 29 ) ButtonRemoveFunc { 30 ctx.Router.slashButtonMu.Lock() 31 defer ctx.Router.slashButtonMu.Unlock() 32 33 ctx.Router.slashButtons[buttonKey{msg, user, customID}] = slashButtonInfo{ctx, fn, del} 34 35 return func() { 36 ctx.Router.slashButtonMu.Lock() 37 delete(ctx.Router.slashButtons, buttonKey{msg, user, customID}) 38 ctx.Router.slashButtonMu.Unlock() 39 } 40 } 41 42 // ButtonPages is like PagedEmbed but uses buttons instead of reactions. 43 func (ctx *SlashContext) ButtonPages(embeds []discord.Embed, timeout time.Duration) (msg *discord.Message, rmFunc func(), err error) { 44 return ctx.ButtonPagesWithComponents(embeds, timeout, nil) 45 } 46 47 // ButtonPagesWithComponents is like ButtonPages but adds the given components before the buttons used for pagination. 48 func (ctx *SlashContext) ButtonPagesWithComponents(embeds []discord.Embed, timeout time.Duration, components []discord.Component) (msg *discord.Message, rmFunc func(), err error) { 49 rmFunc = func() {} 50 51 if len(embeds) == 0 { 52 return nil, func() {}, errors.New("no embeds") 53 } 54 55 ctx.AdditionalParams["page"] = 0 56 57 if len(embeds) == 1 { 58 err = ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, api.InteractionResponse{ 59 Type: api.MessageInteractionWithSource, 60 Data: &api.InteractionResponseData{ 61 Embeds: &[]discord.Embed{embeds[0]}, 62 Components: &components, 63 }, 64 }) 65 if err != nil { 66 return 67 } 68 69 msg, err = ctx.Original() 70 return 71 } 72 73 components = append(components, discord.ActionRowComponent{ 74 Components: []discord.Component{ 75 discord.ButtonComponent{ 76 Emoji: &discord.ButtonEmoji{ 77 Name: "⏪", 78 }, 79 Style: discord.SecondaryButton, 80 CustomID: "first", 81 }, 82 discord.ButtonComponent{ 83 Emoji: &discord.ButtonEmoji{ 84 Name: "⬅️", 85 }, 86 Style: discord.SecondaryButton, 87 CustomID: "prev", 88 }, 89 discord.ButtonComponent{ 90 Emoji: &discord.ButtonEmoji{ 91 Name: "➡️", 92 }, 93 Style: discord.SecondaryButton, 94 CustomID: "next", 95 }, 96 discord.ButtonComponent{ 97 Emoji: &discord.ButtonEmoji{ 98 Name: "⏩", 99 }, 100 Style: discord.SecondaryButton, 101 CustomID: "last", 102 }, 103 discord.ButtonComponent{ 104 Emoji: &discord.ButtonEmoji{ 105 Name: "❌", 106 }, 107 Style: discord.SecondaryButton, 108 CustomID: "cross", 109 }, 110 }, 111 }) 112 113 err = ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, api.InteractionResponse{ 114 Type: api.MessageInteractionWithSource, 115 Data: &api.InteractionResponseData{ 116 Embeds: &[]discord.Embed{embeds[0]}, 117 Components: &components, 118 }, 119 }) 120 if err != nil { 121 return 122 } 123 124 msg, err = ctx.Original() 125 126 page := 0 127 128 prev := ctx.AddButtonHandler(msg.ID, ctx.Author.ID, "prev", false, func(ctx *SlashContext, ev *gateway.InteractionCreateEvent) { 129 if page == 0 { 130 page = len(embeds) - 1 131 ctx.AdditionalParams["page"] = len(embeds) - 1 132 } else { 133 page-- 134 ctx.AdditionalParams["page"] = page 135 } 136 137 err = ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 138 Type: api.UpdateMessage, 139 Data: &api.InteractionResponseData{ 140 Embeds: &[]discord.Embed{embeds[page]}, 141 }, 142 }) 143 if err != nil { 144 ctx.Router.Logger.Error("Editing message: %v", err) 145 } 146 }) 147 148 next := ctx.AddButtonHandler(msg.ID, ctx.Author.ID, "next", false, func(ctx *SlashContext, ev *gateway.InteractionCreateEvent) { 149 if page >= len(embeds)-1 { 150 page = 0 151 ctx.AdditionalParams["page"] = 0 152 } else { 153 page++ 154 ctx.AdditionalParams["page"] = page 155 } 156 157 err = ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 158 Type: api.UpdateMessage, 159 Data: &api.InteractionResponseData{ 160 Embeds: &[]discord.Embed{embeds[page]}, 161 }, 162 }) 163 if err != nil { 164 ctx.Router.Logger.Error("Editing message: %v", err) 165 } 166 }) 167 168 first := ctx.AddButtonHandler(msg.ID, ctx.Author.ID, "first", false, func(ctx *SlashContext, ev *gateway.InteractionCreateEvent) { 169 page = 0 170 ctx.AdditionalParams["page"] = 0 171 172 err = ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 173 Type: api.UpdateMessage, 174 Data: &api.InteractionResponseData{ 175 Embeds: &[]discord.Embed{embeds[page]}, 176 }, 177 }) 178 if err != nil { 179 ctx.Router.Logger.Error("Editing message: %v", err) 180 } 181 }) 182 183 last := ctx.AddButtonHandler(msg.ID, ctx.Author.ID, "last", false, func(ctx *SlashContext, ev *gateway.InteractionCreateEvent) { 184 page = len(embeds) - 1 185 ctx.AdditionalParams["page"] = len(embeds) - 1 186 187 err = ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 188 Type: api.UpdateMessage, 189 Data: &api.InteractionResponseData{ 190 Embeds: &[]discord.Embed{embeds[page]}, 191 }, 192 }) 193 if err != nil { 194 ctx.Router.Logger.Error("Editing message: %v", err) 195 } 196 }) 197 198 var o sync.Once 199 200 cross := ctx.AddButtonHandler(msg.ID, ctx.Author.ID, "cross", false, func(ctx *SlashContext, ev *gateway.InteractionCreateEvent) { 201 err = ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 202 Type: api.UpdateMessage, 203 Data: &api.InteractionResponseData{ 204 Components: &[]discord.Component{}, 205 }, 206 }) 207 if err != nil { 208 ctx.Router.Logger.Error("Editing message: %v", err) 209 } 210 }) 211 212 rmFunc = func() { 213 o.Do(func() { 214 _, err = ctx.State.EditMessageComplex(msg.ChannelID, msg.ID, api.EditMessageData{ 215 Components: &[]discord.Component{}, 216 }) 217 if err != nil { 218 ctx.Router.Logger.Error("Editing message: %v", err) 219 } 220 221 prev() 222 next() 223 first() 224 last() 225 cross() 226 }) 227 } 228 229 time.AfterFunc(timeout, rmFunc) 230 return msg, rmFunc, err 231 } 232 233 // ConfirmButton confirms a prompt with buttons or "yes"/"no" messages. 234 func (ctx *SlashContext) ConfirmButton(userID discord.UserID, data ConfirmData) (yes, timeout bool) { 235 if data.Message == "" && len(data.Embeds) == 0 { 236 return 237 } 238 239 if data.YesPrompt == "" { 240 data.YesPrompt = "Confirm" 241 } 242 if data.YesStyle == 0 { 243 data.YesStyle = discord.PrimaryButton 244 } 245 if data.NoPrompt == "" { 246 data.NoPrompt = "Cancel" 247 } 248 if data.NoStyle == 0 { 249 data.NoStyle = discord.SecondaryButton 250 } 251 if data.Timeout == 0 { 252 data.Timeout = time.Minute 253 } 254 255 con, cancel := context.WithTimeout(context.Background(), data.Timeout) 256 defer cancel() 257 258 err := ctx.State.RespondInteraction(ctx.InteractionID, ctx.InteractionToken, api.InteractionResponse{ 259 Data: &api.InteractionResponseData{ 260 Content: option.NewNullableString(data.Message), 261 Embeds: &data.Embeds, 262 263 Components: &[]discord.Component{ 264 discord.ActionRowComponent{ 265 Components: []discord.Component{ 266 discord.ButtonComponent{ 267 Label: data.YesPrompt, 268 Style: data.YesStyle, 269 CustomID: "yes", 270 }, 271 discord.ButtonComponent{ 272 Label: data.NoPrompt, 273 Style: data.NoStyle, 274 CustomID: "no", 275 }, 276 }, 277 }, 278 }, 279 }, 280 }) 281 if err != nil { 282 return 283 } 284 285 msg, err := ctx.Original() 286 if err != nil { 287 return 288 } 289 290 v := ctx.State.WaitFor(con, func(ev interface{}) bool { 291 v, ok := ev.(*gateway.InteractionCreateEvent) 292 if !ok { 293 return false 294 } 295 296 if v.Message == nil || (v.Member == nil && v.User == nil) { 297 return false 298 } 299 300 if v.Message.ID != msg.ID { 301 return false 302 } 303 304 var uID discord.UserID 305 if v.Member != nil { 306 uID = v.Member.User.ID 307 } else { 308 uID = v.User.ID 309 } 310 311 if uID != userID { 312 return false 313 } 314 315 if v.Data.CustomID == "" { 316 return false 317 } 318 319 yes = v.Data.CustomID == "yes" 320 timeout = false 321 return true 322 }) 323 324 if v == nil { 325 return false, true 326 } 327 328 upd := &[]discord.Component{ 329 discord.ActionRowComponent{ 330 Components: []discord.Component{ 331 discord.ButtonComponent{ 332 Label: data.YesPrompt, 333 Style: data.YesStyle, 334 CustomID: "yes", 335 Disabled: true, 336 }, 337 discord.ButtonComponent{ 338 Label: data.NoPrompt, 339 Style: data.NoStyle, 340 CustomID: "no", 341 Disabled: true, 342 }, 343 }, 344 }, 345 } 346 347 if ev, ok := v.(*gateway.InteractionCreateEvent); ok { 348 ctx.State.RespondInteraction(ev.ID, ev.Token, api.InteractionResponse{ 349 Type: api.UpdateMessage, 350 Data: &api.InteractionResponseData{ 351 Components: upd, 352 }, 353 }) 354 } 355 356 return 357 }