From 64c19c4dcf6d0632b4d8cadba883a168a1247896 Mon Sep 17 00:00:00 2001 From: Dot-Rar Date: Thu, 23 Apr 2020 21:25:21 +0100 Subject: [PATCH] finish rewrite --- app/http/endpoints/api/blacklist.go | 33 +++ app/http/endpoints/api/blacklistadd.go | 60 ++++ app/http/endpoints/api/blacklistremove.go | 26 ++ app/http/endpoints/api/channels.go | 23 ++ app/http/endpoints/api/closeticket.go | 53 ++++ app/http/endpoints/api/getticket.go | 76 +++++ app/http/endpoints/api/gettickets.go | 49 ++++ app/http/endpoints/api/guilds.go | 30 ++ app/http/endpoints/api/logslist.go | 112 +++++++ app/http/endpoints/api/panelcreate.go | 135 +++++++++ app/http/endpoints/api/paneldelete.go | 43 +++ app/http/endpoints/api/panellist.go | 40 +++ app/http/endpoints/api/premium.go | 17 ++ app/http/endpoints/api/sendmessage.go | 132 +++++++++ app/http/endpoints/api/settings.go | 56 ++++ app/http/endpoints/api/token.go | 34 +++ app/http/endpoints/api/updatesettings.go | 130 +++++++++ app/http/endpoints/manage/blacklist.go | 96 +----- app/http/endpoints/manage/blacklistremove.go | 56 ---- app/http/endpoints/manage/logslist.go | 149 +--------- app/http/endpoints/manage/logsview.go | 5 +- app/http/endpoints/manage/panelcreate.go | 175 ----------- app/http/endpoints/manage/paneldelete.go | 62 ---- app/http/endpoints/manage/panels.go | 103 +------ app/http/endpoints/manage/sendmessage.go | 70 ----- app/http/endpoints/manage/settings.go | 150 ++++------ app/http/endpoints/manage/ticketclose.go | 76 ----- app/http/endpoints/manage/ticketlist.go | 97 +----- app/http/endpoints/manage/ticketview.go | 113 +------ app/http/endpoints/manage/updatesettings.go | 136 --------- app/http/endpoints/manage/webchatws.go | 226 +++++--------- app/http/endpoints/root/index.go | 50 ++-- app/http/middleware/authenticatecookie.go | 24 ++ app/http/middleware/authenticateguild.go | 71 +++++ app/http/middleware/authenticatetoken.go | 54 ++++ app/http/server.go | 69 +++-- config/config.go | 1 + database/table/archivechannel.go | 5 +- database/table/blacklist.go | 4 +- database/table/channelcategory.go | 5 +- database/table/panels.go | 16 +- database/table/pingeveryone.go | 5 +- database/table/prefix.go | 5 +- database/table/ticketlimit.go | 5 +- database/table/welcomemessage.go | 5 +- go.mod | 3 +- public/static/css/discordmock.css | 8 +- public/static/css/style.css | 76 ++++- public/templates/includes/head.tmpl | 94 +++++- public/templates/includes/navbar.tmpl | 49 ++-- public/templates/includes/sidebar.tmpl | 10 +- public/templates/layouts/manage.tmpl | 6 +- public/templates/views/blacklist.tmpl | 112 ++++--- public/templates/views/index.tmpl | 69 +++-- public/templates/views/logs.tmpl | 109 ++++++- public/templates/views/panels.tmpl | 292 ++++++++++--------- public/templates/views/settings.tmpl | 214 +++++++++----- public/templates/views/ticketlist.tmpl | 35 ++- public/templates/views/ticketview.tmpl | 168 ++++++----- utils/premiumutils.go | 3 +- 60 files changed, 2292 insertions(+), 1838 deletions(-) create mode 100644 app/http/endpoints/api/blacklist.go create mode 100644 app/http/endpoints/api/blacklistadd.go create mode 100644 app/http/endpoints/api/blacklistremove.go create mode 100644 app/http/endpoints/api/channels.go create mode 100644 app/http/endpoints/api/closeticket.go create mode 100644 app/http/endpoints/api/getticket.go create mode 100644 app/http/endpoints/api/gettickets.go create mode 100644 app/http/endpoints/api/guilds.go create mode 100644 app/http/endpoints/api/logslist.go create mode 100644 app/http/endpoints/api/panelcreate.go create mode 100644 app/http/endpoints/api/paneldelete.go create mode 100644 app/http/endpoints/api/panellist.go create mode 100644 app/http/endpoints/api/premium.go create mode 100644 app/http/endpoints/api/sendmessage.go create mode 100644 app/http/endpoints/api/settings.go create mode 100644 app/http/endpoints/api/token.go create mode 100644 app/http/endpoints/api/updatesettings.go delete mode 100644 app/http/endpoints/manage/blacklistremove.go delete mode 100644 app/http/endpoints/manage/panelcreate.go delete mode 100644 app/http/endpoints/manage/paneldelete.go delete mode 100644 app/http/endpoints/manage/sendmessage.go delete mode 100644 app/http/endpoints/manage/ticketclose.go delete mode 100644 app/http/endpoints/manage/updatesettings.go create mode 100644 app/http/middleware/authenticatecookie.go create mode 100644 app/http/middleware/authenticateguild.go create mode 100644 app/http/middleware/authenticatetoken.go diff --git a/app/http/endpoints/api/blacklist.go b/app/http/endpoints/api/blacklist.go new file mode 100644 index 0000000..00348aa --- /dev/null +++ b/app/http/endpoints/api/blacklist.go @@ -0,0 +1,33 @@ +package api + +import ( + "fmt" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/gin-gonic/gin" + "strconv" +) + +type userData struct { + Username string `json:"username"` + Discriminator string `json:"discriminator"` +} + +func GetBlacklistHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + data := make(map[string]userData) + + blacklistedUsers := table.GetBlacklistNodes(guildId) + for _, row := range blacklistedUsers { + formattedId := strconv.FormatUint(row.User, 10) + user, _ := cache.Instance.GetUser(row.User) + + data[formattedId] = userData{ + Username: user.Username, + Discriminator: fmt.Sprintf("%04d", user.Discriminator), + } + } + + ctx.JSON(200, data) +} diff --git a/app/http/endpoints/api/blacklistadd.go b/app/http/endpoints/api/blacklistadd.go new file mode 100644 index 0000000..852cdb5 --- /dev/null +++ b/app/http/endpoints/api/blacklistadd.go @@ -0,0 +1,60 @@ +package api + +import ( + "context" + "errors" + "fmt" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v4" + "strconv" +) + +func AddBlacklistHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + var data userData + if err := ctx.BindJSON(&data); err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + parsedDiscrim, err := strconv.ParseInt(data.Discriminator, 10, 16) + if err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + var targetId uint64 + if err := cache.Instance.QueryRow(context.Background(), `select users.user_id from "users" where LOWER(users.data->>'Username')=LOWER($1) AND users.data->>'Discriminator'=$2;`, data.Username, strconv.FormatInt(parsedDiscrim, 10)).Scan(&targetId); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + ctx.AbortWithStatusJSON(404, gin.H{ + "success": false, + "error": "user not found", + }) + } else { + fmt.Println(err.Error()) + ctx.AbortWithStatusJSON(500, gin.H{ + "success": false, + "error": err.Error(), + }) + } + return + } + + // TODO: Don't blacklist staff or guild owner + + go table.AddBlacklist(guildId, targetId) + + ctx.JSON(200, gin.H{ + "success": true, + "user_id": strconv.FormatUint(targetId, 10), + }) +} diff --git a/app/http/endpoints/api/blacklistremove.go b/app/http/endpoints/api/blacklistremove.go new file mode 100644 index 0000000..c6b28ed --- /dev/null +++ b/app/http/endpoints/api/blacklistremove.go @@ -0,0 +1,26 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/gin-gonic/gin" + "strconv" +) + +func RemoveBlacklistHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + userId, err := strconv.ParseUint(ctx.Param("user"), 10, 64) + if err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + go table.RemoveBlacklist(guildId, userId) + + ctx.JSON(200, gin.H{ + "success": true, + }) +} diff --git a/app/http/endpoints/api/channels.go b/app/http/endpoints/api/channels.go new file mode 100644 index 0000000..934b796 --- /dev/null +++ b/app/http/endpoints/api/channels.go @@ -0,0 +1,23 @@ +package api + +import ( + "encoding/json" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/gin-gonic/gin" +) + +func ChannelsHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + channels := cache.Instance.GetGuildChannels(guildId) + encoded, err := json.Marshal(channels) + if err != nil { + ctx.JSON(500, gin.H{ + "success": true, + "error": err.Error(), + }) + return + } + + ctx.Data(200, gin.MIMEJSON, encoded) +} diff --git a/app/http/endpoints/api/closeticket.go b/app/http/endpoints/api/closeticket.go new file mode 100644 index 0000000..51e1ae3 --- /dev/null +++ b/app/http/endpoints/api/closeticket.go @@ -0,0 +1,53 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/messagequeue" + "github.com/gin-gonic/gin" +) + +type closeBody struct { + Reason string `json:"reason"` +} + +func CloseTicket(ctx *gin.Context) { + userId := ctx.Keys["userid"].(uint64) + guildId := ctx.Keys["guildid"].(uint64) + uuid := ctx.Param("uuid") + + var data closeBody + if err := ctx.BindJSON(&data); err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": true, + "error": "Missing reason", + }) + return + } + + // Verify that the ticket exists + ticketChan := make(chan table.Ticket) + go table.GetTicket(uuid, ticketChan) + ticket := <-ticketChan + + if ticket.Uuid == "" { + ctx.AbortWithStatusJSON(404, gin.H{ + "success": true, + "error": "Ticket does not exist", + }) + return + } + + if ticket.Guild != guildId { + ctx.AbortWithStatusJSON(403, gin.H{ + "success": true, + "error": "Guild ID does not matched", + }) + return + } + + go messagequeue.Client.PublishTicketClose(ticket.Uuid, userId, data.Reason) + + ctx.JSON(200, gin.H{ + "success": true, + }) +} diff --git a/app/http/endpoints/api/getticket.go b/app/http/endpoints/api/getticket.go new file mode 100644 index 0000000..9447df1 --- /dev/null +++ b/app/http/endpoints/api/getticket.go @@ -0,0 +1,76 @@ +package api + +import ( + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/ratelimit" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "regexp" + "strconv" + "strings" +) + +var MentionRegex, _ = regexp.Compile("<@(\\d+)>") + +func GetTicket(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + uuid := ctx.Param("uuid") + + ticketChan := make(chan table.Ticket) + go table.GetTicket(uuid, ticketChan) + ticket := <-ticketChan + + if ticket.Guild != guildId { + ctx.AbortWithStatusJSON(403, gin.H{ + "success": false, + "error": "Guild ID doesn't match", + }) + return + } + + if !ticket.IsOpen { + ctx.AbortWithStatusJSON(404, gin.H{ + "success": false, + "error": "Ticket does not exist", + }) + return + } + + // Get messages + messages, _ := rest.GetChannelMessages(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.GetChannelMessagesData{Limit: 100}) + + // Format messages, exclude unneeded data + messagesFormatted := make([]map[string]interface{}, 0) + for _, message := range utils.Reverse(messages) { + content := message.Content + + // Format mentions properly + match := MentionRegex.FindAllStringSubmatch(content, -1) + for _, mention := range match { + if len(mention) >= 2 { + mentionedId, err := strconv.ParseUint(mention[1], 10, 64) + if err != nil { + continue + } + + ch := make(chan string) + go table.GetUsername(mentionedId, ch) + content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", <-ch)) + } + } + + messagesFormatted = append(messagesFormatted, map[string]interface{}{ + "username": message.Author.Username, + "content": content, + }) + } + + ctx.JSON(200, gin.H{ + "success": true, + "ticket": ticket, + "messages": messagesFormatted, + }) +} diff --git a/app/http/endpoints/api/gettickets.go b/app/http/endpoints/api/gettickets.go new file mode 100644 index 0000000..7329c7b --- /dev/null +++ b/app/http/endpoints/api/gettickets.go @@ -0,0 +1,49 @@ +package api + +import ( + "fmt" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/gin-gonic/gin" + "strconv" + "strings" +) + +func GetTickets(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + tickets := table.GetOpenTickets(guildId) + ticketsFormatted := make([]map[string]interface{}, 0) + + for _, ticket := range tickets { + membersFormatted := make([]map[string]interface{}, 0) + for index, memberIdStr := range strings.Split(ticket.Members, ",") { + if memberId, err := strconv.ParseUint(memberIdStr, 10, 64); err == nil { + if memberId != 0 { + var separator string + if index != len(strings.Split(ticket.Members, ","))-1 { + separator = ", " + } + + member, _ := cache.Instance.GetUser(memberId) + membersFormatted = append(membersFormatted, map[string]interface{}{ + "username": member.Username, + "discrim": fmt.Sprintf("%04d", member.Discriminator), + "sep": separator, + }) + } + } + } + + owner, _ := cache.Instance.GetUser(ticket.Owner) + ticketsFormatted = append(ticketsFormatted, map[string]interface{}{ + "uuid": ticket.Uuid, + "ticketId": ticket.TicketId, + "username": owner.Username, + "discrim": fmt.Sprintf("%04d", owner.Discriminator), + "members": membersFormatted, + }) + } + + ctx.JSON(200, ticketsFormatted) +} diff --git a/app/http/endpoints/api/guilds.go b/app/http/endpoints/api/guilds.go new file mode 100644 index 0000000..f7d71a0 --- /dev/null +++ b/app/http/endpoints/api/guilds.go @@ -0,0 +1,30 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/objects/guild" +) + +func GetGuilds(ctx *gin.Context) { + userId := ctx.Keys["userid"].(uint64) + + userGuilds := table.GetGuilds(userId) + adminGuilds := make([]guild.Guild, 0) + for _, g := range userGuilds { + fakeGuild := guild.Guild{ + Id: g.Id, + OwnerId: g.OwnerId, + Permissions: g.Permissions, + } + + isAdmin := make(chan bool) + go utils.IsAdmin(fakeGuild, userId, isAdmin) + if <-isAdmin { + adminGuilds = append(adminGuilds, g) + } + } + + ctx.JSON(200, adminGuilds) +} diff --git a/app/http/endpoints/api/logslist.go b/app/http/endpoints/api/logslist.go new file mode 100644 index 0000000..9579096 --- /dev/null +++ b/app/http/endpoints/api/logslist.go @@ -0,0 +1,112 @@ +package api + +import ( + "context" + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/TicketsBot/GoPanel/rpc/ratelimit" + "github.com/TicketsBot/GoPanel/utils" + "github.com/apex/log" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "strconv" +) + +const ( + pageLimit = 30 +) + +func GetLogs(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + page, err := strconv.Atoi(ctx.Param("page")) + if page < 1 { + page = 1 + } + + // Get ticket ID from URL + var ticketId int + if utils.IsInt(ctx.Query("ticketid")) { + ticketId, _ = strconv.Atoi(ctx.Query("ticketid")) + } + + var tickets []table.Ticket + + // Get tickets from DB + if ticketId > 0 { + ticketChan := make(chan table.Ticket) + go table.GetTicketById(guildId, ticketId, ticketChan) + ticket := <-ticketChan + + if ticket.Uuid != "" && !ticket.IsOpen { + tickets = append(tickets, ticket) + } + } else { + // make slice of user IDs to filter by + filteredIds := make([]uint64, 0) + + // Add userid param to slice + filteredUserId, _ := strconv.ParseUint(ctx.Query("userid"), 10, 64) + if filteredUserId != 0 { + filteredIds = append(filteredIds, filteredUserId) + } + + // Get username from URL + if username := ctx.Query("username"); username != "" { + // username -> user id + rows, err := cache.Instance.PgCache.Query(context.Background(), `select users.user_id from users where LOWER("data"->>'Username') LIKE LOWER($1) and exists(SELECT FROM members where members.guild_id=$2);`, fmt.Sprintf("%%%s%%", username), guildId) + defer rows.Close() + if err != nil { + log.Error(err.Error()) + return + } + + for rows.Next() { + var filteredId uint64 + if err := rows.Scan(&filteredId); err != nil { + continue + } + + if filteredId != 0 { + filteredIds = append(filteredIds, filteredId) + } + } + } + + if ctx.Query("userid") != "" || ctx.Query("username") != "" { + tickets = table.GetClosedTicketsByUserId(guildId, filteredIds) + } else { + tickets = table.GetClosedTickets(guildId) + } + } + + // Select 30 logs + format them + formattedLogs := make([]map[string]interface{}, 0) + for i := (page - 1) * pageLimit; i < (page-1)*pageLimit+pageLimit; i++ { + if i >= len(tickets) { + break + } + + ticket := tickets[i] + + // get username + user, found := cache.Instance.GetUser(ticket.Owner) + if !found { + user, err = rest.GetUser(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Owner) + if err != nil { + log.Error(err.Error()) + } + go cache.Instance.StoreUser(user) + } + + formattedLogs = append(formattedLogs, map[string]interface{}{ + "ticketid": ticket.TicketId, + "userid": strconv.FormatUint(ticket.Owner, 10), + "username": user.Username, + }) + } + + ctx.JSON(200, formattedLogs) +} diff --git a/app/http/endpoints/api/panelcreate.go b/app/http/endpoints/api/panelcreate.go new file mode 100644 index 0000000..fa60686 --- /dev/null +++ b/app/http/endpoints/api/panelcreate.go @@ -0,0 +1,135 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/messagequeue" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/objects/channel" +) + +func CreatePanel(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + var data panel + + if err := ctx.BindJSON(&data); err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + data.MessageId = 0 + + // Check panel quota + premium := make(chan bool) + go utils.IsPremiumGuild(guildId, premium) + if !<-premium { + panels := make(chan []table.Panel) + go table.GetPanelsByGuild(guildId, panels) + if len(<-panels) > 0 { + ctx.AbortWithStatusJSON(402, gin.H{ + "success": false, + "error": "You have exceeded your panel quota. Purchase premium to unlock more panels.", + }) + return + } + } + + if !data.verifyTitle() { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Panel titles must be between 1 - 255 characters in length", + }) + return + } + + if !data.verifyContent() { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Panel content must be between 1 - 1024 characters in length", + }) + return + } + + channels := cache.Instance.GetGuildChannels(guildId) + + if !data.verifyChannel(channels) { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Invalid channel", + }) + return + } + + if !data.verifyCategory(channels) { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Invalid channel category", + }) + return + } + + emoji, validEmoji := data.getEmoji() + if !validEmoji { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Invalid emoji. Simply use the emoji's name from Discord.", + }) + return + } + + // TODO: Move panel create logic here + go messagequeue.Client.PublishPanelCreate(table.Panel{ + ChannelId: data.ChannelId, + GuildId: guildId, + Title: data.Title, + Content: data.Content, + Colour: data.Colour, + TargetCategory: data.CategoryId, + ReactionEmote: emoji, + }) + + ctx.JSON(200, gin.H{ + "success": true, + }) +} + +func (p *panel) verifyTitle() bool { + return len(p.Title) > 0 && len(p.Title) < 256 +} + +func (p *panel) verifyContent() bool { + return len(p.Content) > 0 && len(p.Content) < 1025 +} + +func (p *panel) getEmoji() (string, bool) { + emoji := utils.GetEmojiByName(p.Emote) + return emoji, emoji != "" +} + +func (p *panel) verifyChannel(channels []channel.Channel) bool { + var valid bool + for _, ch := range channels { + if ch.Id == p.ChannelId && ch.Type == channel.ChannelTypeGuildText { + valid = true + break + } + } + + return valid +} + +func (p *panel) verifyCategory(channels []channel.Channel) bool { + var valid bool + for _, ch := range channels { + if ch.Id == p.CategoryId && ch.Type == channel.ChannelTypeGuildCategory { + valid = true + break + } + } + + return valid +} diff --git a/app/http/endpoints/api/paneldelete.go b/app/http/endpoints/api/paneldelete.go new file mode 100644 index 0000000..1276d3e --- /dev/null +++ b/app/http/endpoints/api/paneldelete.go @@ -0,0 +1,43 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/ratelimit" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "strconv" +) + +func DeletePanel(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + messageId, err := strconv.ParseUint(ctx.Param("message"), 10, 64) + if err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + // verify panel belongs to guild + panelChan := make(chan table.Panel) + go table.GetPanel(messageId, panelChan) + panel := <-panelChan + + if panel.GuildId != guildId { + ctx.AbortWithStatusJSON(403, gin.H{ + "success": false, + "error": "Guild ID doesn't match", + }) + return + } + + go table.DeletePanel(messageId) + go rest.DeleteMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, panel.ChannelId, panel.MessageId) + + ctx.JSON(200, gin.H{ + "success": true, + }) +} diff --git a/app/http/endpoints/api/panellist.go b/app/http/endpoints/api/panellist.go new file mode 100644 index 0000000..2b30f76 --- /dev/null +++ b/app/http/endpoints/api/panellist.go @@ -0,0 +1,40 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/gin-gonic/gin" +) + +type panel struct { + ChannelId uint64 `json:"channel_id,string"` + MessageId uint64 `json:"message_id,string"` + Title string `json:"title"` + Content string `json:"content"` + Colour uint32 `json:"colour"` + CategoryId uint64 `json:"category_id,string"` + Emote string `json:"emote"` +} + +func ListPanels(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + panelsChan := make(chan []table.Panel) + go table.GetPanelsByGuild(guildId, panelsChan) + panels := <-panelsChan + + wrapped := make([]panel, len(panels)) + + for i, p := range panels { + wrapped[i] = panel{ + ChannelId: p.ChannelId, + MessageId: p.MessageId, + Title: p.Title, + Content: p.Content, + Colour: p.Colour, + CategoryId: p.TargetCategory, + Emote: p.ReactionEmote, + } + } + + ctx.JSON(200, wrapped) +} diff --git a/app/http/endpoints/api/premium.go b/app/http/endpoints/api/premium.go new file mode 100644 index 0000000..ff5fe41 --- /dev/null +++ b/app/http/endpoints/api/premium.go @@ -0,0 +1,17 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" +) + +func PremiumHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + isPremium := make(chan bool) + go utils.IsPremiumGuild(guildId, isPremium) + + ctx.JSON(200, gin.H{ + "premium": <-isPremium, + }) +} diff --git a/app/http/endpoints/api/sendmessage.go b/app/http/endpoints/api/sendmessage.go new file mode 100644 index 0000000..64ecdaa --- /dev/null +++ b/app/http/endpoints/api/sendmessage.go @@ -0,0 +1,132 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/TicketsBot/GoPanel/rpc/ratelimit" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/rest" + "net/http" + "time" +) + +type sendMessageBody struct { + Message string `json:"message"` +} + +func SendMessage(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + userId := ctx.Keys["userid"].(uint64) + + var body sendMessageBody + if err := ctx.BindJSON(&body); err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Message is missing", + }) + return + } + + // Verify guild is premium + isPremium := make(chan bool) + go utils.IsPremiumGuild(guildId, isPremium) + if !<-isPremium { + ctx.AbortWithStatusJSON(402, gin.H{ + "success": false, + "error": "Guild is not premium", + }) + return + } + + // Get ticket + ticketChan := make(chan table.Ticket) + go table.GetTicket(ctx.Param("uuid"), ticketChan) + ticket := <-ticketChan + + // Verify the ticket exists + if ticket.TicketId == 0 { + ctx.AbortWithStatusJSON(404, gin.H{ + "success": false, + "error": "Ticket not found", + }) + return + } + + // Verify the user has permission to send to this guild + if ticket.Guild != guildId { + ctx.AbortWithStatusJSON(403, gin.H{ + "success": false, + "error": "Guild ID doesn't match", + }) + return + } + + user, _ := cache.Instance.GetUser(userId) + + if len(body.Message) > 2000 { + body.Message = body.Message[0:1999] + } + + // Preferably send via a webhook + webhookChan := make(chan *string) + go table.GetWebhookByUuid(ticket.Uuid, webhookChan) + webhook := <-webhookChan + + success := false + if webhook != nil { + // TODO: Use gdl execute webhook wrapper + success = executeWebhook(ticket.Uuid, *webhook, body.Message, user.Username, user.AvatarUrl(256)) + } + + if !success { + body.Message = fmt.Sprintf("**%s**: %s", user.Username, body.Message) + if len(body.Message) > 2000 { + body.Message = body.Message[0:1999] + } + + _, _ = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.CreateMessageData{Content: body.Message}) + } + + ctx.JSON(200, gin.H{ + "success": true, + }) +} + +func executeWebhook(uuid, webhook, content, username, avatar string) bool { + body := map[string]interface{}{ + "content": content, + "username": username, + "avatar_url": avatar, + } + encoded, err := json.Marshal(&body) + if err != nil { + return false + } + req, err := http.NewRequest("POST", fmt.Sprintf("https://canary.discordapp.com/api/webhooks/%s", webhook), bytes.NewBuffer(encoded)) + if err != nil { + return false + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + client.Timeout = 3 * time.Second + + res, err := client.Do(req) + if err != nil { + return false + } + + if res.StatusCode == 404 || res.StatusCode == 403 { + go table.DeleteWebhookByUuid(uuid) + } else { + return true + } + + return false +} diff --git a/app/http/endpoints/api/settings.go b/app/http/endpoints/api/settings.go new file mode 100644 index 0000000..5a0a4e3 --- /dev/null +++ b/app/http/endpoints/api/settings.go @@ -0,0 +1,56 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/gin-gonic/gin" +) + +type Settings struct { + Prefix string `json:"prefix"` + WelcomeMessaage string `json:"welcome_message"` + TicketLimit int `json:"ticket_limit"` + Category uint64 `json:"category,string"` + ArchiveChannel uint64 `json:"archive_channel,string"` + NamingScheme table.NamingScheme `json:"naming_scheme"` + PingEveryone bool `json:"ping_everyone"` + UsersCanClose bool `json:"users_can_close"` +} + +func GetSettingsHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + prefix := make(chan string) + go table.GetPrefix(guildId, prefix) + + welcomeMessage := make(chan string) + go table.GetWelcomeMessage(guildId, welcomeMessage) + + ticketLimit := make(chan int) + go table.GetTicketLimit(guildId, ticketLimit) + + category := make(chan uint64) + go table.GetChannelCategory(guildId, category) + + archiveChannel := make(chan uint64) + go table.GetArchiveChannel(guildId, archiveChannel) + + allowUsersToClose := make(chan bool) + go table.IsUserCanClose(guildId, allowUsersToClose) + + namingScheme := make(chan table.NamingScheme) + go table.GetTicketNamingScheme(guildId, namingScheme) + + pingEveryone := make(chan bool) + go table.GetPingEveryone(guildId, pingEveryone) + + ctx.JSON(200, Settings{ + Prefix: <-prefix, + WelcomeMessaage: <-welcomeMessage, + TicketLimit: <-ticketLimit, + Category: <-category, + ArchiveChannel: <-archiveChannel, + NamingScheme: <-namingScheme, + PingEveryone: <-pingEveryone, + UsersCanClose: <-allowUsersToClose, + }) +} diff --git a/app/http/endpoints/api/token.go b/app/http/endpoints/api/token.go new file mode 100644 index 0000000..8c74e7c --- /dev/null +++ b/app/http/endpoints/api/token.go @@ -0,0 +1,34 @@ +package api + +import ( + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/utils" + "github.com/dgrijalva/jwt-go" + "github.com/gin-gonic/contrib/sessions" + "github.com/gin-gonic/gin" + "strconv" +) + +func TokenHandler(ctx *gin.Context) { + session := sessions.Default(ctx) + userId := utils.GetUserId(session) + //TODO : CSRF + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "userid": strconv.FormatUint(userId, 10), + }) + + str, err := token.SignedString([]byte(config.Conf.Server.Secret)) + if err != nil { + fmt.Println(err.Error()) + ctx.JSON(500, gin.H{ + "success": false, + "error": err.Error(), + }) + } else { + ctx.JSON(200, gin.H{ + "success": true, + "token": str, + }) + } +} diff --git a/app/http/endpoints/api/updatesettings.go b/app/http/endpoints/api/updatesettings.go new file mode 100644 index 0000000..34089a6 --- /dev/null +++ b/app/http/endpoints/api/updatesettings.go @@ -0,0 +1,130 @@ +package api + +import ( + "github.com/TicketsBot/GoPanel/database/table" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/gin-gonic/gin" + "github.com/rxdn/gdl/objects/channel" +) + +func UpdateSettingsHandler(ctx *gin.Context) { + guildId := ctx.Keys["guildid"].(uint64) + + var settings Settings + if err := ctx.BindJSON(&settings); err != nil { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + return + } + + // Get a list of all channel IDs + channels := cache.Instance.GetGuildChannels(guildId) + + // Get prefix + validPrefix := settings.updatePrefix(guildId) + validWelcomeMessage := settings.updateWelcomeMessage(guildId) + validTicketLimit := settings.updateTicketLimit(guildId) + validArchiveChannel := settings.updateArchiveChannel(channels, guildId) + validCategory := settings.updateCategory(channels, guildId) + validNamingScheme := settings.updateNamingScheme(guildId) + settings.updatePingEveryone(guildId) + settings.updateUsersCanClose(guildId) + + ctx.JSON(200, gin.H{ + "prefix": validPrefix, + "welcome_message": validWelcomeMessage, + "ticket_limit": validTicketLimit, + "archive_channel": validArchiveChannel, + "category": validCategory, + "naming_scheme": validNamingScheme, + }) +} + +func (s *Settings) updatePrefix(guildId uint64) bool { + if s.Prefix == "" || len(s.Prefix) > 8 { + return false + } + + go table.UpdatePrefix(guildId, s.Prefix) + return true +} + +func (s *Settings) updateWelcomeMessage(guildId uint64) bool { + if s.WelcomeMessaage == "" || len(s.WelcomeMessaage) > 1000 { + return false + } + + go table.UpdateWelcomeMessage(guildId, s.WelcomeMessaage) + return true +} + +func (s *Settings) updateTicketLimit(guildId uint64) bool { + if s.TicketLimit > 10 || s.TicketLimit < 1 { + return false + } + + go table.UpdateTicketLimit(guildId, s.TicketLimit) + return true +} + +func (s *Settings) updateCategory(channels []channel.Channel, guildId uint64) bool { + var valid bool + for _, ch := range channels { + if ch.Id == s.Category && ch.Type == channel.ChannelTypeGuildCategory { + valid = true + break + } + } + + if !valid { + return false + } + + go table.UpdateChannelCategory(guildId, s.Category) + return true +} + +func (s *Settings) updateArchiveChannel(channels []channel.Channel, guildId uint64) bool { + var valid bool + for _, ch := range channels { + if ch.Id == s.ArchiveChannel && ch.Type == channel.ChannelTypeGuildText { + valid = true + break + } + } + + if !valid { + return false + } + + go table.UpdateArchiveChannel(guildId, s.ArchiveChannel) + return true +} + +var validScheme = []table.NamingScheme{table.Id, table.Username} +func (s *Settings) updateNamingScheme(guildId uint64) bool { + var valid bool + for _, scheme := range validScheme { + if scheme == s.NamingScheme { + valid = true + break + } + } + + if !valid { + return false + } + + go table.SetTicketNamingScheme(guildId, s.NamingScheme) + return true +} + +func (s *Settings) updatePingEveryone(guildId uint64) { + go table.UpdatePingEveryone(guildId, s.PingEveryone) +} + +func (s *Settings) updateUsersCanClose(guildId uint64) { + go table.SetUserCanClose(guildId, s.UsersCanClose) +} diff --git a/app/http/endpoints/manage/blacklist.go b/app/http/endpoints/manage/blacklist.go index ff09af9..226349d 100644 --- a/app/http/endpoints/manage/blacklist.go +++ b/app/http/endpoints/manage/blacklist.go @@ -2,100 +2,18 @@ package manage import ( "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "strconv" ) func BlacklistHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + guildId := ctx.Keys["guildid"].(uint64) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - blacklistedUsers := table.GetBlacklistNodes(guildId) - - var blacklistedIds []uint64 - for _, user := range blacklistedUsers { - blacklistedIds = append(blacklistedIds, user.User) - } - - nodes := table.GetUserNodes(blacklistedIds) - - var blacklisted []map[string]interface{} - for _, node := range nodes { - blacklisted = append(blacklisted, map[string]interface{}{ - "userId": node.Id, - "username": utils.Base64Decode(node.Name), - "discrim": node.Discriminator, - }) - } - - userNotFound := false - isStaff := false - if store.Get("csrf").(string) == ctx.Query("csrf") { // CSRF is correct *and* set - username := ctx.Query("username") - discrim := ctx.Query("discrim") - - // Verify that the user ID is real and in a shared guild - targetId := table.GetUserId(username, discrim) - exists := targetId != 0 - - if exists { - if guild.OwnerId == targetId || table.IsStaff(guildId, targetId) { // Prevent users from blacklisting staff - isStaff = true - } else { - if !utils.Contains(blacklistedIds, targetId) { // Prevent duplicates - table.AddBlacklist(guildId, targetId) - blacklisted = append(blacklisted, map[string]interface{}{ - "userId": targetId, - "username": username, - "discrim": discrim, - }) - } - } - } else { - userNotFound = true - } - } - - ctx.HTML(200, "manage/blacklist", gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "csrf": store.Get("csrf").(string), - "avatar": store.Get("avatar").(string), - "baseUrl": config.Conf.Server.BaseUrl, - "blacklisted": blacklisted, - "userNotFound": userNotFound, - "isStaff": isStaff, - }) - } else { - ctx.Redirect(302, "/login") - } + ctx.HTML(200, "manage/blacklist", gin.H{ + "name": store.Get("name").(string), + "guildId": guildId, + "avatar": store.Get("avatar").(string), + "baseUrl": config.Conf.Server.BaseUrl, + }) } diff --git a/app/http/endpoints/manage/blacklistremove.go b/app/http/endpoints/manage/blacklistremove.go deleted file mode 100644 index 81fafc0..0000000 --- a/app/http/endpoints/manage/blacklistremove.go +++ /dev/null @@ -1,56 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "strconv" -) - -func BlacklistRemoveHandler(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - if ctx.Query("c") == store.Get("csrf").(string) { - targetIdStr := ctx.Param("user") - targetId, err := strconv.ParseUint(targetIdStr, 10, 64) - - if err == nil { // If it's a real ID - table.RemoveBlacklist(guildId, targetId) - } - } - - ctx.Redirect(302, fmt.Sprintf("/manage/%s/blacklist", guildIdStr)) - } else { - ctx.Redirect(302, "/login") - } -} diff --git a/app/http/endpoints/manage/logslist.go b/app/http/endpoints/manage/logslist.go index 9c12c98..ab1d0b6 100644 --- a/app/http/endpoints/manage/logslist.go +++ b/app/http/endpoints/manage/logslist.go @@ -1,154 +1,19 @@ package manage import ( - "context" - "fmt" "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/rpc/ratelimit" - "github.com/TicketsBot/GoPanel/utils" - "github.com/apex/log" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "github.com/rxdn/gdl/rest" - "strconv" ) func LogsHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + guildId := ctx.Keys["guildid"].(uint64) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - pageStr := ctx.Param("page") - page := 1 - i, err := strconv.Atoi(pageStr) - if err == nil { - if i > 0 { - page = i - } - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - pageLimit := 30 - - // Get ticket ID from URL - var ticketId int - if utils.IsInt(ctx.Query("ticketid")) { - ticketId, _ = strconv.Atoi(ctx.Query("ticketid")) - } - - var tickets []table.Ticket - - // Get tickets from DB - if ticketId > 0 { - ticketChan := make(chan table.Ticket) - go table.GetTicketById(guildId, ticketId, ticketChan) - ticket := <-ticketChan - - if ticket.Uuid != "" && !ticket.IsOpen { - tickets = append(tickets, ticket) - } - } else { - // make slice of user IDs to filter by - filteredIds := make([]uint64, 0) - - // Add userid param to slice - filteredUserId, _ := strconv.ParseUint(ctx.Query("userid"), 10, 64) - if filteredUserId != 0 { - filteredIds = append(filteredIds, filteredUserId) - } - - // Get username from URL - if username := ctx.Query("username"); username != "" { - // username -> user id - rows, err := cache.Instance.PgCache.Query(context.Background(), `select users.user_id from users where LOWER("data"->>'Username') LIKE LOWER($1) and exists(SELECT FROM members where members.guild_id=$2);`, fmt.Sprintf("%%%s%%", username), guildId) - defer rows.Close() - if err != nil { - log.Error(err.Error()) - return - } - - for rows.Next() { - var filteredId uint64 - if err := rows.Scan(&filteredId); err != nil { - continue - } - - if filteredId != 0 { - filteredIds = append(filteredIds, filteredId) - } - } - } - - if ctx.Query("userid") != "" || ctx.Query("username") != "" { - tickets = table.GetClosedTicketsByUserId(guildId, filteredIds) - } else { - tickets = table.GetClosedTickets(guildId) - } - } - - // Select 30 logs + format them - var formattedLogs []map[string]interface{} - for i := (page - 1) * pageLimit; i < (page - 1) * pageLimit + pageLimit; i++ { - if i >= len(tickets) { - break - } - - ticket := tickets[i] - - // get username - user, found := cache.Instance.GetUser(ticket.Owner) - if !found { - user, err = rest.GetUser(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Owner) - if err != nil { - log.Error(err.Error()) - } - go cache.Instance.StoreUser(user) - } - - formattedLogs = append(formattedLogs, map[string]interface{}{ - "ticketid": ticket.TicketId, - "userid": ticket.Owner, - "username": user.Username, - }) - } - - ctx.HTML(200, "manage/logs",gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "avatar": store.Get("avatar").(string), - "baseUrl": config.Conf.Server.BaseUrl, - "isPageOne": page == 1, - "previousPage": page - 1, - "nextPage": page + 1, - "logs": formattedLogs, - "page": page, - }) - } else { - ctx.Redirect(302, "/login") - } + ctx.HTML(200, "manage/logs", gin.H{ + "name": store.Get("name").(string), + "guildId": guildId, + "avatar": store.Get("avatar").(string), + "baseUrl": config.Conf.Server.BaseUrl, + }) } diff --git a/app/http/endpoints/manage/logsview.go b/app/http/endpoints/manage/logsview.go index 169094b..7c7984a 100644 --- a/app/http/endpoints/manage/logsview.go +++ b/app/http/endpoints/manage/logsview.go @@ -20,7 +20,6 @@ func LogViewHandler(ctx *gin.Context) { if store == nil { return } - defer store.Save() if utils.IsLoggedIn(store) { userId := utils.GetUserId(store) @@ -37,7 +36,7 @@ func LogViewHandler(ctx *gin.Context) { // format ticket ID ticketId, err := strconv.Atoi(ctx.Param("ticket")); if err != nil { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/page/1", guild.Id)) + ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id)) return } @@ -48,7 +47,7 @@ func LogViewHandler(ctx *gin.Context) { // Verify this is a valid ticket and it is closed if ticket.Uuid == "" || ticket.IsOpen { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/page/1", guild.Id)) + ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs", guild.Id)) return } diff --git a/app/http/endpoints/manage/panelcreate.go b/app/http/endpoints/manage/panelcreate.go deleted file mode 100644 index 0ba188f..0000000 --- a/app/http/endpoints/manage/panelcreate.go +++ /dev/null @@ -1,175 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/messagequeue" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/rxdn/gdl/objects/channel" - "strconv" - "strings" -) - -func PanelCreateHandler(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get CSRF token - csrfCorrect := ctx.PostForm("csrf") == store.Get("csrf").(string) - if !csrfCorrect { - ctx.Redirect(302, "/") - return - } - - // Get if the guild is premium - premiumChan := make(chan bool) - go utils.IsPremiumGuild(store, guildId, premiumChan) - premium := <-premiumChan - - // Check the user hasn't met their panel quota - if !premium { - panels := make(chan []table.Panel) - go table.GetPanelsByGuild(guildId, panels) - if len(<-panels) > 1 { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?metQuota=true", guildId)) - return - } - } - - // Validate title - title := ctx.PostForm("title") - if len(title) == 0 || len(title) > 255 { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validTitle=false", guildId)) - return - } - - // Validate content - content := ctx.PostForm("content") - if len(content) == 0 || len(content) > 1024 { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validContent=false", guildId)) - return - } - - // Validate colour - validColour := true - panelColourHex := strings.Replace(ctx.PostForm("colour"), "#", "", -1) - panelColour, err := strconv.ParseUint(panelColourHex, 16, 32) - if err != nil { - validColour = false - panelColour = 0x23A31A - } - - // Validate channel - channelIdStr := ctx.PostForm("channel") - channelId, err := strconv.ParseUint(channelIdStr, 10, 64); if err != nil { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validChannel=false", guildId)) - return - } - - validChannel := make(chan bool) - go validateChannel(guildId, channelId, validChannel) - if !<-validChannel { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validChannel=false", guildId)) - return - } - - // Validate category - categoryStr := ctx.PostForm("categories") - categoryId, err := strconv.ParseUint(categoryStr, 10, 64); if err != nil { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validCategory=false", guildId)) - return - } - - validCategory := make(chan bool) - go validateCategory(guildId, categoryId, validCategory) - if !<-validCategory { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validCategory=false", guildId)) - return - } - - // Validate reaction emote - reaction := strings.ToLower(ctx.PostForm("reaction")) - if len(reaction) == 0 || len(reaction) > 32 { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validReaction=false", guildId)) - return - } - reaction = strings.Replace(reaction, ":", "", -1) - - emoji := utils.GetEmojiByName(reaction) - if emoji == "" { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?validReaction=false", guildId)) - return - } - - settings := table.Panel{ - ChannelId: channelId, - GuildId: guildId, - Title: title, - Content: content, - Colour: int(panelColour), - TargetCategory: categoryId, - ReactionEmote: emoji, - } - - go messagequeue.Client.PublishPanelCreate(settings) - - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels?created=true&validColour=%t", guildId, validColour)) - } else { - ctx.Redirect(302, "/login") - } -} - -func validateChannel(guildId, channelId uint64, res chan bool) { - // Compare channel IDs - validChannel := false - for _, guildChannel := range cache.Instance.GetGuildChannels(guildId) { - if guildChannel.Id == channelId { - validChannel = true - break - } - } - - res <- validChannel -} - -func validateCategory(guildId, categoryId uint64, res chan bool) { - // Compare ch IDs - validCategory := false - for _, ch := range cache.Instance.GetGuildChannels(guildId) { - if ch.Type == channel.ChannelTypeGuildCategory && ch.Id == categoryId { - validCategory = true - break - } - } - - res <- validCategory -} diff --git a/app/http/endpoints/manage/paneldelete.go b/app/http/endpoints/manage/paneldelete.go deleted file mode 100644 index a237a4e..0000000 --- a/app/http/endpoints/manage/paneldelete.go +++ /dev/null @@ -1,62 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "strconv" -) - -func PanelDeleteHandler(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - messageIdStr := ctx.Param("msg") - messageId, err := strconv.ParseUint(messageIdStr, 10, 64); if err != nil { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels", guildId)) - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get CSRF token - csrfCorrect := ctx.Query("csrf") == store.Get("csrf").(string) - if !csrfCorrect { - ctx.Redirect(302, "/") - return - } - - go table.DeletePanel(messageId) - - ctx.Redirect(302, fmt.Sprintf("/manage/%d/panels", guildId)) - } else { - ctx.Redirect(302, "/login") - } -} diff --git a/app/http/endpoints/manage/panels.go b/app/http/endpoints/manage/panels.go index ff61658..3786196 100644 --- a/app/http/endpoints/manage/panels.go +++ b/app/http/endpoints/manage/panels.go @@ -2,107 +2,18 @@ package manage import ( "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "strconv" ) -type wrappedPanel struct { - MessageId uint64 - ChannelName string - Title string - Content string - CategoryName string -} - func PanelHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + guildId := ctx.Keys["guildid"].(uint64) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get active panels - panelChan := make(chan []table.Panel) - go table.GetPanelsByGuild(guildId, panelChan) - panels := <-panelChan - - // Get channels - channels := cache.Instance.GetGuildChannels(guildId) - - // Convert to wrapped panels - wrappedPanels := make([]wrappedPanel, 0) - for _, panel := range panels { - wrapper := wrappedPanel{ - MessageId: panel.MessageId, - Title: panel.Title, - Content: panel.Content, - CategoryName: "", - } - - // Get channel name & category name - for _, guildChannel := range channels { - if guildChannel.Id == panel.ChannelId { - wrapper.ChannelName = guildChannel.Name - } else if guildChannel.Id == panel.TargetCategory { - wrapper.CategoryName = guildChannel.Name - } - } - - wrappedPanels = append(wrappedPanels, wrapper) - } - - // Get is premium - isPremiumChan := make(chan bool) - go utils.IsPremiumGuild(store, guildId, isPremiumChan) - isPremium := <-isPremiumChan - - ctx.HTML(200, "manage/panels", gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "csrf": store.Get("csrf").(string), - "avatar": store.Get("avatar").(string), - "baseUrl": config.Conf.Server.BaseUrl, - "panelcount": len(panels), - "premium": isPremium, - "panels": wrappedPanels, - "channels": channels, - - "validTitle": ctx.Query("validTitle") != "true", - "validContent": ctx.Query("validContent") != "false", - "validColour": ctx.Query("validColour") != "false", - "validChannel": ctx.Query("validChannel") != "false", - "validCategory": ctx.Query("validCategory") != "false", - "validReaction": ctx.Query("validReaction") != "false", - "created": ctx.Query("created") == "true", - "metQuota": ctx.Query("metQuota") == "true", - }) - } else { - ctx.Redirect(302, "/login") - } + ctx.HTML(200, "manage/panels", gin.H{ + "name": store.Get("name").(string), + "guildId": guildId, + "avatar": store.Get("avatar").(string), + "baseUrl": config.Conf.Server.BaseUrl, + }) } diff --git a/app/http/endpoints/manage/sendmessage.go b/app/http/endpoints/manage/sendmessage.go deleted file mode 100644 index d71b732..0000000 --- a/app/http/endpoints/manage/sendmessage.go +++ /dev/null @@ -1,70 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/rpc/ratelimit" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/rxdn/gdl/rest" - "strconv" -) - -func SendMessage(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get ticket UUID from URL and verify it exists - ticketChan := make(chan table.Ticket) - go table.GetTicket(ctx.Param("uuid"), ticketChan) - ticket := <-ticketChan - exists := ticket != table.Ticket{} - - // Verify that the user has permission to be here - if ticket.Guild != guildId { - ctx.Redirect(302, fmt.Sprintf("/manage/%s/tickets", guildIdStr)) - return - } - - if exists { - content := fmt.Sprintf("**%s**: %s", store.Get("name").(string), ctx.PostForm("message")) - if len(content) > 2000 { - content = content[0:1999] - } - - _, _ = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.CreateMessageData{Content: content}) - } - } else { - ctx.Redirect(302, "/login") - } - - ctx.Redirect(301, ctx.Request.URL.String()) -} diff --git a/app/http/endpoints/manage/settings.go b/app/http/endpoints/manage/settings.go index 6093b62..65b7f0d 100644 --- a/app/http/endpoints/manage/settings.go +++ b/app/http/endpoints/manage/settings.go @@ -14,94 +14,72 @@ import ( func SettingsHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { + + userId := utils.GetUserId(store) + + // Verify the guild exists + guildIdStr := ctx.Param("id") + guildId, err := strconv.ParseUint(guildIdStr, 10, 64) + if err != nil { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page return } - defer store.Save() - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Check the bot is in the guild - guild, isInGuild := cache.Instance.GetGuild(guildId, false) - if !isInGuild { - ctx.Redirect(302, fmt.Sprintf("https://invite.ticketsbot.net/?guild_id=%s&disable_guild_select=true&response_type=code&scope=bot%%20identify&redirect_uri=%s", guildIdStr, config.Conf.Server.BaseUrl)) - return - } - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get settings from database - prefix := table.GetPrefix(guildId) - welcomeMessage := table.GetWelcomeMessage(guildId) - limit := table.GetTicketLimit(guildId) - pingEveryone := table.GetPingEveryone(guildId) - archiveChannel := table.GetArchiveChannel(guildId) - categoryId := table.GetChannelCategory(guildId) - - namingSchemeChan := make(chan table.NamingScheme) - go table.GetTicketNamingScheme(guildId, namingSchemeChan) - namingScheme := <-namingSchemeChan - - // get guild channels from cache - channels := cache.Instance.GetGuildChannels(guildId) - - // separate out categories - categories := make([]channel.Channel, 0) - for _, ch := range channels { - if ch.Type == channel.ChannelTypeGuildCategory { - categories = append(categories, ch) - } - } - - panelSettings := table.GetPanelSettings(guildId) - - // Users can close - usersCanCloseChan := make(chan bool) - go table.IsUserCanClose(guildId, usersCanCloseChan) - usersCanClose := <-usersCanCloseChan - - invalidPrefix := ctx.Query("validPrefix") == "false" - invalidWelcomeMessage := ctx.Query("validWelcomeMessage") == "false" - invalidTicketLimit := ctx.Query("validTicketLimit") == "false" - - ctx.HTML(200, "manage/settings", gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "avatar": store.Get("avatar").(string), - "prefix": prefix, - "welcomeMessage": welcomeMessage, - "ticketLimit": limit, - "categories": categories, - "activecategory": categoryId, - "channels": channels, - "archivechannel": archiveChannel, - "invalidPrefix": invalidPrefix, - "invalidWelcomeMessage": invalidWelcomeMessage, - "invalidTicketLimit": invalidTicketLimit, - "csrf": store.Get("csrf").(string), - "pingEveryone": pingEveryone, - "paneltitle": panelSettings.Title, - "panelcontent": panelSettings.Content, - "panelcolour": strconv.FormatInt(int64(panelSettings.Colour), 16), - "usersCanClose": usersCanClose, - "namingScheme": string(namingScheme), - }) - } else { - ctx.Redirect(302, "/login") + // Check the bot is in the guild + guild, isInGuild := cache.Instance.GetGuild(guildId, false) + if !isInGuild { + ctx.Redirect(302, fmt.Sprintf("https://invite.ticketsbot.net/?guild_id=%s&disable_guild_select=true&response_type=code&scope=bot%%20identify&redirect_uri=%s", guildIdStr, config.Conf.Server.BaseUrl)) + return } + + // Verify the user has permissions to be here + isAdmin := make(chan bool) + go utils.IsAdmin(guild, userId, isAdmin) + if !<-isAdmin { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page + return + } + + namingSchemeChan := make(chan table.NamingScheme) + go table.GetTicketNamingScheme(guildId, namingSchemeChan) + namingScheme := <-namingSchemeChan + + // get guild channels from cache + channels := cache.Instance.GetGuildChannels(guildId) + + // separate out categories + categories := make([]channel.Channel, 0) + for _, ch := range channels { + if ch.Type == channel.ChannelTypeGuildCategory { + categories = append(categories, ch) + } + } + + panelSettings := table.GetPanelSettings(guildId) + + // Users can close + usersCanCloseChan := make(chan bool) + go table.IsUserCanClose(guildId, usersCanCloseChan) + usersCanClose := <-usersCanCloseChan + + invalidPrefix := ctx.Query("validPrefix") == "false" + invalidWelcomeMessage := ctx.Query("validWelcomeMessage") == "false" + invalidTicketLimit := ctx.Query("validTicketLimit") == "false" + + ctx.HTML(200, "manage/settings", gin.H{ + "name": store.Get("name").(string), + "guildId": guildIdStr, + "avatar": store.Get("avatar").(string), + "categories": categories, + "channels": channels, + "invalidPrefix": invalidPrefix, + "invalidWelcomeMessage": invalidWelcomeMessage, + "invalidTicketLimit": invalidTicketLimit, + "csrf": store.Get("csrf").(string), + "paneltitle": panelSettings.Title, + "panelcontent": panelSettings.Content, + "panelcolour": strconv.FormatInt(int64(panelSettings.Colour), 16), + "usersCanClose": usersCanClose, + "namingScheme": string(namingScheme), + }) } diff --git a/app/http/endpoints/manage/ticketclose.go b/app/http/endpoints/manage/ticketclose.go deleted file mode 100644 index 3e2e75f..0000000 --- a/app/http/endpoints/manage/ticketclose.go +++ /dev/null @@ -1,76 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/messagequeue" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "strconv" -) - -func TicketCloseHandler(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get CSRF token - csrfCorrect := ctx.PostForm("csrf") == store.Get("csrf").(string) - if !csrfCorrect { - ctx.Redirect(302, "/") - return - } - - // Get the UUID - uuid := ctx.Param("uuid") - - // Verify that tbe ticket exists - ticketChan := make(chan table.Ticket) - go table.GetTicket(uuid, ticketChan) - ticket := <-ticketChan - - if ticket.Uuid == "" { - ctx.Redirect(302, fmt.Sprintf("/manage/%d/tickets/view/%s?sucess=false", guildId, uuid)) - return - } - - // Get the reason - reason := ctx.PostForm("reason") - if len(reason) > 255 { - reason = reason[:255] - } - - go messagequeue.Client.PublishTicketClose(ticket.Uuid, userId, reason) - - ctx.Redirect(302, fmt.Sprintf("/manage/%d/tickets", guildId)) - } else { - ctx.Redirect(302, "/login") - } -} diff --git a/app/http/endpoints/manage/ticketlist.go b/app/http/endpoints/manage/ticketlist.go index 7d7221c..f49b78e 100644 --- a/app/http/endpoints/manage/ticketlist.go +++ b/app/http/endpoints/manage/ticketlist.go @@ -2,101 +2,18 @@ package manage import ( "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "strconv" - "strings" ) func TicketListHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + guildId := ctx.Keys["guildid"].(uint64) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - tickets := table.GetOpenTickets(guildId) - - var toFetch []uint64 - for _, ticket := range tickets { - toFetch = append(toFetch, ticket.Owner) - - for _, idStr := range strings.Split(ticket.Members, ",") { - if memberId, err := strconv.ParseUint(idStr, 10, 64); err == nil { - toFetch = append(toFetch, memberId) - } - } - } - - nodes := make(map[uint64]table.UsernameNode) - for _, node := range table.GetUserNodes(toFetch) { - nodes[node.Id] = node - } - - var ticketsFormatted []map[string]interface{} - - for _, ticket := range tickets { - var membersFormatted []map[string]interface{} - for index, memberIdStr := range strings.Split(ticket.Members, ",") { - if memberId, err := strconv.ParseUint(memberIdStr, 10, 64); err == nil { - if memberId != 0 { - var separator string - if index != len(strings.Split(ticket.Members, ",")) - 1 { - separator = ", " - } - - membersFormatted = append(membersFormatted, map[string]interface{}{ - "username": nodes[memberId].Name, - "discrim": nodes[memberId].Discriminator, - "sep": separator, - }) - } - } - } - - ticketsFormatted = append(ticketsFormatted, map[string]interface{}{ - "uuid": ticket.Uuid, - "ticketId": ticket.TicketId, - "username": nodes[ticket.Owner].Name, - "discrim": nodes[ticket.Owner].Discriminator, - "members": membersFormatted, - }) - } - - ctx.HTML(200, "manage/ticketlist", gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "csrf": store.Get("csrf").(string), - "avatar": store.Get("avatar").(string), - "baseUrl": config.Conf.Server.BaseUrl, - "tickets": ticketsFormatted, - }) - } else { - ctx.Redirect(302, "/login") - } + ctx.HTML(200, "manage/ticketlist", gin.H{ + "name": store.Get("name").(string), + "guildId": guildId, + "avatar": store.Get("avatar").(string), + "baseUrl": config.Conf.Server.BaseUrl, + }) } diff --git a/app/http/endpoints/manage/ticketview.go b/app/http/endpoints/manage/ticketview.go index d5c18ba..e9417eb 100644 --- a/app/http/endpoints/manage/ticketview.go +++ b/app/http/endpoints/manage/ticketview.go @@ -1,117 +1,20 @@ package manage import ( - "fmt" "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/rpc/ratelimit" - "github.com/TicketsBot/GoPanel/utils" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "github.com/rxdn/gdl/rest" - "regexp" - "strconv" - "strings" ) -var MentionRegex, _ = regexp.Compile("<@(\\d+)>") - func TicketViewHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + guildId := ctx.Keys["guildid"].(uint64) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get ticket UUID from URL and verify it exists - uuid := ctx.Param("uuid") - ticketChan := make(chan table.Ticket) - go table.GetTicket(uuid, ticketChan) - ticket := <-ticketChan - exists := ticket != table.Ticket{} - - // If invalid ticket UUID, take user to ticket list - if !exists { - ctx.Redirect(302, fmt.Sprintf("/manage/%s/tickets", guildIdStr)) - return - } - - // Verify that the user has permission to be here - if ticket.Guild != guildId { - ctx.Redirect(302, fmt.Sprintf("/manage/%s/tickets", guildIdStr)) - return - } - - // Get messages - messages, err := rest.GetChannelMessages(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.GetChannelMessagesData{Limit: 100}) - - // Format messages, exclude unneeded data - var messagesFormatted []map[string]interface{} - for _, message := range utils.Reverse(messages) { - content := message.Content - - // Format mentions properly - match := MentionRegex.FindAllStringSubmatch(content, -1) - for _, mention := range match { - if len(mention) >= 2 { - mentionedId, err := strconv.ParseUint(mention[1], 10, 64) - if err != nil { - continue - } - - ch := make(chan string) - go table.GetUsername(mentionedId, ch) - content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", <-ch)) - } - } - - messagesFormatted = append(messagesFormatted, map[string]interface{}{ - "username": message.Author.Username, - "content": content, - }) - } - - premium := make(chan bool) - go utils.IsPremiumGuild(store, guildId, premium) - - ctx.HTML(200, "manage/ticketview", gin.H{ - "name": store.Get("name").(string), - "guildId": guildIdStr, - "csrf": store.Get("csrf").(string), - "avatar": store.Get("avatar").(string), - "baseUrl": config.Conf.Server.BaseUrl, - "isError": false, - "error": "", - "messages": messagesFormatted, - "ticketId": ticket.TicketId, - "uuid": ticket.Uuid, - "include_mock": true, - "premium": <-premium, - }) - } else { - ctx.Redirect(302, "/login") - } + ctx.HTML(200, "manage/ticketview", gin.H{ + "name": store.Get("name").(string), + "guildId": guildId, + "avatar": store.Get("avatar").(string), + "baseUrl": config.Conf.Server.BaseUrl, + "uuid": ctx.Param("uuid"), + }) } diff --git a/app/http/endpoints/manage/updatesettings.go b/app/http/endpoints/manage/updatesettings.go deleted file mode 100644 index 9087f22..0000000 --- a/app/http/endpoints/manage/updatesettings.go +++ /dev/null @@ -1,136 +0,0 @@ -package manage - -import ( - "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" - "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/utils" - "github.com/gin-gonic/contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/rxdn/gdl/objects/channel" - "strconv" -) - -func UpdateSettingsHandler(ctx *gin.Context) { - store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() - - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - // Verify the guild exists - guildIdStr := ctx.Param("id") - guildId, err := strconv.ParseUint(guildIdStr, 10, 64) - if err != nil { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page - return - } - - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildId, false) - - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page - return - } - - // Get CSRF token - csrfCorrect := ctx.PostForm("csrf") == store.Get("csrf").(string) - if !csrfCorrect { - ctx.Redirect(302, "/") - return - } - - // Get prefix - prefix := ctx.PostForm("prefix") - prefixValid := false - if prefix != "" && len(prefix) < 8 { - table.UpdatePrefix(guildId, prefix) - prefixValid = true - } - - // Get welcome message - welcomeMessageValid := false - welcomeMessage := ctx.PostForm("welcomeMessage") - if welcomeMessage != "" && len(welcomeMessage) < 1000 { - table.UpdateWelcomeMessage(guildId, welcomeMessage) - welcomeMessageValid = true - } - - // Get ticket limit - var limit int - limitStr := ctx.PostForm("ticketlimit") - - // Verify input is an int and overwrite default limit - if utils.IsInt(limitStr) { - limit, _ = strconv.Atoi(limitStr) - } - - // Update limit, or get current limit if user input is invalid - ticketLimitValid := false - if limitStr != "" && utils.IsInt(limitStr) && limit >= 1 && limit <= 10 { - table.UpdateTicketLimit(guildId, limit) - ticketLimitValid = true - } - - // Ping everyone - pingEveryone := ctx.PostForm("pingeveryone") == "on" - table.UpdatePingEveryone(guildId, pingEveryone) - - // Get a list of actual category IDs - channels := cache.Instance.GetGuildChannels(guildId) - - // Update category - if categoryId, err := strconv.ParseUint(ctx.PostForm("category"), 10, 64); err == nil { - for _, ch := range channels { - if ch.Id == categoryId { // compare ID - if ch.Type == channel.ChannelTypeGuildCategory { // verify we're dealing with a category - table.UpdateChannelCategory(guildId, categoryId) - } - break - } - } - } - - // Archive channel - if archiveChannelId, err := strconv.ParseUint(ctx.PostForm("archivechannel"), 10, 64); err == nil { - for _, ch := range channels { - if ch.Id == archiveChannelId { // compare ID - if ch.Type == channel.ChannelTypeGuildText { // verify channel type - table.UpdateArchiveChannel(guildId, archiveChannelId) - } - break - } - } - } - - // Users can close - usersCanClose := ctx.PostForm("userscanclose") == "on" - table.SetUserCanClose(guildId, usersCanClose) - - // Get naming scheme - namingScheme := table.NamingScheme(ctx.PostForm("namingscheme")) - isValidScheme := false - for _, validNamingScheme := range table.Schemes { - if validNamingScheme == namingScheme { - isValidScheme = true - break - } - } - - if isValidScheme { - go table.SetTicketNamingScheme(guildId, namingScheme) - } - - ctx.Redirect(302, fmt.Sprintf("/manage/%d/settings?validPrefix=%t&validWelcomeMessage=%t&validTicketLimit=%t", guildId, prefixValid, welcomeMessageValid, ticketLimitValid)) - } else { - ctx.Redirect(302, "/login") - } -} diff --git a/app/http/endpoints/manage/webchatws.go b/app/http/endpoints/manage/webchatws.go index b5c80da..7877972 100644 --- a/app/http/endpoints/manage/webchatws.go +++ b/app/http/endpoints/manage/webchatws.go @@ -1,22 +1,14 @@ package manage import ( - "bytes" - "encoding/json" "fmt" - "github.com/TicketsBot/GoPanel/config" - "github.com/TicketsBot/GoPanel/database/table" "github.com/TicketsBot/GoPanel/rpc/cache" - "github.com/TicketsBot/GoPanel/rpc/ratelimit" "github.com/TicketsBot/GoPanel/utils" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - "github.com/rxdn/gdl/rest" - "net/http" "strconv" "sync" - "time" ) var upgrader = websocket.Upgrader{ @@ -47,169 +39,95 @@ type ( func WebChatWs(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { + + conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil) + if err != nil { + fmt.Println(err.Error()) return } - defer store.Save() - if utils.IsLoggedIn(store) { - conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil) - if err != nil { - fmt.Println(err.Error()) - return - } - - socket := &Socket{ - Ws: conn, - } - - conn.SetCloseHandler(func(code int, text string) error { - i := -1 - SocketsLock.Lock() - - for index, element := range Sockets { - if element == socket { - i = index - break - } - } - - if i != -1 { - Sockets = Sockets[:i+copy(Sockets[i:], Sockets[i+1:])] - } - SocketsLock.Unlock() - - return nil - }) + socket := &Socket{ + Ws: conn, + } + conn.SetCloseHandler(func(code int, text string) error { + i := -1 SocketsLock.Lock() - Sockets = append(Sockets, socket) - SocketsLock.Unlock() - userId := utils.GetUserId(store) - var guildId string - var guildIdParsed uint64 - var ticket int - - for { - var evnt WsEvent - err := conn.ReadJSON(&evnt) - if err != nil { + for index, element := range Sockets { + if element == socket { + i = index break } + } - if guildId == "" && evnt.Type != "auth" { + if i != -1 { + Sockets = Sockets[:i+copy(Sockets[i:], Sockets[i+1:])] + } + SocketsLock.Unlock() + + return nil + }) + + SocketsLock.Lock() + Sockets = append(Sockets, socket) + SocketsLock.Unlock() + userId := utils.GetUserId(store) + + var guildId string + var guildIdParsed uint64 + var ticket int + + for { + var evnt WsEvent + err := conn.ReadJSON(&evnt) + if err != nil { + break + } + + if guildId == "" && evnt.Type != "auth" { + conn.Close() + break + } else if evnt.Type == "auth" { + data := evnt.Data.(map[string]interface{}) + + guildId = data["guild"].(string) + ticket, err = strconv.Atoi(data["ticket"].(string)) + if err != nil { conn.Close() break - } else if evnt.Type == "auth" { - data := evnt.Data.(map[string]interface{}) + } - guildId = data["guild"].(string) - ticket, err = strconv.Atoi(data["ticket"].(string)); - if err != nil { - conn.Close() - } + socket.Guild = guildId + socket.Ticket = ticket - socket.Guild = guildId - socket.Ticket = ticket + // Verify the guild exists + guildIdParsed, err = strconv.ParseUint(guildId, 10, 64) + if err != nil { + fmt.Println(err.Error()) + conn.Close() + return + } - // Verify the guild exists - guildIdParsed, err = strconv.ParseUint(guildId, 10, 64) - if err != nil { - fmt.Println(err.Error()) - conn.Close() - return - } + // Get object for selected guild + guild, _ := cache.Instance.GetGuild(guildIdParsed, false) - // Get object for selected guild - guild, _ := cache.Instance.GetGuild(guildIdParsed, false) + // Verify the user has permissions to be here + isAdmin := make(chan bool) + go utils.IsAdmin(guild, userId, isAdmin) + if !<-isAdmin { + fmt.Println(err.Error()) + conn.Close() + return + } - // Verify the user has permissions to be here - isAdmin := make(chan bool) - go utils.IsAdmin(guild, userId, isAdmin) - if !<-isAdmin { - fmt.Println(err.Error()) - conn.Close() - return - } - - // Verify the guild is premium - premium := make(chan bool) - go utils.IsPremiumGuild(store, guildIdParsed, premium) - if !<-premium { - conn.Close() - return - } - } else if evnt.Type == "send" { - data := evnt.Data.(string) - - if data == "" { - continue - } - - // Get ticket UUID from URL and verify it exists - ticketChan := make(chan table.Ticket) - go table.GetTicketById(guildIdParsed, ticket, ticketChan) - ticket := <-ticketChan - exists := ticket != table.Ticket{} - - if exists { - content := data - if len(content) > 2000 { - content = content[0:1999] - } - - // Preferably send via a webhook - webhookChan := make(chan *string) - go table.GetWebhookByUuid(ticket.Uuid, webhookChan) - webhook := <-webhookChan - - success := false - if webhook != nil { - success = executeWebhook( ticket.Uuid, *webhook, content, store) - } - - if !success { - content = fmt.Sprintf("**%s**: %s", store.Get("name").(string), data) - if len(content) > 2000 { - content = content[0:1999] - } - - _, _ = rest.CreateMessage(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Channel, rest.CreateMessageData{Content: content}) - } - } + // Verify the guild is premium + premium := make(chan bool) + go utils.IsPremiumGuild(guildIdParsed, premium) + if !<-premium { + conn.Close() + return } } } } - -func executeWebhook(uuid, webhook, content string, store sessions.Session) bool { - body := map[string]interface{}{ - "content": content, - "username": store.Get("name").(string), - "avatar_url": store.Get("avatar").(string), - } - encoded, err := json.Marshal(&body); if err != nil { - return false - } - req, err := http.NewRequest("POST", fmt.Sprintf("https://canary.discordapp.com/api/webhooks/%s", webhook), bytes.NewBuffer(encoded)); if err != nil { - return false - } - - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - client.Timeout = 3 * time.Second - - res, err := client.Do(req); if err != nil { - return false - } - - if res.StatusCode == 404 || res.StatusCode == 403 { - go table.DeleteWebhookByUuid(uuid) - } else { - return true - } - - return false -} diff --git a/app/http/endpoints/root/index.go b/app/http/endpoints/root/index.go index f91cac3..f2a4308 100644 --- a/app/http/endpoints/root/index.go +++ b/app/http/endpoints/root/index.go @@ -11,39 +11,27 @@ import ( func IndexHandler(ctx *gin.Context) { store := sessions.Default(ctx) - if store == nil { - return - } - defer store.Save() + userId := utils.GetUserId(store) - if utils.IsLoggedIn(store) { - userId := utils.GetUserId(store) - - userGuilds := table.GetGuilds(userId) - adminGuilds := make([]guild.Guild, 0) - for _, g := range userGuilds { - fakeGuild := guild.Guild{ - Id: g.Id, - OwnerId: g.OwnerId, - Permissions: g.Permissions, - } - - isAdmin := make(chan bool) - go utils.IsAdmin(fakeGuild, userId, isAdmin) - if <-isAdmin { - adminGuilds = append(adminGuilds, g) - } + userGuilds := table.GetGuilds(userId) + adminGuilds := make([]guild.Guild, 0) + for _, g := range userGuilds { + fakeGuild := guild.Guild{ + Id: g.Id, + OwnerId: g.OwnerId, + Permissions: g.Permissions, } - ctx.HTML(200, "main/index", gin.H{ - "name": store.Get("name").(string), - "baseurl": config.Conf.Server.BaseUrl, - "servers": adminGuilds, - "empty": len(adminGuilds) == 0, - "isIndex": true, - "avatar": store.Get("avatar").(string), - }) - } else { - ctx.Redirect(302, "/login") + isAdmin := make(chan bool) + go utils.IsAdmin(fakeGuild, userId, isAdmin) + if <-isAdmin { + adminGuilds = append(adminGuilds, g) + } } + + ctx.HTML(200, "main/index", gin.H{ + "name": store.Get("name").(string), + "baseurl": config.Conf.Server.BaseUrl, + "avatar": store.Get("avatar").(string), + }) } diff --git a/app/http/middleware/authenticatecookie.go b/app/http/middleware/authenticatecookie.go new file mode 100644 index 0000000..0f34458 --- /dev/null +++ b/app/http/middleware/authenticatecookie.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/contrib/sessions" + "github.com/gin-gonic/gin" +) + +func AuthenticateCookie(ctx *gin.Context) { + store := sessions.Default(ctx) + if store == nil { + ctx.Redirect(302, "/login") + ctx.Abort() + return + } + + if !utils.IsLoggedIn(store) { + ctx.Redirect(302, "/login") + ctx.Abort() + return + } + + ctx.Keys["userid"] = utils.GetUserId(store) +} diff --git a/app/http/middleware/authenticateguild.go b/app/http/middleware/authenticateguild.go new file mode 100644 index 0000000..6eb5af5 --- /dev/null +++ b/app/http/middleware/authenticateguild.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/TicketsBot/GoPanel/rpc/cache" + "github.com/TicketsBot/GoPanel/utils" + "github.com/gin-gonic/gin" + "strconv" +) + +// requires AuthenticateCookie middleware to be run before +func AuthenticateGuild(isApiMethod bool) gin.HandlerFunc { + return func(ctx *gin.Context) { + if guildId, ok := ctx.Params.Get("id"); ok { + parsed, err := strconv.ParseUint(guildId, 10, 64) + if err != nil { + if isApiMethod { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page + ctx.Abort() + } else { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Invalid guild ID", + }) + } + return + } + + ctx.Keys["guildid"] = parsed + + guild, found := cache.Instance.GetGuild(parsed, false) + if !found { + if isApiMethod { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page + } else { + ctx.Redirect(302, fmt.Sprintf("https://invite.ticketsbot.net/?guild_id=%d&disable_guild_select=true&response_type=code&scope=bot%%20identify&redirect_uri=%s", parsed, config.Conf.Server.BaseUrl)) + } + ctx.Abort() + return + } + + ctx.Keys["guild"] = guild + + // Verify the user has permissions to be here + isAdmin := make(chan bool) + go utils.IsAdmin(guild, ctx.Keys["userid"].(uint64), isAdmin) + if !<-isAdmin { + if isApiMethod { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page + ctx.Abort() + } else { + ctx.AbortWithStatusJSON(403, gin.H{ + "success": false, + "error": "Unauthorized", + }) + } + } + } else { + if isApiMethod { + ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page + ctx.Abort() + } else { + ctx.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": "Invalid guild ID", + }) + } + } + } +} diff --git a/app/http/middleware/authenticatetoken.go b/app/http/middleware/authenticatetoken.go new file mode 100644 index 0000000..4bab341 --- /dev/null +++ b/app/http/middleware/authenticatetoken.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "errors" + "fmt" + "github.com/TicketsBot/GoPanel/config" + "github.com/dgrijalva/jwt-go" + "github.com/gin-gonic/gin" + "strconv" +) + +func AuthenticateToken(ctx *gin.Context) { + header := ctx.GetHeader("Authorization") + + token, err := jwt.Parse(header, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + + return []byte(config.Conf.Server.Secret), nil + }) + + if err != nil { + ctx.AbortWithStatusJSON(403, gin.H{ + "error": err.Error(), + }) + return + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + userId, hasUserId := claims["userid"] + if !hasUserId { + ctx.AbortWithStatusJSON(403, gin.H{ + "error": errors.New("token is invalid"), + }) + return + } + + parsedId, err := strconv.ParseUint(userId.(string), 10, 64) + if err != nil { + ctx.AbortWithStatusJSON(403, gin.H{ + "error": errors.New("token is invalid"), + }) + return + } + + ctx.Keys["userid"] = parsedId + } else { + ctx.AbortWithStatusJSON(403, gin.H{ + "error": errors.New("token is invalid"), + }) + return + } +} diff --git a/app/http/server.go b/app/http/server.go index c463480..2d3b0e9 100644 --- a/app/http/server.go +++ b/app/http/server.go @@ -2,8 +2,10 @@ package http import ( "fmt" + "github.com/TicketsBot/GoPanel/app/http/endpoints/api" "github.com/TicketsBot/GoPanel/app/http/endpoints/manage" "github.com/TicketsBot/GoPanel/app/http/endpoints/root" + "github.com/TicketsBot/GoPanel/app/http/middleware" "github.com/TicketsBot/GoPanel/config" "github.com/gin-contrib/multitemplate" "github.com/gin-contrib/static" @@ -31,33 +33,66 @@ func StartServer() { // Handle static asset requests router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false))) + router.Use(gin.Recovery()) + // Register templates router.HTMLRender = createRenderer() - router.GET("/", root.IndexHandler) - router.GET("/login", root.LoginHandler) router.GET("/callback", root.CallbackHandler) - router.GET("/logout", root.LogoutHandler) - router.GET("/manage/:id/settings", manage.SettingsHandler) - router.POST("/manage/:id/settings", manage.UpdateSettingsHandler) + router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect - router.GET("/manage/:id/logs/page/:page", manage.LogsHandler) - router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) + authorized := router.Group("/", middleware.AuthenticateCookie) + { + authorized.POST("/token", api.TokenHandler) - router.GET("/manage/:id/blacklist", manage.BlacklistHandler) - router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler) + authenticateGuild := authorized.Group("/", middleware.AuthenticateGuild(false)) - router.GET("/manage/:id/panels", manage.PanelHandler) - router.POST("/manage/:id/panels/create", manage.PanelCreateHandler) - router.GET("/manage/:id/panels/delete/:msg", manage.PanelDeleteHandler) + authorized.GET("/", root.IndexHandler) + authorized.GET("/logout", root.LogoutHandler) - router.GET("/manage/:id/tickets", manage.TicketListHandler) - router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler) - router.POST("/manage/:id/tickets/view/:uuid/close", manage.TicketCloseHandler) - router.POST("/manage/:id/tickets/view/:uuid", manage.SendMessage) - router.GET("/webchat", manage.WebChatWs) + authenticateGuild.GET("/manage/:id/settings", manage.SettingsHandler) + authenticateGuild.GET("/manage/:id/logs", manage.LogsHandler) + authenticateGuild.GET("/manage/:id/blacklist", manage.BlacklistHandler) + authenticateGuild.GET("/manage/:id/panels", manage.PanelHandler) + + authenticateGuild.GET("/manage/:id/tickets", manage.TicketListHandler) + authenticateGuild.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler) + authenticateGuild.POST("/manage/:id/tickets/view/:uuid", api.SendMessage) + + authorized.GET("/webchat", manage.WebChatWs) + } + + apiGroup := router.Group("/api", middleware.AuthenticateToken) + guildAuthApi := apiGroup.Group("/", middleware.AuthenticateGuild(true)) + { + guildAuthApi.GET("/:id/channels", api.ChannelsHandler) + guildAuthApi.GET("/:id/premium", api.PremiumHandler) + + guildAuthApi.GET("/:id/settings", api.GetSettingsHandler) + guildAuthApi.POST("/:id/settings", api.UpdateSettingsHandler) + + guildAuthApi.GET("/:id/blacklist", api.GetBlacklistHandler) + guildAuthApi.PUT("/:id/blacklist", api.AddBlacklistHandler) + guildAuthApi.DELETE("/:id/blacklist/:user", api.RemoveBlacklistHandler) + + guildAuthApi.GET("/:id/panels", api.ListPanels) + guildAuthApi.PUT("/:id/panels", api.CreatePanel) + guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel) + + guildAuthApi.GET("/:id/logs/:page", api.GetLogs) + + guildAuthApi.GET("/:id/tickets", api.GetTickets) + guildAuthApi.GET("/:id/tickets/:uuid", api.GetTicket) + guildAuthApi.POST("/:id/tickets/:uuid", api.SendMessage) + guildAuthApi.DELETE("/:id/tickets/:uuid", api.CloseTicket) + } + + userGroup := router.Group("/user", middleware.AuthenticateToken) + { + userGroup.GET("/guilds", api.GetGuilds) + } if err := router.Run(config.Conf.Server.Host); err != nil { panic(err) diff --git a/config/config.go b/config/config.go index be0dcf3..4c713d5 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,7 @@ type ( MainSite string Ratelimit Ratelimit Session Session + Secret string } Ratelimit struct { diff --git a/database/table/archivechannel.go b/database/table/archivechannel.go index 6748a9d..8bbc01b 100644 --- a/database/table/archivechannel.go +++ b/database/table/archivechannel.go @@ -18,9 +18,8 @@ func UpdateArchiveChannel(guildId uint64, channelId uint64) { database.Database.Where(ArchiveChannel{Guild: guildId}).Assign(ArchiveChannel{Channel: channelId}).FirstOrCreate(&channel) } -func GetArchiveChannel(guildId uint64) uint64 { +func GetArchiveChannel(guildId uint64, ch chan uint64) { var channel ArchiveChannel database.Database.Where(&ArchiveChannel{Guild: guildId}).First(&channel) - - return channel.Channel + ch <- channel.Channel } diff --git a/database/table/blacklist.go b/database/table/blacklist.go index d59b8c2..6c36166 100644 --- a/database/table/blacklist.go +++ b/database/table/blacklist.go @@ -25,9 +25,7 @@ func AddBlacklist(guildId, userId uint64) { } func RemoveBlacklist(guildId, userId uint64) { - var node BlacklistNode - database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Take(&node) - database.Database.Delete(&node) + database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Delete(BlacklistNode{}) } func GetBlacklistNodes(guildId uint64) []BlacklistNode { diff --git a/database/table/channelcategory.go b/database/table/channelcategory.go index 40bd9de..b1f905c 100644 --- a/database/table/channelcategory.go +++ b/database/table/channelcategory.go @@ -17,9 +17,8 @@ func UpdateChannelCategory(guildId uint64, categoryId uint64) { database.Database.Where(&ChannelCategory{GuildId: guildId}).Assign(&ChannelCategory{Category: categoryId}).FirstOrCreate(&ChannelCategory{}) } -func GetChannelCategory(guildId uint64) uint64 { +func GetChannelCategory(guildId uint64, ch chan uint64) { var category ChannelCategory database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category) - - return category.Category + ch <- category.Category } diff --git a/database/table/panels.go b/database/table/panels.go index c65a006..59ed716 100644 --- a/database/table/panels.go +++ b/database/table/panels.go @@ -5,12 +5,12 @@ import ( ) type Panel struct { - MessageId uint64 `gorm:"column:MESSAGEID"` - ChannelId uint64 `gorm:"column:CHANNELID"` - GuildId uint64 `gorm:"column:GUILDID"` // Might be useful in the future so we store it + MessageId uint64 `gorm:"column:MESSAGEID"` + ChannelId uint64 `gorm:"column:CHANNELID"` + GuildId uint64 `gorm:"column:GUILDID"` // Might be useful in the future so we store it Title string `gorm:"column:TITLE;type:VARCHAR(255)"` Content string `gorm:"column:CONTENT;type:TEXT"` - Colour int `gorm:"column:COLOUR` + Colour uint32 `gorm:"column:COLOUR` TargetCategory uint64 `gorm:"column:TARGETCATEGORY"` ReactionEmote string `gorm:"column:REACTIONEMOTE;type:VARCHAR(32)"` } @@ -19,7 +19,7 @@ func (Panel) TableName() string { return "panels" } -func AddPanel(messageId, channelId, guildId uint64, title, content string, colour int, targetCategory uint64, reactionEmote string) { +func AddPanel(messageId, channelId, guildId uint64, title, content string, colour uint32, targetCategory uint64, reactionEmote string) { database.Database.Create(&Panel{ MessageId: messageId, ChannelId: channelId, @@ -45,6 +45,12 @@ func GetPanelsByGuild(guildId uint64, ch chan []Panel) { ch <- panels } +func GetPanel(messageId uint64, ch chan Panel) { + var row Panel + database.Database.Where(Panel{MessageId: messageId}).Take(&row) + ch <- row +} + func DeletePanel(msgId uint64) { database.Database.Where(Panel{MessageId: msgId}).Delete(Panel{}) } diff --git a/database/table/pingeveryone.go b/database/table/pingeveryone.go index 02c788e..b3c2003 100644 --- a/database/table/pingeveryone.go +++ b/database/table/pingeveryone.go @@ -29,9 +29,8 @@ func UpdatePingEveryone(guildId uint64, pingEveryone bool) { //database.Database.Where(&PingEveryone{GuildId: guildId}).Assign(&updated).FirstOrCreate(&PingEveryone{}) } -func GetPingEveryone(guildId uint64) bool { +func GetPingEveryone(guildId uint64, ch chan bool) { pingEveryone := PingEveryone{PingEveryone: true} database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone) - - return pingEveryone.PingEveryone + ch <- pingEveryone.PingEveryone } diff --git a/database/table/prefix.go b/database/table/prefix.go index cd76ee6..f0325d0 100644 --- a/database/table/prefix.go +++ b/database/table/prefix.go @@ -17,9 +17,8 @@ func UpdatePrefix(guildId uint64, prefix string) { database.Database.Where(&Prefix{GuildId: guildId}).Assign(&Prefix{Prefix: prefix}).FirstOrCreate(&Prefix{}) } -func GetPrefix(guildId uint64) string { +func GetPrefix(guildId uint64, ch chan string) { prefix := Prefix{Prefix: "t!"} database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix) - - return prefix.Prefix + ch <- prefix.Prefix } diff --git a/database/table/ticketlimit.go b/database/table/ticketlimit.go index f463e37..874a47a 100644 --- a/database/table/ticketlimit.go +++ b/database/table/ticketlimit.go @@ -17,9 +17,8 @@ func UpdateTicketLimit(guildId uint64, limit int) { database.Database.Where(&TicketLimit{GuildId: guildId}).Assign(&TicketLimit{Limit: limit}).FirstOrCreate(&TicketLimit{}) } -func GetTicketLimit(guildId uint64) int { +func GetTicketLimit(guildId uint64, ch chan int) { limit := TicketLimit{Limit: 5} database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit) - - return limit.Limit + ch <- limit.Limit } diff --git a/database/table/welcomemessage.go b/database/table/welcomemessage.go index f932860..6b4187d 100644 --- a/database/table/welcomemessage.go +++ b/database/table/welcomemessage.go @@ -17,9 +17,8 @@ func UpdateWelcomeMessage(guildId uint64, message string) { database.Database.Where(&WelcomeMessage{GuildId: guildId}).Assign(&WelcomeMessage{Message: message}).FirstOrCreate(&WelcomeMessage{}) } -func GetWelcomeMessage(guildId uint64) string { +func GetWelcomeMessage(guildId uint64, ch chan string) { message := WelcomeMessage{Message: "No message specified"} database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message) - - return message.Message + ch <- message.Message } diff --git a/go.mod b/go.mod index 7856fe2..f5d787a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/TicketsBot/archiverclient v0.0.0-20200420161043-3532ff9ea943 github.com/apex/log v1.1.2 github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/multitemplate v0.0.0-20200226145339-3e397ee01bc6 github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607 @@ -21,5 +22,5 @@ require ( github.com/pkg/errors v0.9.1 github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 github.com/rxdn/gdl v0.0.0-20200417164852-76b2d3c847c1 - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a ) diff --git a/public/static/css/discordmock.css b/public/static/css/discordmock.css index c89b02a..b737a37 100644 --- a/public/static/css/discordmock.css +++ b/public/static/css/discordmock.css @@ -26,7 +26,7 @@ align-items: center; } -.channel-name { +#channel-name { color: white; padding-left: 20px; } @@ -56,12 +56,6 @@ min-height: 100%; } -.form-control:focus { - border-color: #2e3136 !important; - background-color: #2e3136 !important; - color: white !important; -} - .message-input:focus { border-color: #2e3136 !important; background-color: #2e3136 !important; diff --git a/public/static/css/style.css b/public/static/css/style.css index 7526190..cd06d91 100644 --- a/public/static/css/style.css +++ b/public/static/css/style.css @@ -3,10 +3,19 @@ body { font-size: 1rem; font-weight: 400; line-height: 1.5; + background-color: #121212 !important; + color: white; +} + +.card { + background-color: #272727 !important; +} + +.card-title { + color: white; } .sidebar { - background: url("/assets/img/sidebar-2.jpg"); background-size: cover; overflow-x: hidden !important; } @@ -37,3 +46,68 @@ body { margin: 0 !important; padding: 0 !important; } + +.form-control, .input-group-text, .form-check-input { + border-color: #2e3136 !important; + background-color: #2e3136 !important; + color: white !important; +} + +.server { + color: white; +} + +#bg-dark { + background-color: #272727 !important; +} + +#sidebar-gradient:after { + background: #272727 !important; +} + +.simple-text { + padding: 0 !important; +} + +.white { + color: white !important; +} + +.icon { + color: white; + float: left; + vertical-align: middle; + padding-right: 5px; +} + +.avatar { + width: 32px; + height: 32px; + display: block; + background-size: cover; + border-radius: 50%; + margin-left: 5px; + float: right; + vertical-align: middle; + bottom: 100%; + top: 0; +} + +.toast-header { + background-color: #272727 !important; + color: white !important; +} + +.toast-body { + background-color: #2e3136 !important; + color: white !important; +} + +.toast { + border-radius: .25rem !important; + border-color: #272727 !important; +} + +#premium-ad { + display: none; +} diff --git a/public/templates/includes/head.tmpl b/public/templates/includes/head.tmpl index 1704ec9..edb190d 100644 --- a/public/templates/includes/head.tmpl +++ b/public/templates/includes/head.tmpl @@ -14,16 +14,55 @@ + + + + + + @@ -51,6 +90,49 @@ window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; ga('create', 'UA-161945537-1', 'auto'); ga('send', 'pageview'); - - + + + + + + {{end}} \ No newline at end of file diff --git a/public/templates/includes/navbar.tmpl b/public/templates/includes/navbar.tmpl index d6022a8..b58c103 100644 --- a/public/templates/includes/navbar.tmpl +++ b/public/templates/includes/navbar.tmpl @@ -1,23 +1,32 @@ {{define "navbar"}} -