github.com/starshine-sys/bcr@v0.21.0/paged_embed.go (about) 1 package bcr 2 3 import ( 4 "errors" 5 "fmt" 6 "math" 7 "time" 8 9 "github.com/diamondburned/arikawa/v3/discord" 10 ) 11 12 // ErrNoEmbeds is returned if PagedEmbed() is called without any embeds 13 var ErrNoEmbeds = errors.New("PagedEmbed: no embeds") 14 15 // PagedEmbed sends a slice of embeds, and attaches reaction handlers to flip through them. 16 // if extendedReactions is true, also add delete, first page, and last page reactions. 17 func (ctx *Context) PagedEmbed(embeds []discord.Embed, extendedReactions bool) (msg *discord.Message, err error) { 18 msg, _, err = ctx.PagedEmbedTimeout(embeds, extendedReactions, 15*time.Minute) 19 return 20 } 21 22 // PagedEmbedTimeout creates a paged embed (see PagedEmbed) that times out after the given time. 23 // It also returns a timer that can be used to cancel the attached reaction-clearing timer. 24 func (ctx *Context) PagedEmbedTimeout(embeds []discord.Embed, extendedReactions bool, timeout time.Duration) (msg *discord.Message, timer *time.Timer, err error) { 25 // if there's no embeds, return 26 if len(embeds) == 0 { 27 return nil, nil, ErrNoEmbeds 28 } 29 30 // set additional parameters, used for pagination 31 ctx.AdditionalParams["page"] = 0 32 ctx.AdditionalParams["embeds"] = embeds 33 34 // send the first embed 35 msg, err = ctx.Send("", embeds[0]) 36 if err != nil { 37 return 38 } 39 40 // this doesn't seem to work rn for some reason 41 // timer = time.AfterFunc(timeout, func() { 42 // ctx.State.DeleteAllReactions(msg.ChannelID, msg.ID) 43 // }) 44 45 // add :x: handler 46 ctx.AddReactionHandlerWithTimeout(msg.ID, ctx.Author.ID, "❌", true, false, timeout, func(*Context) { 47 // timer.Stop() 48 err = ctx.State.DeleteMessage(ctx.Channel.ID, msg.ID, "") 49 if err != nil { 50 ctx.Router.Logger.Error("deleting message: %v", err) 51 } 52 }) 53 54 // if there's only one embed, that's it! no pager emoji needed 55 if len(embeds) == 1 { 56 return 57 } 58 59 // react with all required emoji--afawk there's no more concise way to do this 60 if extendedReactions { 61 if err = ctx.State.React(ctx.Channel.ID, msg.ID, "❌"); err != nil { 62 return 63 } 64 if err = ctx.State.React(ctx.Channel.ID, msg.ID, "⏪"); err != nil { 65 return 66 } 67 } 68 if err = ctx.State.React(ctx.Channel.ID, msg.ID, "⬅️"); err != nil { 69 return 70 } 71 if err = ctx.State.React(ctx.Channel.ID, msg.ID, "➡️"); err != nil { 72 return 73 } 74 if extendedReactions { 75 if err = ctx.State.React(ctx.Channel.ID, msg.ID, "⏩"); err != nil { 76 return 77 } 78 } 79 80 // add handlers for the reactions 81 ctx.AddReactionHandlerWithTimeoutRemove(msg.ID, ctx.Author.ID, "⬅️", false, true, timeout, func(ctx *Context) { 82 page := ctx.AdditionalParams["page"].(int) 83 embeds := ctx.AdditionalParams["embeds"].([]discord.Embed) 84 85 if page == 0 { 86 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[len(embeds)-1]) 87 ctx.AdditionalParams["page"] = len(embeds) - 1 88 return 89 } 90 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[page-1]) 91 ctx.AdditionalParams["page"] = page - 1 92 }) 93 94 ctx.AddReactionHandlerWithTimeoutRemove(msg.ID, ctx.Author.ID, "➡️", false, true, timeout, func(ctx *Context) { 95 page := ctx.AdditionalParams["page"].(int) 96 embeds := ctx.AdditionalParams["embeds"].([]discord.Embed) 97 98 if page >= len(embeds)-1 { 99 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[0]) 100 ctx.AdditionalParams["page"] = 0 101 return 102 } 103 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[page+1]) 104 ctx.AdditionalParams["page"] = page + 1 105 }) 106 107 if extendedReactions { 108 ctx.AddReactionHandlerWithTimeoutRemove(msg.ID, ctx.Author.ID, "⏪", false, true, timeout, func(ctx *Context) { 109 embeds := ctx.AdditionalParams["embeds"].([]discord.Embed) 110 111 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[0]) 112 ctx.AdditionalParams["page"] = 0 113 }) 114 115 ctx.AddReactionHandlerWithTimeoutRemove(msg.ID, ctx.Author.ID, "⏩", false, true, timeout, func(ctx *Context) { 116 embeds := ctx.AdditionalParams["embeds"].([]discord.Embed) 117 118 ctx.State.EditEmbeds(ctx.Channel.ID, msg.ID, embeds[len(embeds)-1]) 119 ctx.AdditionalParams["page"] = len(embeds) - 1 120 }) 121 } 122 return 123 } 124 125 // FieldPaginator paginates embed fields, for use in ctx.PagedEmbed 126 func FieldPaginator(title, description string, colour discord.Color, fields []discord.EmbedField, perPage int) []discord.Embed { 127 var ( 128 embeds []discord.Embed 129 count int 130 131 pages = 1 132 buf = discord.Embed{ 133 Title: title, 134 Description: description, 135 Color: colour, 136 Footer: &discord.EmbedFooter{ 137 Text: fmt.Sprintf("Page 1/%v", math.Ceil(float64(len(fields))/float64(perPage))), 138 }, 139 } 140 ) 141 142 for _, field := range fields { 143 if count >= perPage { 144 embeds = append(embeds, buf) 145 buf = discord.Embed{ 146 Title: title, 147 Description: description, 148 Color: colour, 149 Footer: &discord.EmbedFooter{ 150 Text: fmt.Sprintf("Page %v/%v", pages+1, math.Ceil(float64(len(fields))/float64(perPage))), 151 }, 152 } 153 count = 0 154 pages++ 155 } 156 buf.Fields = append(buf.Fields, field) 157 count++ 158 } 159 160 embeds = append(embeds, buf) 161 162 return embeds 163 } 164 165 // StringPaginator paginates strings, for use in ctx.PagedEmbed 166 func StringPaginator(title string, colour discord.Color, slice []string, perPage int) []discord.Embed { 167 var ( 168 embeds []discord.Embed 169 count int 170 171 pages = 1 172 buf = discord.Embed{ 173 Title: title, 174 Color: colour, 175 Footer: &discord.EmbedFooter{ 176 Text: fmt.Sprintf("Page 1/%v", math.Ceil(float64(len(slice))/float64(perPage))), 177 }, 178 } 179 ) 180 181 for _, s := range slice { 182 if count >= perPage { 183 embeds = append(embeds, buf) 184 buf = discord.Embed{ 185 Title: title, 186 Color: colour, 187 Footer: &discord.EmbedFooter{ 188 Text: fmt.Sprintf("Page %v/%v", pages+1, math.Ceil(float64(len(slice))/float64(perPage))), 189 }, 190 } 191 count = 0 192 pages++ 193 } 194 buf.Description += s 195 count++ 196 } 197 198 embeds = append(embeds, buf) 199 200 return embeds 201 }