diff --git a/app/http/endpoints/api/panel/multipanelcreate.go b/app/http/endpoints/api/panel/multipanelcreate.go index 2f0fd24..02d7eae 100644 --- a/app/http/endpoints/api/panel/multipanelcreate.go +++ b/app/http/endpoints/api/panel/multipanelcreate.go @@ -12,13 +12,8 @@ import ( "github.com/TicketsBot/database" "github.com/gin-gonic/gin" "github.com/rxdn/gdl/objects/channel" - "github.com/rxdn/gdl/objects/channel/embed" - "github.com/rxdn/gdl/objects/guild/emoji" - "github.com/rxdn/gdl/objects/interaction/component" - "github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest/request" "golang.org/x/sync/errgroup" - "math" ) type multiPanelCreateData struct { @@ -29,6 +24,16 @@ type multiPanelCreateData struct { Panels []int `json:"panels"` } +func (d *multiPanelCreateData) IntoMessageData(isPremium bool) multiPanelMessageData { + return multiPanelMessageData{ + ChannelId: d.ChannelId, + Title: d.Title, + Content: d.Content, + Colour: int(d.Colour), + IsPremium: isPremium, + } +} + func MultiPanelCreate(ctx *gin.Context) { guildId := ctx.Keys["guildid"].(uint64) @@ -62,7 +67,8 @@ func MultiPanelCreate(ctx *gin.Context) { return } - messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None, panels) + messageData := data.IntoMessageData(premiumTier > premium.None) + messageId, err := messageData.send(&botContext, panels) if err != nil { var unwrapped request.RestError if errors.As(err, &unwrapped); unwrapped.StatusCode == 403 { @@ -197,61 +203,3 @@ func (d *multiPanelCreateData) validatePanels(guildId uint64) (panels []database return } - -func (d *multiPanelCreateData) sendEmbed(ctx *botcontext.BotContext, isPremium bool, panels []database.Panel) (uint64, error) { - e := embed.NewEmbed(). - SetTitle(d.Title). - SetDescription(d.Content). - SetColor(int(d.Colour)) - - if !isPremium { - // TODO: Don't harcode - e.SetFooter("Powered by ticketsbot.net", "https://cdn.discordapp.com/avatars/508391840525975553/ac2647ffd4025009e2aa852f719a8027.png?size=256") - } - - buttons := make([]component.Component, len(panels)) - for i, panel := range panels { - var buttonEmoji *emoji.Emoji - if panel.ReactionEmote != "" { - buttonEmoji = &emoji.Emoji{ - Name: panel.ReactionEmote, - } - } - - buttons[i] = component.BuildButton(component.Button{ - Label: panel.Title, - CustomId: panel.CustomId, - Style: component.ButtonStyle(panel.ButtonStyle), - Emoji: buttonEmoji, - }) - } - - var rows []component.Component - for i := 0; i <= int(math.Ceil(float64(len(buttons)/5))); i++ { - lb := i * 5 - ub := lb + 5 - - if ub >= len(buttons) { - ub = len(buttons) - } - - if lb >= ub { - break - } - - row := component.BuildActionRow(buttons[lb:ub]...) - rows = append(rows, row) - } - - data := rest.CreateMessageData{ - Embeds: []*embed.Embed{e}, - Components: rows, - } - - msg, err := rest.CreateMessage(ctx.Token, ctx.RateLimiter, d.ChannelId, data) - if err != nil { - return 0, err - } - - return msg.Id, nil -} diff --git a/app/http/endpoints/api/panel/multipanelmessagedata.go b/app/http/endpoints/api/panel/multipanelmessagedata.go new file mode 100644 index 0000000..d5d5655 --- /dev/null +++ b/app/http/endpoints/api/panel/multipanelmessagedata.go @@ -0,0 +1,88 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/botcontext" + "github.com/TicketsBot/database" + "github.com/rxdn/gdl/objects/channel/embed" + "github.com/rxdn/gdl/objects/guild/emoji" + "github.com/rxdn/gdl/objects/interaction/component" + "github.com/rxdn/gdl/rest" + "math" +) + +type multiPanelMessageData struct { + ChannelId uint64 + + Title string + Content string + Colour int + IsPremium bool +} + +func multiPanelIntoMessageData(panel database.MultiPanel, isPremium bool) multiPanelMessageData { + return multiPanelMessageData{ + ChannelId: panel.ChannelId, + Title: panel.Title, + Content: panel.Content, + Colour: panel.Colour, + IsPremium: isPremium, + } +} + +func (d *multiPanelMessageData) send(ctx *botcontext.BotContext, panels []database.Panel) (uint64, error) { + e := embed.NewEmbed(). + SetTitle(d.Title). + SetDescription(d.Content). + SetColor(d.Colour) + + if !d.IsPremium { + // TODO: Don't harcode + e.SetFooter("Powered by ticketsbot.net", "https://ticketsbot.net/assets/img/logo.png") + } + + buttons := make([]component.Component, len(panels)) + for i, panel := range panels { + var buttonEmoji *emoji.Emoji + if panel.ReactionEmote != "" { + buttonEmoji = &emoji.Emoji{ + Name: panel.ReactionEmote, + } + } + + buttons[i] = component.BuildButton(component.Button{ + Label: panel.Title, + CustomId: panel.CustomId, + Style: component.ButtonStyle(panel.ButtonStyle), + Emoji: buttonEmoji, + }) + } + + var rows []component.Component + for i := 0; i <= int(math.Ceil(float64(len(buttons)/5))); i++ { + lb := i * 5 + ub := lb + 5 + + if ub >= len(buttons) { + ub = len(buttons) + } + + if lb >= ub { + break + } + + row := component.BuildActionRow(buttons[lb:ub]...) + rows = append(rows, row) + } + + data := rest.CreateMessageData{ + Embeds: []*embed.Embed{e}, + Components: rows, + } + + msg, err := rest.CreateMessage(ctx.Token, ctx.RateLimiter, d.ChannelId, data) + if err != nil { + return 0, err + } + + return msg.Id, nil +} diff --git a/app/http/endpoints/api/panel/multipanelresend.go b/app/http/endpoints/api/panel/multipanelresend.go new file mode 100644 index 0000000..db19433 --- /dev/null +++ b/app/http/endpoints/api/panel/multipanelresend.go @@ -0,0 +1,96 @@ +package api + +import ( + "errors" + "github.com/TicketsBot/GoPanel/botcontext" + dbclient "github.com/TicketsBot/GoPanel/database" + "github.com/TicketsBot/GoPanel/rpc" + "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/common/premium" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" + "strconv" +) + +func MultiPanelResend(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + // parse panel ID + panelId, err := strconv.Atoi(ctx.Param("panelid")) + if err != nil { + ctx.JSON(400, utils.ErrorJson(err)) + return + } + + // retrieve panel from DB + multiPanel, ok, err := dbclient.Client.MultiPanels.Get(panelId) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + // check panel exists + if !ok { + ctx.JSON(404, utils.ErrorJson(errors.New("No panel with the provided ID found"))) + return + } + + // check panel is in the same guild + if guildId != multiPanel.GuildId { + ctx.JSON(403, utils.ErrorJson(errors.New("Guild ID doesn't match"))) + return + } + + // get bot context + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + return + } + + // delete old message + if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, multiPanel.ChannelId, multiPanel.MessageId); err != nil { + var unwrapped request.RestError + if errors.As(err, &unwrapped) && !unwrapped.IsClientError() { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } + + // get premium status + premiumTier, err := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + panels, err := dbclient.Client.MultiPanelTargets.GetPanels(multiPanel.Id) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + // send new message + messageData := multiPanelIntoMessageData(multiPanel, premiumTier > premium.None) + messageId, err := messageData.send(&botContext, panels) + if err != nil { + var unwrapped request.RestError + if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { + ctx.JSON(500, utils.ErrorJson(errors.New("I do not have permission to send messages in the provided channel"))) + } else { + ctx.JSON(500, utils.ErrorJson(err)) + } + + return + } + + if err = dbclient.Client.MultiPanels.UpdateMessageId(multiPanel.Id, messageId); err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + ctx.JSON(200, gin.H{ + "success": true, + }) +} diff --git a/app/http/endpoints/api/panel/multipanelupdate.go b/app/http/endpoints/api/panel/multipanelupdate.go index 3d529e0..bad154b 100644 --- a/app/http/endpoints/api/panel/multipanelupdate.go +++ b/app/http/endpoints/api/panel/multipanelupdate.go @@ -91,7 +91,8 @@ func MultiPanelUpdate(ctx *gin.Context) { } // send new message - messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None, panels) + messageData := data.IntoMessageData(premiumTier > premium.None) + messageId, err := messageData.send(&botContext, panels) if err != nil { var unwrapped request.RestError if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { diff --git a/app/http/endpoints/api/panel/panelcreate.go b/app/http/endpoints/api/panel/panelcreate.go index 7a53fbf..8328387 100644 --- a/app/http/endpoints/api/panel/panelcreate.go +++ b/app/http/endpoints/api/panel/panelcreate.go @@ -13,10 +13,7 @@ import ( "github.com/TicketsBot/database" "github.com/gin-gonic/gin" "github.com/rxdn/gdl/objects/channel" - "github.com/rxdn/gdl/objects/channel/embed" - "github.com/rxdn/gdl/objects/guild/emoji" "github.com/rxdn/gdl/objects/interaction/component" - "github.com/rxdn/gdl/rest" "github.com/rxdn/gdl/rest/request" "golang.org/x/sync/errgroup" "regexp" @@ -43,6 +40,26 @@ type panelBody struct { ButtonStyle component.ButtonStyle `json:"button_style,string"` } +func (p *panelBody) IntoPanelMessageData(customId string, isPremium bool) panelMessageData { + var emoji *string + if p.Emote != "" { + emoji = &p.Emote + } + + return panelMessageData{ + ChannelId: p.ChannelId, + Title: p.Title, + Content: p.Content, + CustomId: customId, + Colour: int(p.Colour), + ImageUrl: p.ImageUrl, + ThumbnailUrl: p.ThumbnailUrl, + Emoji: emoji, + ButtonStyle: p.ButtonStyle, + IsPremium: isPremium, + } +} + func CreatePanel(ctx *gin.Context) { guildId := ctx.Keys["guildid"].(uint64) @@ -99,7 +116,10 @@ func CreatePanel(ctx *gin.Context) { customId := utils.RandString(80) emoji, _ := data.getEmoji() // already validated - msgId, err := data.sendEmbed(&botContext, data.Title, customId, emoji, data.ImageUrl, data.ThumbnailUrl, data.ButtonStyle, premiumTier > premium.None) + data.Emote = emoji + + messageData := data.IntoPanelMessageData(customId, premiumTier > premium.None) + msgId, err := messageData.send(&botContext) if err != nil { var unwrapped request.RestError if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { @@ -363,51 +383,3 @@ func (p *panelBody) verifyThumbnailUrl() bool { func (p *panelBody) verifyButtonStyle() bool { return p.ButtonStyle >= component.ButtonStylePrimary && p.ButtonStyle <= component.ButtonStyleDanger } - -func (p *panelBody) sendEmbed(ctx *botcontext.BotContext, title, customId, emote string, imageUrl, thumbnailUrl *string, buttonStyle component.ButtonStyle, isPremium bool) (uint64, error) { - e := embed.NewEmbed(). - SetTitle(p.Title). - SetDescription(p.Content). - SetColor(int(p.Colour)) - - if imageUrl != nil { - e.SetImage(*imageUrl) - } - - if thumbnailUrl != nil { - e.SetThumbnail(*thumbnailUrl) - } - - if !isPremium { - // TODO: Don't harcode - e.SetFooter("Powered by ticketsbot.net", "https://ticketsbot.net/assets/img/logo.png") - } - - var buttonEmoji *emoji.Emoji - if emote != "" { - buttonEmoji = &emoji.Emoji{ - Name: emote, - } - } - - data := rest.CreateMessageData{ - Embeds: []*embed.Embed{e}, - Components: []component.Component{ - component.BuildActionRow(component.BuildButton(component.Button{ - Label: title, - CustomId: customId, - Style: buttonStyle, - Emoji: buttonEmoji, - Url: nil, - Disabled: false, - })), - }, - } - - msg, err := rest.CreateMessage(ctx.Token, ctx.RateLimiter, p.ChannelId, data) - if err != nil { - return 0, err - } - - return msg.Id, nil -} diff --git a/app/http/endpoints/api/panel/panelmessagedata.go b/app/http/endpoints/api/panel/panelmessagedata.go new file mode 100644 index 0000000..7e54e23 --- /dev/null +++ b/app/http/endpoints/api/panel/panelmessagedata.go @@ -0,0 +1,87 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/botcontext" + "github.com/TicketsBot/database" + "github.com/rxdn/gdl/objects/channel/embed" + "github.com/rxdn/gdl/objects/guild/emoji" + "github.com/rxdn/gdl/objects/interaction/component" + "github.com/rxdn/gdl/rest" +) + +type panelMessageData struct { + ChannelId uint64 + + Title, Content, CustomId string + Colour int + ImageUrl, ThumbnailUrl, Emoji *string + ButtonStyle component.ButtonStyle + IsPremium bool +} + +func panelIntoMessageData(panel database.Panel, isPremium bool) panelMessageData { + var emoji *string + if panel.ReactionEmote != "" { + emoji = &panel.ReactionEmote + } + + return panelMessageData{ + ChannelId: panel.ChannelId, + Title: panel.Title, + Content: panel.Content, + CustomId: panel.CustomId, + Colour: int(panel.Colour), + ImageUrl: panel.ImageUrl, + ThumbnailUrl: panel.ThumbnailUrl, + Emoji: emoji, + ButtonStyle: component.ButtonStyle(panel.ButtonStyle), + IsPremium: isPremium, + } +} + +func (p *panelMessageData) send(ctx *botcontext.BotContext) (uint64, error) { + e := embed.NewEmbed(). + SetTitle(p.Title). + SetDescription(p.Content). + SetColor(p.Colour) + + if p.ImageUrl != nil { + e.SetImage(*p.ImageUrl) + } + + if p.ThumbnailUrl != nil { + e.SetThumbnail(*p.ThumbnailUrl) + } + + if !p.IsPremium { + e.SetFooter("Powered by ticketsbot.net", "https://ticketsbot.net/assets/img/logo.png") + } + + var buttonEmoji *emoji.Emoji + if p.Emoji != nil { + buttonEmoji = &emoji.Emoji{ + Name: *p.Emoji, + } + } + + data := rest.CreateMessageData{ + Embeds: []*embed.Embed{e}, + Components: []component.Component{ + component.BuildActionRow(component.BuildButton(component.Button{ + Label: p.Title, + CustomId: p.CustomId, + Style: p.ButtonStyle, + Emoji: buttonEmoji, + Url: nil, + Disabled: false, + })), + }, + } + + msg, err := rest.CreateMessage(ctx.Token, ctx.RateLimiter, p.ChannelId, data) + if err != nil { + return 0, err + } + + return msg.Id, nil +} diff --git a/app/http/endpoints/api/panel/panelresend.go b/app/http/endpoints/api/panel/panelresend.go new file mode 100644 index 0000000..a55157f --- /dev/null +++ b/app/http/endpoints/api/panel/panelresend.go @@ -0,0 +1,81 @@ +package api + +import ( + "errors" + "github.com/TicketsBot/GoPanel/botcontext" + dbclient "github.com/TicketsBot/GoPanel/database" + "github.com/TicketsBot/GoPanel/rpc" + "github.com/TicketsBot/GoPanel/utils" + "github.com/TicketsBot/common/premium" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "github.com/rxdn/gdl/rest/request" + "strconv" +) + +func ResendPanel(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + botContext, err := botcontext.ContextForGuild(guildId) + if err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + return + } + + panelId, err := strconv.Atoi(ctx.Param("panelid")) + if err != nil { + ctx.AbortWithStatusJSON(400, utils.ErrorJson(err)) + return + } + + // get existing + panel, err := dbclient.Client.Panel.GetById(panelId) + if err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + return + } + + // check guild ID matches + if panel.GuildId != guildId { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Guild ID does not match", + }) + return + } + + // delete old message + if err := rest.DeleteMessage(botContext.Token, botContext.RateLimiter, panel.ChannelId, panel.GuildId); err != nil { + var unwrapped request.RestError + if errors.As(err, &unwrapped) && !unwrapped.IsClientError() { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + } + + premiumTier, err := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter) + if err != nil { + ctx.JSON(500, utils.ErrorJson(err)) + return + } + + messageData := panelIntoMessageData(panel, premiumTier > premium.None) + msgId, err := messageData.send(&botContext) + if err != nil { + var unwrapped request.RestError + if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { + ctx.JSON(500, utils.ErrorStr("I do not have permission to send messages in the provided channel")) + } else { + ctx.JSON(500, utils.ErrorJson(err)) + } + + return + } + + if err = dbclient.Client.Panel.UpdateMessageId(panel.PanelId, msgId); err != nil { + ctx.AbortWithStatusJSON(500, utils.ErrorJson(err)) + return + } + + ctx.JSON(200, utils.SuccessResponse) +} diff --git a/app/http/endpoints/api/panel/panelupdate.go b/app/http/endpoints/api/panel/panelupdate.go index 0c6520c..b1bf391 100644 --- a/app/http/endpoints/api/panel/panelupdate.go +++ b/app/http/endpoints/api/panel/panelupdate.go @@ -83,15 +83,15 @@ func UpdatePanel(ctx *gin.Context) { panelIds[i] = panel.PanelId } - data := multiPanelCreateData{ + messageData := multiPanelMessageData{ Title: multiPanel.Title, Content: multiPanel.Content, - Colour: int32(multiPanel.Colour), + Colour: multiPanel.Colour, ChannelId: multiPanel.ChannelId, - Panels: panelIds, + IsPremium: premiumTier > premium.None, } - messageId, err := data.sendEmbed(&botContext, premiumTier > premium.None, panels) + messageId, err := messageData.send(&botContext, panels) if err != nil { ctx.JSON(500, utils.ErrorJson(err)) return @@ -123,7 +123,8 @@ func UpdatePanel(ctx *gin.Context) { // delete old message, ignoring error _ = rest.DeleteMessage(botContext.Token, botContext.RateLimiter, existing.ChannelId, existing.MessageId) - newMessageId, err = data.sendEmbed(&botContext, data.Title, existing.CustomId, data.Emote, data.ImageUrl, data.ThumbnailUrl, data.ButtonStyle, premiumTier > premium.None) + messageData := data.IntoPanelMessageData(existing.CustomId, premiumTier > premium.None) + newMessageId, err = messageData.send(&botContext) if err != nil { var unwrapped request.RestError if errors.As(err, &unwrapped) && unwrapped.StatusCode == 403 { diff --git a/app/http/server.go b/app/http/server.go index a453903..5d12554 100644 --- a/app/http/server.go +++ b/app/http/server.go @@ -86,11 +86,13 @@ func StartServer() { guildAuthApiAdmin.GET("/panels", api_panels.ListPanels) guildAuthApiAdmin.POST("/panels", api_panels.CreatePanel) + guildAuthApiAdmin.POST("/panels/:panelid", rl(middleware.RateLimitTypeGuild, 5, 5*time.Second), api_panels.ResendPanel) guildAuthApiAdmin.PATCH("/panels/:panelid", api_panels.UpdatePanel) guildAuthApiAdmin.DELETE("/panels/:panelid", api_panels.DeletePanel) guildAuthApiAdmin.GET("/multipanels", api_panels.MultiPanelList) guildAuthApiAdmin.POST("/multipanels", api_panels.MultiPanelCreate) + guildAuthApiAdmin.POST("/multipanels/:panelid", rl(middleware.RateLimitTypeGuild, 5, 5*time.Second), api_panels.MultiPanelResend) guildAuthApiAdmin.PATCH("/multipanels/:panelid", api_panels.MultiPanelUpdate) guildAuthApiAdmin.DELETE("/multipanels/:panelid", api_panels.MultiPanelDelete) diff --git a/frontend/src/includes/NotifyModal.svelte b/frontend/src/includes/NotifyModal.svelte index d07a780..d53d0c1 100644 --- a/frontend/src/includes/NotifyModal.svelte +++ b/frontend/src/includes/NotifyModal.svelte @@ -1,5 +1,5 @@ {#if $notifyModal} -