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  }