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

     1  package bcr
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/diamondburned/arikawa/v3/gateway"
    10  
    11  	"github.com/diamondburned/arikawa/v3/discord"
    12  )
    13  
    14  // AddReactionHandlerWithTimeout is like AddReactionHandler but accepts a timeout
    15  func (ctx *Context) AddReactionHandlerWithTimeout(
    16  	msg discord.MessageID,
    17  	user discord.UserID,
    18  	reaction string,
    19  	deleteOnTrigger, deleteReaction bool,
    20  	timeout time.Duration,
    21  	fn func(*Context),
    22  ) {
    23  	ctx.Router.reactionMu.Lock()
    24  
    25  	ctx.Router.reactions[reactionKey{
    26  		messageID: msg,
    27  		emoji:     discord.APIEmoji(reaction),
    28  	}] = reactionInfo{
    29  		userID:          user,
    30  		ctx:             ctx,
    31  		fn:              fn,
    32  		deleteOnTrigger: deleteOnTrigger,
    33  		deleteReaction:  deleteReaction,
    34  	}
    35  
    36  	ctx.Router.reactionMu.Unlock()
    37  
    38  	// delete handlers after the set time to stop them from building up
    39  	time.AfterFunc(ctx.Router.ReactTimeout, func() {
    40  		ctx.Router.DeleteReactions(msg)
    41  	})
    42  }
    43  
    44  // AddReactionHandlerRemove adds a reaction handler for the given message.
    45  // This handler is also called when the reaction is *removed*.
    46  func (ctx *Context) AddReactionHandlerRemove(
    47  	msg discord.MessageID,
    48  	user discord.UserID,
    49  	reaction string,
    50  	deleteOnTrigger, deleteReaction bool,
    51  	fn func(*Context),
    52  ) {
    53  	ctx.AddReactionHandlerWithTimeoutRemove(msg, user, reaction, deleteOnTrigger, deleteReaction, ctx.Router.ReactTimeout, fn)
    54  	return
    55  }
    56  
    57  // AddReactionHandlerWithTimeoutRemove is like AddReactionHandlerRemove but accepts a timeout
    58  func (ctx *Context) AddReactionHandlerWithTimeoutRemove(
    59  	msg discord.MessageID,
    60  	user discord.UserID,
    61  	reaction string,
    62  	deleteOnTrigger, deleteReaction bool,
    63  	timeout time.Duration,
    64  	fn func(*Context),
    65  ) {
    66  	ctx.Router.reactionMu.Lock()
    67  
    68  	ctx.Router.reactions[reactionKey{
    69  		messageID: msg,
    70  		emoji:     discord.APIEmoji(reaction),
    71  	}] = reactionInfo{
    72  		userID:          user,
    73  		ctx:             ctx,
    74  		fn:              fn,
    75  		deleteOnTrigger: deleteOnTrigger,
    76  		deleteReaction:  deleteReaction,
    77  		respondToRemove: true,
    78  	}
    79  
    80  	ctx.Router.reactionMu.Unlock()
    81  
    82  	// delete handlers after the set time to stop them from building up
    83  	time.AfterFunc(ctx.Router.ReactTimeout, func() {
    84  		ctx.Router.DeleteReactions(msg)
    85  	})
    86  }
    87  
    88  // AddReactionHandler adds a reaction handler for the given message
    89  func (ctx *Context) AddReactionHandler(
    90  	msg discord.MessageID,
    91  	user discord.UserID,
    92  	reaction string,
    93  	deleteOnTrigger, deleteReaction bool,
    94  	fn func(*Context),
    95  ) {
    96  	ctx.AddReactionHandlerWithTimeout(msg, user, reaction, deleteOnTrigger, deleteReaction, ctx.Router.ReactTimeout, fn)
    97  	return
    98  }
    99  
   100  // YesNoHandler adds a reaction handler for the given message.
   101  // This handler times out after one minute. If it timed out, `false` and `true` are returned, respectively.
   102  func (ctx *Context) YesNoHandler(msg discord.Message, user discord.UserID) (yes, timeout bool) {
   103  	return ctx.YesNoHandlerWithTimeout(msg, user, time.Minute)
   104  }
   105  
   106  // YesNoHandlerWithTimeout is like YesNoHandler but lets you specify your own timeout.
   107  func (ctx *Context) YesNoHandlerWithTimeout(msg discord.Message, user discord.UserID, t time.Duration) (yes, timeout bool) {
   108  	c, cancel := context.WithTimeout(context.Background(), t)
   109  
   110  	go func() {
   111  		// react with the correct emojis
   112  		// this is run in a goroutine to add the handler immediately
   113  		ctx.State.React(msg.ChannelID, msg.ID, discord.APIEmoji("✅"))
   114  		ctx.State.React(msg.ChannelID, msg.ID, discord.APIEmoji("❌"))
   115  	}()
   116  
   117  	defer cancel()
   118  	ev := ctx.State.WaitFor(c, func(ev interface{}) bool {
   119  		// first, try a reaction add event
   120  		v, ok := ev.(*gateway.MessageReactionAddEvent)
   121  		if !ok {
   122  			// try a message create event
   123  			m, ok := ev.(*gateway.MessageCreateEvent)
   124  			if !ok {
   125  				return false
   126  			}
   127  
   128  			// return true if
   129  			// - the channel ID and user ID are correct
   130  			// - the message is "yes", "y", "no", or "n"
   131  			return m.ChannelID == msg.ChannelID && m.Author.ID == user &&
   132  				(strings.ToLower(m.Content) == "yes" || strings.ToLower(m.Content) == "y" || strings.ToLower(m.Content) == "no" || strings.ToLower(m.Content) == "n")
   133  		}
   134  
   135  		// return true if
   136  		// - the channel ID, message ID, and user ID are correct
   137  		// - the emoji is the yes or no emoji
   138  		return v.ChannelID == msg.ChannelID && v.MessageID == msg.ID && v.UserID == user &&
   139  			(v.Emoji.APIString() == "✅" || v.Emoji.APIString() == "❌")
   140  	})
   141  
   142  	// if the event timed out, return
   143  	if ev == nil {
   144  		return false, true
   145  	}
   146  
   147  	// try a message reaction event
   148  	v, ok := ev.(*gateway.MessageReactionAddEvent)
   149  	if !ok {
   150  		m, ok := ev.(*gateway.MessageCreateEvent)
   151  		if !ok {
   152  			return false, false
   153  		}
   154  		// if not, it's a message create event
   155  		return strings.EqualFold(m.Content, "yes") || strings.EqualFold(m.Content, "y"), false
   156  	}
   157  	return v.Emoji.APIString() == "✅", false
   158  }
   159  
   160  var (
   161  	// ErrorTimedOut is returned when WaitForReaction times out
   162  	ErrorTimedOut = errors.New("context: timed out waiting for reaction")
   163  	// ErrorFailedConversion is returned when WaitForReaction can't convert the interface{} to a MessageReactionAddEvent
   164  	ErrorFailedConversion = errors.New("context: failed conversion in WaitForReaction")
   165  )
   166  
   167  // WaitForReaction calls WaitForReactionWithTimeout with a 3-minute timeout
   168  func (ctx *Context) WaitForReaction(msg discord.Message, user discord.UserID) (ev *gateway.MessageReactionAddEvent, err error) {
   169  	return ctx.WaitForReactionWithTimeout(msg, user, 5*time.Minute)
   170  }
   171  
   172  // WaitForReactionWithTimeout waits for a reaction with a user-given timeout
   173  func (ctx *Context) WaitForReactionWithTimeout(msg discord.Message, user discord.UserID, timeout time.Duration) (ev *gateway.MessageReactionAddEvent, err error) {
   174  	c, cancel := context.WithTimeout(context.Background(), timeout)
   175  	defer cancel()
   176  
   177  	v := ctx.State.WaitFor(c, func(ev interface{}) bool {
   178  		v, ok := ev.(*gateway.MessageReactionAddEvent)
   179  		if !ok {
   180  			return false
   181  		}
   182  		return v.ChannelID == msg.ChannelID && v.MessageID == msg.ID && v.UserID == user
   183  	})
   184  
   185  	if v == nil {
   186  		return nil, ErrorTimedOut
   187  	}
   188  
   189  	ev, ok := v.(*gateway.MessageReactionAddEvent)
   190  	if !ok {
   191  		return nil, ErrorFailedConversion
   192  	}
   193  	return ev, nil
   194  }