finish rewrite
This commit is contained in:
parent
3e21f25dfd
commit
64c19c4dcf
33
app/http/endpoints/api/blacklist.go
Normal file
33
app/http/endpoints/api/blacklist.go
Normal file
@ -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)
|
||||
}
|
60
app/http/endpoints/api/blacklistadd.go
Normal file
60
app/http/endpoints/api/blacklistadd.go
Normal file
@ -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),
|
||||
})
|
||||
}
|
26
app/http/endpoints/api/blacklistremove.go
Normal file
26
app/http/endpoints/api/blacklistremove.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
23
app/http/endpoints/api/channels.go
Normal file
23
app/http/endpoints/api/channels.go
Normal file
@ -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)
|
||||
}
|
53
app/http/endpoints/api/closeticket.go
Normal file
53
app/http/endpoints/api/closeticket.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
76
app/http/endpoints/api/getticket.go
Normal file
76
app/http/endpoints/api/getticket.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
49
app/http/endpoints/api/gettickets.go
Normal file
49
app/http/endpoints/api/gettickets.go
Normal file
@ -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)
|
||||
}
|
30
app/http/endpoints/api/guilds.go
Normal file
30
app/http/endpoints/api/guilds.go
Normal file
@ -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)
|
||||
}
|
112
app/http/endpoints/api/logslist.go
Normal file
112
app/http/endpoints/api/logslist.go
Normal file
@ -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)
|
||||
}
|
135
app/http/endpoints/api/panelcreate.go
Normal file
135
app/http/endpoints/api/panelcreate.go
Normal file
@ -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
|
||||
}
|
43
app/http/endpoints/api/paneldelete.go
Normal file
43
app/http/endpoints/api/paneldelete.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
40
app/http/endpoints/api/panellist.go
Normal file
40
app/http/endpoints/api/panellist.go
Normal file
@ -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)
|
||||
}
|
17
app/http/endpoints/api/premium.go
Normal file
17
app/http/endpoints/api/premium.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
132
app/http/endpoints/api/sendmessage.go
Normal file
132
app/http/endpoints/api/sendmessage.go
Normal file
@ -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
|
||||
}
|
56
app/http/endpoints/api/settings.go
Normal file
56
app/http/endpoints/api/settings.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
34
app/http/endpoints/api/token.go
Normal file
34
app/http/endpoints/api/token.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
130
app/http/endpoints/api/updatesettings.go
Normal file
130
app/http/endpoints/api/updatesettings.go
Normal file
@ -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)
|
||||
}
|
@ -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()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
ctx.HTML(200, "manage/blacklist", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"guildId": guildId,
|
||||
"avatar": store.Get("avatar").(string),
|
||||
"baseUrl": config.Conf.Server.BaseUrl,
|
||||
"blacklisted": blacklisted,
|
||||
"userNotFound": userNotFound,
|
||||
"isStaff": isStaff,
|
||||
})
|
||||
} else {
|
||||
ctx.Redirect(302, "/login")
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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{
|
||||
ctx.HTML(200, "manage/logs", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"guildId": guildId,
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
||||
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
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
ctx.HTML(200, "manage/panels", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"guildId": guildId,
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
@ -14,12 +14,7 @@ import (
|
||||
|
||||
func SettingsHandler(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
|
||||
@ -45,14 +40,6 @@ func SettingsHandler(ctx *gin.Context) {
|
||||
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
|
||||
@ -83,25 +70,16 @@ func SettingsHandler(ctx *gin.Context) {
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
ctx.HTML(200, "manage/ticketlist", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"guildId": guildId,
|
||||
"avatar": store.Get("avatar").(string),
|
||||
"baseUrl": config.Conf.Server.BaseUrl,
|
||||
"tickets": ticketsFormatted,
|
||||
})
|
||||
} else {
|
||||
ctx.Redirect(302, "/login")
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
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)
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
ctx.HTML(200, "manage/ticketview", gin.H{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"guildId": guildId,
|
||||
"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,
|
||||
"uuid": ctx.Param("uuid"),
|
||||
})
|
||||
} else {
|
||||
ctx.Redirect(302, "/login")
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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,12 +39,7 @@ type (
|
||||
|
||||
func WebChatWs(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer store.Save()
|
||||
|
||||
if utils.IsLoggedIn(store) {
|
||||
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
@ -105,9 +92,10 @@ func WebChatWs(ctx *gin.Context) {
|
||||
data := evnt.Data.(map[string]interface{})
|
||||
|
||||
guildId = data["guild"].(string)
|
||||
ticket, err = strconv.Atoi(data["ticket"].(string));
|
||||
ticket, err = strconv.Atoi(data["ticket"].(string))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
socket.Guild = guildId
|
||||
@ -135,81 +123,11 @@ func WebChatWs(ctx *gin.Context) {
|
||||
|
||||
// Verify the guild is premium
|
||||
premium := make(chan bool)
|
||||
go utils.IsPremiumGuild(store, guildIdParsed, premium)
|
||||
go utils.IsPremiumGuild(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})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -11,12 +11,6 @@ import (
|
||||
|
||||
func IndexHandler(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer store.Save()
|
||||
|
||||
if utils.IsLoggedIn(store) {
|
||||
userId := utils.GetUserId(store)
|
||||
|
||||
userGuilds := table.GetGuilds(userId)
|
||||
@ -38,12 +32,6 @@ func IndexHandler(ctx *gin.Context) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
24
app/http/middleware/authenticatecookie.go
Normal file
24
app/http/middleware/authenticatecookie.go
Normal file
@ -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)
|
||||
}
|
71
app/http/middleware/authenticateguild.go
Normal file
71
app/http/middleware/authenticateguild.go
Normal file
@ -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",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
app/http/middleware/authenticatetoken.go
Normal file
54
app/http/middleware/authenticatetoken.go
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -22,6 +22,7 @@ type (
|
||||
MainSite string
|
||||
Ratelimit Ratelimit
|
||||
Session Session
|
||||
Secret string
|
||||
}
|
||||
|
||||
Ratelimit struct {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type Panel struct {
|
||||
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{})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
3
go.mod
3
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
|
||||
)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -14,16 +14,55 @@
|
||||
<link href="/assets/css/style.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
.avatar {
|
||||
background: url("{{.avatar}}?size=32");
|
||||
width: 28px;
|
||||
height: 29px;
|
||||
#avatar-sidebar {
|
||||
background: url("{{.avatar}}?size=256") center center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: block;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
float: right;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Vue
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
|
||||
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>-->
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"
|
||||
integrity="sha256-T/f7Sju1ZfNNfBh7skWn0idlCBcI3RwdLSS4/I7NQKQ=" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
async function getToken() {
|
||||
let token = window.localStorage.getItem('token');
|
||||
if (token == null) {
|
||||
let res = await axios.post('/token', {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (res.status !== 200 || !res.data.success) {
|
||||
console.log("An error occurred whilst retrieving an authentication token. Please contact the developer");
|
||||
console.log(res);
|
||||
return;
|
||||
}
|
||||
|
||||
token = res.data.token;
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async function setDefaultHeader() {
|
||||
const token = await getToken();
|
||||
axios.defaults.headers.common['Authorization'] = token;
|
||||
axios.defaults.validateStatus = false;
|
||||
}
|
||||
|
||||
setDefaultHeader();
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
@ -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');
|
||||
|
||||
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'
|
||||
type="80be96f83bbfbba3d4097e23-text/javascript"></script>
|
||||
|
||||
<script>
|
||||
function showToast(title, content) {
|
||||
const container = document.getElementById('toast-container');
|
||||
|
||||
container.innerHTML += `
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">${title}</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('.toast').toast('show');
|
||||
}
|
||||
|
||||
function appendTd(tr, content) {
|
||||
const td = document.createElement('td');
|
||||
td.appendChild(document.createTextNode(content));
|
||||
td.classList.add('white');
|
||||
tr.appendChild(td);
|
||||
}
|
||||
|
||||
function appendButton(tr, content, onclick) {
|
||||
const tdRemove = document.createElement('td');
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.type = 'submit';
|
||||
removeButton.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
removeButton.appendChild(document.createTextNode(content));
|
||||
removeButton.onclick = onclick;
|
||||
tdRemove.appendChild(removeButton);
|
||||
tr.appendChild(tdRemove);
|
||||
}
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js' type="80be96f83bbfbba3d4097e23-text/javascript"></script>
|
||||
{{end}}
|
@ -1,23 +1,32 @@
|
||||
{{define "navbar"}}
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" id="bg-dark">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/settings">Settings</a>
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/settings"><i class="fas fa-cogs icon"></i>Settings</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/logs/page/1">Logs</a>
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/logs"><i class="fas fa-copy icon"></i>Logs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/blacklist">Blacklist</a>
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/blacklist"><i class="fas fa-ban icon"></i>Blacklist</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/tickets">Ticket List</a>
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/tickets"><i class="fas fa-ticket-alt icon"></i>Ticket List</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/panels">Reaction Panels</a>
|
||||
<a class="nav-link" href="/manage/{{.guildId}}/panels"><i class="fas fa-mouse-pointer icon"></i>Reaction Panels</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav navbar-right">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/"><i class="fas fa-server icon"></i>Servers</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout"><i class="fas fa-sign-out-alt icon"></i>Logout</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">{{.name}}<img class="avatar" src="{{.avatar}}?size=256" alt="Avatar"/></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
{{end}}
|
@ -1,12 +1,6 @@
|
||||
{{define "sidebar"}}
|
||||
<div class="sidebar" data-color="orange" data-image="/assets/img/sidebar-2.jpg">
|
||||
<div class="sidebar" id="sidebar-gradient">
|
||||
<div class="sidebar-wrapper">
|
||||
<div class="logo">
|
||||
<a href="https://ticketsbot.net" class="simple-text">
|
||||
TicketsBot
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav">
|
||||
<li class="nav-item sidebar-bottom">
|
||||
<a href="/">
|
||||
@ -23,7 +17,7 @@
|
||||
</li>
|
||||
<li class="nav-item sidebar-bottom" style="bottom: 10px">
|
||||
<a href="#">
|
||||
<i class="avatar"></i>
|
||||
<i id="avatar-sidebar"></i>
|
||||
<p>{{.name}}</p>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -1,13 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="">
|
||||
<head>
|
||||
{{template "head" .}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
{{template "sidebar" .}}
|
||||
|
||||
<div class="main-panel">
|
||||
<div class="main-panel" style="width: 100% !important;">
|
||||
{{template "navbar" .}}
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
|
@ -12,27 +12,27 @@
|
||||
<div id="accordion">
|
||||
<div class="card">
|
||||
<div class="card-header collapsed filterCard" id="addBlacklistHeader" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
|
||||
<span class="align-middle" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
|
||||
<span class="align-middle white" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
|
||||
<i class="fas fa-plus"></i> Add New User
|
||||
</span>
|
||||
</div>
|
||||
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<form onsubmit="blacklistUser(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-3 pr-1">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input name="username" type="text" class="form-control" placeholder="Username">
|
||||
<input name="username" type="text" class="form-control" placeholder="Username" id="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1 px-1">
|
||||
<div class="col-md-2 px-1">
|
||||
<label>Discriminator</label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<input name="discrim" type="text" class="form-control" placeholder="0000">
|
||||
<input name="discrim" type="text" class="form-control" placeholder="0000" id="discriminator">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,7 +41,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
<button type="submit" class="btn btn-primary btn-fill mx-auto"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -59,14 +59,7 @@
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .blacklisted}}
|
||||
<tr>
|
||||
<td>{{.userId}}</td>
|
||||
<td>{{.username}}#{{.discrim}}</td>
|
||||
<td><a href="/manage/{{$.guildId}}/blacklist/remove/{{.userId}}?c={{$.csrf}}">Remove</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tbody id="blacklisted-container">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -76,40 +69,73 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
||||
{{if .userNotFound}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
The user you specified couldn't be found
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .isStaff}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
You cannot blacklist a staff member
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
async function appendUser(id, username, discrim) {
|
||||
const container = document.getElementById('blacklisted-container');
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = id;
|
||||
|
||||
appendTd(tr, id);
|
||||
appendTd(tr, `${username}#${discrim}`);
|
||||
|
||||
const tdRemove = document.createElement('td');
|
||||
const removeButton = document.createElement('button');
|
||||
removeButton.type = 'submit';
|
||||
removeButton.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
removeButton.appendChild(document.createTextNode('Remove'));
|
||||
removeButton.onclick = () => {unblacklistUser(id)};
|
||||
tdRemove.appendChild(removeButton);
|
||||
|
||||
tr.appendChild(tdRemove);
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function unblacklistUser(id) {
|
||||
const res = await axios.delete('/api/{{.guildId}}/blacklist/' + id);
|
||||
if (res.status === 200 && res.data.success) {
|
||||
showToast("Success", "Unblacklisted user");
|
||||
|
||||
const el = document.getElementById(id);
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
showToast("Error", res.data.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function blacklistUser() {
|
||||
const username = document.getElementById('username').value;
|
||||
const discriminator = document.getElementById('discriminator').value;
|
||||
const res = await axios.put('/api/{{.guildId}}/blacklist', {
|
||||
'username': username,
|
||||
'discriminator': discriminator,
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data.success) {
|
||||
showToast("Success", "User has been blacklisted");
|
||||
appendUser(res.data.user_id, username, discriminator);
|
||||
|
||||
document.getElementById('username').value = '';
|
||||
document.getElementById('discriminator').value = '';
|
||||
} else {
|
||||
showToast("Error", res.data.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get('/api/{{.guildId}}/blacklist');
|
||||
Object.keys(res.data).forEach((userId) => {
|
||||
const userData = res.data[userId];
|
||||
appendUser(userId, userData.username, userData.discriminator);
|
||||
});
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -6,41 +6,56 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Servers</h4>
|
||||
{{if .empty}}
|
||||
<p class="card-category">Select a server to manage below</p>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .empty}}
|
||||
<p class="center-align" style="padding-top: 50px; font-size: 16px">
|
||||
<div class="card-body" id="card">
|
||||
<div class="card-body table-responsive">
|
||||
<p class="center-align white" style="padding-top: 50px; font-size: 16px; display: none" id="no-guilds">
|
||||
You are not the admin of any guilds that the bot is in. Click below to invite the bot:
|
||||
<br/>
|
||||
<a href="https://invite.ticketsbot.net">Invite</a>
|
||||
<a href="https://invite.ticketsbot.net"><button class="btn btn-primary btn-fill"><i class="fas fa-plus"></i> Invite</button></a>
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="card-body table-responsive">
|
||||
<table class="table table-hover table-striped">
|
||||
<table class="table table-hover table-striped" id="guild-table" style="display: none">
|
||||
<thead>
|
||||
<th>Server Name</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .servers}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/manage/{{.Id}}/settings">
|
||||
{{.Name}}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tbody id="guild-container">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function loadData() {
|
||||
const res = await axios.get('/user/guilds');
|
||||
|
||||
if (res.data.length > 0) {
|
||||
document.getElementById('guild-table').style.display = 'table';
|
||||
|
||||
const container = document.getElementById('guild-container');
|
||||
|
||||
for (guild of res.data) {
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `/manage/${guild.id}/settings`;
|
||||
link.classList.add('server');
|
||||
link.appendChild(document.createTextNode(guild.name));
|
||||
|
||||
td.appendChild(link);
|
||||
tr.appendChild(td);
|
||||
container.appendChild(tr);
|
||||
}
|
||||
} else {
|
||||
document.getElementById('no-guilds').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
{{end}}
|
@ -6,37 +6,37 @@
|
||||
<div id="accordion">
|
||||
<div class="card">
|
||||
<div class="card-header collapsed filterCard" id="filterHeader" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
||||
<span class="align-middle" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
||||
<span class="align-middle white" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
||||
<i class="fas fa-search"></i> Filter Logs
|
||||
</span>
|
||||
</div>
|
||||
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<form action="/manage/{{.guildId}}/logs/page/1">
|
||||
<form onsubmit="filterLogs(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-4 pr-1">
|
||||
<div class="form-group">
|
||||
<label>Ticket ID</label>
|
||||
<input name="ticketid" type="text" class="form-control" placeholder="Ticket ID">
|
||||
<input name="ticketid" type="text" class="form-control" placeholder="Ticket ID" id="ticketid">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 px-1">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input name="username" type="text" class="form-control" placeholder="Username">
|
||||
<input name="username" type="text" class="form-control" placeholder="Username" id="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 px-1">
|
||||
<div class="form-group">
|
||||
<label>User ID</label>
|
||||
<input name="userid" type="text" class="form-control" placeholder="User ID">
|
||||
<input name="userid" type="text" class="form-control" placeholder="User ID" id="userid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Filter</button>
|
||||
<button type="submit" class="btn btn-primary mx-auto btn-fill"><i class="fas fa-paper-plane"></i> Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -64,7 +64,7 @@
|
||||
<th>Log URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="log-container">
|
||||
{{range .logs}}
|
||||
<tr>
|
||||
<td>{{.ticketid}}</td>
|
||||
@ -79,14 +79,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<ul class="pagination justify-content-center">
|
||||
{{if .isPageOne}}
|
||||
<li class="disabled"><a href="#"><i class="fas fa-chevron-left"></i></a></li>
|
||||
{{else}}
|
||||
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.previousPage}}"><i class="fas fa-chevron-left"></i></a></li>
|
||||
{{end}}
|
||||
|
||||
<p class="center-align" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
|
||||
<li class="waves-effect"><a href="/manage/{{.guildId}}/logs/page/{{.nextPage}}"><i class="fas fa-chevron-right"></i></a></li>
|
||||
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
|
||||
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,5 +91,89 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--<div aria-live="polite" aria-atomic="true" style="position: relative">
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>-->
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
|
||||
function appendLog(log) {
|
||||
const container = document.getElementById('log-container');
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
appendTd(tr, log.ticketid);
|
||||
appendTd(tr, log.username);
|
||||
appendTd(tr, log.userid);
|
||||
appendButton(tr, 'View', () => { location.href = '/manage/{{.guildId}}/logs/view/' + log.ticketid });
|
||||
|
||||
// create view button
|
||||
const viewTd = document.createElement('td');
|
||||
tr.appendChild(viewTd);
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function loadPage(page, ticketId, username, userId) {
|
||||
const container = document.getElementById('log-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
let url = '/api/{{.guildId}}/logs/' + page;
|
||||
|
||||
if (ticketId !== undefined) {
|
||||
url += `?ticketid=${ticketId}`;
|
||||
} else if (username !== undefined) {
|
||||
url += `?username=${username}`;
|
||||
} else if (userId !== undefined) {
|
||||
url += `?userid=${userId}`;
|
||||
}
|
||||
|
||||
const res = await axios.get(url);
|
||||
for (log of res.data) {
|
||||
appendLog(log);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function next() {
|
||||
currentPage += 1;
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
async function previous() {
|
||||
if (currentPage <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
currentPage -= 1;
|
||||
await loadPage(currentPage);
|
||||
}
|
||||
|
||||
async function filterLogs() {
|
||||
const ticketId = document.getElementById('ticketid').value;
|
||||
const username = document.getElementById('username').value;
|
||||
const userId = document.getElementById('userid').value;
|
||||
|
||||
if (ticketId > 0) {
|
||||
await loadPage(1, ticketId);
|
||||
} else if (username !== "") {
|
||||
await loadPage(1, undefined, username);
|
||||
} else if (userId !== "") {
|
||||
await loadPage(1, undefined, undefined, userId);
|
||||
} else {
|
||||
await loadPage(1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -8,11 +8,8 @@
|
||||
<h4 class="card-title">Reaction Panels</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Your panel quota: <b>{{.panelcount}} / {{if .premium}}∞{{else}}1{{end}}</b></p>
|
||||
|
||||
{{if not .premium}}
|
||||
<p>Note: You can expand your panel quote by purchasing premium</p>
|
||||
{{end}}
|
||||
<p class="white">Your panel quota: <b><span id="panel-count"></span> / <span id="panel-quota"></span></b></p>
|
||||
<p class="white" id="premium-ad">Note: You can create unlimited panels with premium</p>
|
||||
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
@ -24,16 +21,7 @@
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .panels}}
|
||||
<tr>
|
||||
<td>#{{.ChannelName}}</td>
|
||||
<td>{{.Title}}</td>
|
||||
<td>{{.Content}}</td>
|
||||
<td>{{.CategoryName}}</td>
|
||||
<td><a href="/manage/{{$.guildId}}/panels/delete/{{.MessageId}}?csrf={{$.csrf}}">Delete</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tbody id="panel-container">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -43,19 +31,19 @@
|
||||
<h4 class="card-title">Create A Panel</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/manage/{{.guildId}}/panels/create">
|
||||
<form onsubmit="createPanel(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-4 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Panel Title</label>
|
||||
<input name="title" type="text" class="form-control" placeholder="Open a ticket!">
|
||||
<input name="title" type="text" class="form-control" placeholder="Open a ticket!" id="title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Panel Content</label>
|
||||
<textarea name="content" type="text" class="form-control"
|
||||
placeholder="By reacting to this ticket, a ticket will be opened for you."></textarea>
|
||||
placeholder="By reacting to this ticket, a ticket will be opened for you." id="content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +51,7 @@
|
||||
<div class="col-md-3 pr-1">
|
||||
<label class="black">Panel Colour</label>
|
||||
<div class="input-group mb-3">
|
||||
<input name="colour" type="color" class="form-control input-fill" value="#23A31A">
|
||||
<input name="colour" type="color" class="form-control input-fill" value="#2ECC71" id="colour">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -73,12 +61,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<select class="form-control" name="channel">
|
||||
{{range .channels}}
|
||||
{{if eq .Type 0}} <!-- Check the channel is a text channel -->
|
||||
<option value="{{.Id}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<select class="form-control" name="channel" id="channel-container">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,12 +72,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<select class="form-control" name="categories">
|
||||
{{range .channels}}
|
||||
{{if eq .Type 4}} <!-- Check the channel is a category -->
|
||||
<option value="{{.Id}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<select class="form-control" name="categories" id="category-container">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,16 +80,15 @@
|
||||
<div class="col-md-3 pr-1">
|
||||
<div class="form-group">
|
||||
<label class="black">Reaction Emote</label>
|
||||
<input name="reaction" type="text" class="form-control" placeholder="envelope_with_arrow">
|
||||
<input name="reaction" type="text" class="form-control" placeholder="envelope_with_arrow" id="reaction">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input name="csrf" type="hidden" value="{{.csrf}}">
|
||||
<div class="col-md-2 pr-1 offset-md-5">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
<button type="submit" class="btn btn-primary btn-fill"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -123,119 +100,148 @@
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
||||
{{if not .validTitle}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Panel titles must be between 1 and 255 characters long
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .validContent}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Panel content must be between 1 and 1024 characters long
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .validColour}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Invalid panel colour. You must use the hex value of the colour, which you can find <a
|
||||
href="https://www.google.co.uk/search?client=opera&q=html+colour+picker">here</a>.
|
||||
<br/>Colour has defaulted to green.
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .validChannel}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Invalid channel - please try again
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .validCategory}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Invalid category - please try again
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .validReaction}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Invalid reaction emote
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .created}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Success</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Your panel has been created. You may need to refresh this page to see it displayed.
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .metQuota}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
You've hit your panel quota. Premium users can create <b>unlimited panels</b>. Click <a
|
||||
href="https://ticketsbot.net/premium">here</a> to learn more about premium.
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
async function getChannels() {
|
||||
const res = await axios.get('/api/{{.guildId}}/channels');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function getChannelName(channels, channelId) {
|
||||
return channels.find(ch => ch.id === channelId);
|
||||
}
|
||||
|
||||
async function deletePanel(messageId) {
|
||||
const res = await axios.delete('/api/{{.guildId}}/panels/' + messageId);
|
||||
|
||||
if (res.status === 200 && res.data.success) {
|
||||
showToast('Success', 'Panel deleted successfully');
|
||||
|
||||
const el = document.getElementById(messageId);
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createPanel() {
|
||||
const data = {
|
||||
title: document.getElementById('title').value,
|
||||
content: document.getElementById('content').value,
|
||||
emote: document.getElementById('reaction').value.replace(':', ''),
|
||||
colour: parseInt(`0x${document.getElementById('colour').value.slice(1)}`),
|
||||
channel_id: document.getElementById('channel-container').options[document.getElementById('channel-container').selectedIndex].value,
|
||||
category_id: document.getElementById('category-container').options[document.getElementById('category-container').selectedIndex].value,
|
||||
};
|
||||
|
||||
// fill defaults
|
||||
if (data.title === '') {
|
||||
data.title = 'Open a ticket!';
|
||||
}
|
||||
if (data.content === '') {
|
||||
data.content = 'By reacting to this ticket, a ticket will be opened for you.';
|
||||
}
|
||||
if (data.emote === '') {
|
||||
data.emote = 'envelope_with_arrow';
|
||||
}
|
||||
|
||||
const res = await axios.put('/api/{{.guildId}}/panels', data);
|
||||
if (res.status === 200 && res.data.success) {
|
||||
appendPanel(data, await getChannels());
|
||||
showToast('Success', 'Panel created successfully')
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fillChannels(channels) {
|
||||
const container = document.getElementById('channel-container');
|
||||
|
||||
channels.filter(ch => ch.type === 0).forEach(ch => {
|
||||
const el = document.createElement('option');
|
||||
el.value = ch.id;
|
||||
el.appendChild(document.createTextNode(ch.name));
|
||||
container.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
async function fillCategories(channels) {
|
||||
const container = document.getElementById('category-container');
|
||||
|
||||
channels.filter(ch => ch.type === 4).forEach(ch => {
|
||||
const el = document.createElement('option');
|
||||
el.value = ch.id;
|
||||
el.appendChild(document.createTextNode(ch.name));
|
||||
container.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Update on append / delete
|
||||
async function fillPanelQuota(panelCount) {
|
||||
const res = await axios.get('/api/{{.guildId}}/premium');
|
||||
|
||||
const el = document.getElementById('panel-quota');
|
||||
if (res.data.premium) {
|
||||
el.appendChild(document.createTextNode('∞'));
|
||||
} else {
|
||||
el.appendChild(document.createTextNode('1'));
|
||||
document.getElementById('premium-ad').style.display = 'block';
|
||||
}
|
||||
|
||||
document.getElementById('panel-count').appendChild(document.createTextNode(panelCount));
|
||||
}
|
||||
|
||||
function appendPanel(panel, channels) {
|
||||
const container = document.getElementById('panel-container');
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = panel.message_id; // TODO: When we call this after creating a panel, we don't know the message ID yet
|
||||
|
||||
appendTd(tr, `#${getChannelName(channels, panel.channel_id).name}`);
|
||||
appendTd(tr, panel.title);
|
||||
appendTd(tr, panel.content);
|
||||
appendTd(tr, getChannelName(channels, panel.category_id).name);
|
||||
|
||||
// build remove button
|
||||
const deleteTd = document.createElement('td');
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'submit';
|
||||
deleteButton.classList.add('btn', 'btn-primary', 'btn-fill', 'mx-auto');
|
||||
deleteButton.appendChild(document.createTextNode('Delete'));
|
||||
deleteButton.onclick = () => {deletePanel(panel.message_id)};
|
||||
deleteTd.appendChild(deleteButton);
|
||||
tr.appendChild(deleteTd);
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function fillPanels(channels) {
|
||||
const res = await axios.get('/api/{{.guildId}}/panels');
|
||||
if (res.status !== 200) {
|
||||
showToast("Error", res.data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const panel of res.data) {
|
||||
appendPanel(panel, channels);
|
||||
}
|
||||
|
||||
return res.data.length;
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const channels = await getChannels();
|
||||
|
||||
const panelCount = await fillPanels(channels);
|
||||
await fillPanelQuota(panelCount);
|
||||
await fillChannels(channels);
|
||||
await fillCategories(channels);
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -11,25 +11,25 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<form onsubmit="updateSettings(); return false;">
|
||||
<div class="row">
|
||||
<div class="col-md-5 pr-1">
|
||||
<div class="form-group">
|
||||
<label>Prefix (Max len. 8)</label>
|
||||
<input name="prefix" type="text" class="form-control" placeholder="t!" value="{{.prefix}}">
|
||||
<input name="prefix" type="text" class="form-control" placeholder="t!" id="prefix">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 px-1">
|
||||
<div class="form-group">
|
||||
<label>Ticket Limit (1-10)</label>
|
||||
<input name="ticketlimit" type="text" class="form-control" placeholder="5" value="{{.ticketLimit}}">
|
||||
<input name="ticketlimit" type="text" class="form-control" placeholder="5" id="ticket_limit">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 px-1">
|
||||
<div class="form-group">
|
||||
<label>Ping @everyone on ticket open</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="pingeveryone" value="on" {{if .pingEveryone}}checked{{end}} style="width:30px;height:30px;">
|
||||
<input class="form-check-input" type="checkbox" name="pingeveryone" value="on" id="ping_everyone" style="width:30px;height:30px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,7 +38,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label>Welcome Message (Max len. 1000)</label>
|
||||
<textarea name="welcomeMessage" class="form-control" rows="3" style="resize: none">{{.welcomeMessage}}</textarea>
|
||||
<textarea name="welcomeMessage" class="form-control" rows="3" id="welcome_message"
|
||||
style="resize: none">{{.welcomeMessage}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -49,10 +50,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<select class="form-control" name="archivechannel">
|
||||
{{range .channels}}
|
||||
<option {{if eq .Id $.archivechannel }}selected{{end}} value="{{.Id}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
<select class="form-control" name="archive_channel" id="archive_channel">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,10 +58,7 @@
|
||||
<div class="col-md-5 px-1">
|
||||
<div class="form-group">
|
||||
<label>Channel Category</label>
|
||||
<select class="form-control" name="category">
|
||||
{{range .categories}}
|
||||
<option {{if eq $.activecategory .Id}}selected{{end}} value="{{.Id}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
<select class="form-control" name="category" id="category">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -72,7 +67,8 @@
|
||||
<div class="form-group">
|
||||
<label>Allow users to close tickets</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="userscanclose" value="on" {{if .usersCanClose}}checked{{end}} style="width:30px;height:30px;">
|
||||
<input class="form-check-input" type="checkbox" name="userscanclose" value="on"
|
||||
id="users_can_close" style="width:30px;height:30px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -80,23 +76,25 @@
|
||||
|
||||
<label>Ticket Naming Scheme</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-id" value="id" {{if eq .namingScheme "id"}}checked{{end}}>
|
||||
<label class="form-check-label" for="naming-by-id">
|
||||
Ticket ID
|
||||
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-id" value="id">
|
||||
<label class="form-check-label white" for="naming-by-id">
|
||||
Ticket ID (#ticket-1)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-username" value="username" {{if eq .namingScheme "username"}}checked{{end}}>
|
||||
<label class="form-check-label" for="naming-by-username">
|
||||
Username
|
||||
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-username"
|
||||
value="username">
|
||||
<label class="form-check-label white" for="naming-by-username">
|
||||
Username (#ryan-1)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input name="csrf" type="hidden" value="{{.csrf}}">
|
||||
<div class="row">
|
||||
<div class="col-md-1 pr-1">
|
||||
<div class="col-md-2 pr-1">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Submit</button>
|
||||
<button class="btn btn-primary btn-fill" type="submit"><i class="fas fa-paper-plane"></i>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -107,53 +105,137 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
||||
{{if .invalidPrefix}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
The prefix you specified was invalid
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .invalidWelcomeMessage}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
The welcome message you specified was invalid
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .invalidTicketLimit}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Warning</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
The ticket limit you specified was invalid
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative;">
|
||||
<div style="position: absolute; right: 10px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
$('#pingeveryone').prop('indeterminate', {{.pingEveryone}});
|
||||
async function getSettings() {
|
||||
const res = await axios.get('/api/{{.guildId}}/settings');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async function getChannels() {
|
||||
const res = await axios.get('/api/{{.guildId}}/channels');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async function fillArchiveChannels(channels, archiveChannelId) {
|
||||
const container = document.getElementById('archive_channel');
|
||||
channels.filter((ch) => ch.type === 0).forEach((ch) => {
|
||||
const node = document.createElement("option");
|
||||
const text = document.createTextNode(ch.name);
|
||||
node.appendChild(text);
|
||||
|
||||
node.value = ch.id;
|
||||
if (archiveChannelId === ch.id) {
|
||||
node.selected = true;
|
||||
}
|
||||
|
||||
|
||||
container.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
async function fillCategories(channels, categoryId) {
|
||||
const container = document.getElementById('category');
|
||||
channels.filter((ch) => ch.type === 4).forEach((ch) => {
|
||||
const node = document.createElement("option");
|
||||
const text = document.createTextNode(ch.name);
|
||||
node.appendChild(text);
|
||||
|
||||
node.value = ch.id;
|
||||
if (categoryId === ch.id) {
|
||||
node.selected = true;
|
||||
}
|
||||
|
||||
container.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const settings = await getSettings();
|
||||
document.getElementById("prefix").value = settings.prefix;
|
||||
document.getElementById("welcome_message").value = settings.welcome_message;
|
||||
document.getElementById("ticket_limit").value = settings.ticket_limit;
|
||||
document.getElementById("ping_everyone").checked = settings.ping_everyone;
|
||||
document.getElementById("users_can_close").checked = settings.users_can_close;
|
||||
|
||||
if (settings.naming_scheme === "username") {
|
||||
document.getElementById("naming-by-username").checked = true;
|
||||
} else {
|
||||
document.getElementById("naming-by-id").checked = true;
|
||||
}
|
||||
|
||||
const channels = await getChannels();
|
||||
await fillArchiveChannels(channels, settings.archive_channel);
|
||||
await fillCategories(channels, settings.category);
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function updateSettings() {
|
||||
const data = {
|
||||
'prefix': document.getElementById('prefix').value,
|
||||
'welcome_message': document.getElementById("welcome_message").value,
|
||||
'ticket_limit': parseInt(document.getElementById('ticket_limit').value),
|
||||
'ping_everyone': document.getElementById("ping_everyone").checked,
|
||||
'users_can_close': document.getElementById("users_can_close").checked,
|
||||
'naming_scheme': document.querySelector('input[name="namingscheme"]:checked').value,
|
||||
'archive_channel': document.getElementById('archive_channel').options[document.getElementById('archive_channel').selectedIndex].value,
|
||||
'category': document.getElementById('category').options[document.getElementById('category').selectedIndex].value
|
||||
};
|
||||
|
||||
const res = await axios.post('/api/{{.guildId}}/settings', data);
|
||||
if (res.status === 200) {
|
||||
const success = showValidations(res.data);
|
||||
if (success) {
|
||||
showToast("Success", "Your settings have been saved.")
|
||||
}
|
||||
} else {
|
||||
showToast("Error", "A severe error occurred. Please check your console for more information.");
|
||||
console.log(res);
|
||||
}
|
||||
}
|
||||
|
||||
function showValidations(data) {
|
||||
let success = true;
|
||||
|
||||
if (!data.prefix) {
|
||||
success = false;
|
||||
showToast("Warning", "Your prefix has not been saved.<br />Prefixes must be between 1 - 8 characters in length.")
|
||||
}
|
||||
|
||||
if (!data.welcome_message) {
|
||||
success = false;
|
||||
showToast("Warning", "Your welcome message has not been saved.<br />Welcome messages must be between 1 - 1000 characters in length.")
|
||||
}
|
||||
|
||||
if (!data.ticket_limit) {
|
||||
success = false;
|
||||
showToast("Warning", "Your ticket limit has not been saved.<br />Ticket limits must be in the range 1 - 10.")
|
||||
}
|
||||
|
||||
if (!data.archive_channel) {
|
||||
success = false;
|
||||
showToast("Warning", "Your archive channel has not been saved.")
|
||||
}
|
||||
|
||||
if (!data.category) {
|
||||
success = false;
|
||||
showToast("Warning", "Your channel category has not been saved.")
|
||||
}
|
||||
|
||||
if (!data.naming_scheme) {
|
||||
success = false;
|
||||
showToast("Warning", "Your archive channel has not been saved.")
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -18,15 +18,7 @@
|
||||
<th>View</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .tickets}}
|
||||
<tr>
|
||||
<td>{{.ticketId}}</td>
|
||||
<td>{{.username}}#{{.discrim}}</td>
|
||||
<td>{{range .members}}{{.username}}#{{.discrim}}{{.sep}}{{end}}</td>
|
||||
<td><a class="btn btn-primary btn-sm" role="button" href="/manage/{{$.guildId}}/tickets/view/{{.uuid}}">View</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tbody id="ticket-container">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -37,7 +29,30 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
function appendTicket(ticket) {
|
||||
const container = document.getElementById('ticket-container');
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
appendTd(tr, ticket.ticketId);
|
||||
appendTd(tr, `${ticket.username}#${ticket.discrim}`);
|
||||
|
||||
const members = ticket.members.map(member => `${member.username}#${member.discrim}`).join(', ');
|
||||
appendTd(tr, members);
|
||||
|
||||
appendButton(tr, 'View', () => { console.log(ticket); location.href = '/manage/{{.guildId}}/tickets/view/' + ticket.uuid });
|
||||
|
||||
container.appendChild(tr);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get('/api/{{.guildId}}/tickets');
|
||||
|
||||
for (ticket of res.data) {
|
||||
appendTicket(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</div>
|
||||
{{end}}
|
@ -7,11 +7,10 @@
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Close Ticket</h4>
|
||||
<div class="close-container">
|
||||
<form class="form-inline" action="/manage/{{.guildId}}/tickets/view/{{.uuid}}/close" method="post">
|
||||
<form class="form-inline" action="javascript:close()">
|
||||
<input type="text" class="form-control" id="reason" name="reason" placeholder="No reason specified" style="width: 80%">
|
||||
<input name="csrf" type="hidden" value="{{.csrf}}">
|
||||
<div style="padding-left: 10px">
|
||||
<button type="submit" class="btn btn-primary">Close Ticket</button>
|
||||
<button type="submit" class="btn btn-primary btn-fill">Close Ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -19,25 +18,13 @@
|
||||
<h4 class="card-title">View Ticket</h4>
|
||||
<div class="discord-container">
|
||||
<div class="channel-header">
|
||||
<span class="channel-name">#ticket-{{.ticketId}}</span>
|
||||
<span id="channel-name"></span>
|
||||
</div>
|
||||
<div id="message-container">
|
||||
{{range .messages}}
|
||||
<div class="message">
|
||||
<b>{{.username}}</b>
|
||||
{{.content}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<form action="javascript:sendMessage()">
|
||||
{{if .premium}}
|
||||
<input type="text" class="form-control message-input" id="message" name="message"
|
||||
placeholder="Message #ticket-{{.ticketId}}">
|
||||
{{else}}
|
||||
<input type="text" class="form-control message-input" id="message" name="message"
|
||||
placeholder="Premium users get live messages and can respond through webchat" disabled>
|
||||
{{end}}
|
||||
<form onsubmit="sendMessage(); return false;">
|
||||
<input type="text" class="form-control message-input" id="message" name="message">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -47,76 +34,115 @@
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
||||
{{if .isError}}
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto">Error</strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
{{.error}}
|
||||
<div style="position: absolute; right: 10px; min-width: 300px" id="toast-container">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
async function close() {
|
||||
const reason = document.getElementById('reason').value;
|
||||
document.getElementById('reason').value = '';
|
||||
|
||||
const res = await axios.delete('/api/{{.guildId}}/tickets/{{.uuid}}', {
|
||||
data: {
|
||||
reason: reason
|
||||
}
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data.success) {
|
||||
location.href = '/manage/{{.guildId}}/tickets';
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
async function isPremium() {
|
||||
const res = await axios.get('/api/{{.guildId}}/premium');
|
||||
return res.data.premium;
|
||||
}
|
||||
|
||||
async function appendMessages(messages) {
|
||||
const container = document.getElementById('message-container');
|
||||
|
||||
for (message of messages) {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('message');
|
||||
|
||||
const username = document.createElement('b');
|
||||
username.appendChild(document.createTextNode(message.username));
|
||||
username.appendChild(document.createTextNode(': '));
|
||||
div.appendChild(username);
|
||||
|
||||
div.appendChild(document.createTextNode(message.content));
|
||||
|
||||
container.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const premium = await isPremium();
|
||||
|
||||
const res = await axios.get('/api/{{.guildId}}/tickets/{{.uuid}}');
|
||||
if (res.status === 200 && res.data.success) {
|
||||
const data = res.data;
|
||||
document.getElementById('channel-name').innerText = `#ticket-${data.ticket.TicketId}`;
|
||||
await appendMessages(data.messages);
|
||||
|
||||
startWebsocket(data.ticket.TicketId);
|
||||
} else {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
|
||||
const el = document.getElementById('message');
|
||||
if (premium) {
|
||||
el.placeholder = `Message #ticket-${res.data.ticket.TicketId}`;
|
||||
} else {
|
||||
el.disabled = true;
|
||||
el.placeholder = 'Premium users get live messages and can respond through webchat'
|
||||
}
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Scroll to bottom
|
||||
let container = document.getElementById("message-container");
|
||||
const container = document.getElementById("message-container");
|
||||
container.scrollTop = container.scrollHeight;
|
||||
</script>
|
||||
|
||||
{{if .premium}}
|
||||
<script>
|
||||
let ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
|
||||
async function startWebsocket(ticketId) {
|
||||
//const ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
|
||||
const ws = new WebSocket("ws://localhost:3000/webchat");
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({
|
||||
"type": "auth",
|
||||
"data": {
|
||||
"guild": "{{.guildId}}",
|
||||
"ticket": "{{.ticketId}}"
|
||||
"ticket": `${ticketId}`
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = (evt) => {
|
||||
let data = JSON.parse(evt.data);
|
||||
|
||||
let container = document.getElementById("message-container");
|
||||
|
||||
let element = document.createElement("div");
|
||||
element.className = "message";
|
||||
element.innerHTML = `
|
||||
<b>${data.username}</b>
|
||||
${data.content}
|
||||
`;
|
||||
|
||||
container.appendChild(element);
|
||||
|
||||
// Scroll to bottom
|
||||
const data = JSON.parse(evt.data);
|
||||
appendMessages([data]);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
};
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
let msg = document.getElementById("message").value;
|
||||
async function sendMessage() {
|
||||
const msg = document.getElementById("message").value;
|
||||
document.getElementById("message").value = "";
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
"type": "send",
|
||||
"data": msg
|
||||
}))
|
||||
const res = await axios.post('/api/{{.guildId}}/tickets/{{.uuid}}', {message: msg});
|
||||
if (res.status !== 200 || !res.data.success) {
|
||||
showToast('Error', res.data.error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
gocache "github.com/robfig/go-cache"
|
||||
"github.com/rxdn/gdl/rest"
|
||||
"io/ioutil"
|
||||
@ -23,7 +22,7 @@ type ProxyResponse struct {
|
||||
|
||||
var premiumCache = gocache.New(10 * time.Minute, 10 * time.Minute)
|
||||
|
||||
func IsPremiumGuild(store sessions.Session, guildId uint64, ch chan bool) {
|
||||
func IsPremiumGuild(guildId uint64, ch chan bool) {
|
||||
guildIdRaw := strconv.FormatUint(guildId, 10)
|
||||
|
||||
if premium, ok := premiumCache.Get(guildIdRaw); ok {
|
||||
|
Loading…
x
Reference in New Issue
Block a user