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 }