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 (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"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/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func BlacklistHandler(ctx *gin.Context) {
|
func BlacklistHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.HTML(200, "manage/blacklist", gin.H{
|
ctx.HTML(200, "manage/blacklist", gin.H{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildId,
|
||||||
"csrf": store.Get("csrf").(string),
|
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"baseUrl": config.Conf.Server.BaseUrl,
|
"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
|
package manage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"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/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rxdn/gdl/rest"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func LogsHandler(ctx *gin.Context) {
|
func LogsHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
return
|
|
||||||
}
|
|
||||||
defer store.Save()
|
|
||||||
|
|
||||||
if utils.IsLoggedIn(store) {
|
ctx.HTML(200, "manage/logs", gin.H{
|
||||||
userId := utils.GetUserId(store)
|
|
||||||
|
|
||||||
// Verify the guild exists
|
|
||||||
guildIdStr := ctx.Param("id")
|
|
||||||
guildId, err := strconv.ParseUint(guildIdStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pageStr := ctx.Param("page")
|
|
||||||
page := 1
|
|
||||||
i, err := strconv.Atoi(pageStr)
|
|
||||||
if err == nil {
|
|
||||||
if i > 0 {
|
|
||||||
page = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get object for selected guild
|
|
||||||
guild, _ := cache.Instance.GetGuild(guildId, false)
|
|
||||||
|
|
||||||
// Verify the user has permissions to be here
|
|
||||||
isAdmin := make(chan bool)
|
|
||||||
go utils.IsAdmin(guild, userId, isAdmin)
|
|
||||||
if !<-isAdmin {
|
|
||||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pageLimit := 30
|
|
||||||
|
|
||||||
// Get ticket ID from URL
|
|
||||||
var ticketId int
|
|
||||||
if utils.IsInt(ctx.Query("ticketid")) {
|
|
||||||
ticketId, _ = strconv.Atoi(ctx.Query("ticketid"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var tickets []table.Ticket
|
|
||||||
|
|
||||||
// Get tickets from DB
|
|
||||||
if ticketId > 0 {
|
|
||||||
ticketChan := make(chan table.Ticket)
|
|
||||||
go table.GetTicketById(guildId, ticketId, ticketChan)
|
|
||||||
ticket := <-ticketChan
|
|
||||||
|
|
||||||
if ticket.Uuid != "" && !ticket.IsOpen {
|
|
||||||
tickets = append(tickets, ticket)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// make slice of user IDs to filter by
|
|
||||||
filteredIds := make([]uint64, 0)
|
|
||||||
|
|
||||||
// Add userid param to slice
|
|
||||||
filteredUserId, _ := strconv.ParseUint(ctx.Query("userid"), 10, 64)
|
|
||||||
if filteredUserId != 0 {
|
|
||||||
filteredIds = append(filteredIds, filteredUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get username from URL
|
|
||||||
if username := ctx.Query("username"); username != "" {
|
|
||||||
// username -> user id
|
|
||||||
rows, err := cache.Instance.PgCache.Query(context.Background(), `select users.user_id from users where LOWER("data"->>'Username') LIKE LOWER($1) and exists(SELECT FROM members where members.guild_id=$2);`, fmt.Sprintf("%%%s%%", username), guildId)
|
|
||||||
defer rows.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var filteredId uint64
|
|
||||||
if err := rows.Scan(&filteredId); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if filteredId != 0 {
|
|
||||||
filteredIds = append(filteredIds, filteredId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Query("userid") != "" || ctx.Query("username") != "" {
|
|
||||||
tickets = table.GetClosedTicketsByUserId(guildId, filteredIds)
|
|
||||||
} else {
|
|
||||||
tickets = table.GetClosedTickets(guildId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select 30 logs + format them
|
|
||||||
var formattedLogs []map[string]interface{}
|
|
||||||
for i := (page - 1) * pageLimit; i < (page - 1) * pageLimit + pageLimit; i++ {
|
|
||||||
if i >= len(tickets) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket := tickets[i]
|
|
||||||
|
|
||||||
// get username
|
|
||||||
user, found := cache.Instance.GetUser(ticket.Owner)
|
|
||||||
if !found {
|
|
||||||
user, err = rest.GetUser(config.Conf.Bot.Token, ratelimit.Ratelimiter, ticket.Owner)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
go cache.Instance.StoreUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedLogs = append(formattedLogs, map[string]interface{}{
|
|
||||||
"ticketid": ticket.TicketId,
|
|
||||||
"userid": ticket.Owner,
|
|
||||||
"username": user.Username,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.HTML(200, "manage/logs",gin.H{
|
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildId,
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"baseUrl": config.Conf.Server.BaseUrl,
|
"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 {
|
if store == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer store.Save()
|
|
||||||
|
|
||||||
if utils.IsLoggedIn(store) {
|
if utils.IsLoggedIn(store) {
|
||||||
userId := utils.GetUserId(store)
|
userId := utils.GetUserId(store)
|
||||||
@ -37,7 +36,7 @@ func LogViewHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
// format ticket ID
|
// format ticket ID
|
||||||
ticketId, err := strconv.Atoi(ctx.Param("ticket")); if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ func LogViewHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Verify this is a valid ticket and it is closed
|
// Verify this is a valid ticket and it is closed
|
||||||
if ticket.Uuid == "" || ticket.IsOpen {
|
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
|
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 (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"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/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type wrappedPanel struct {
|
|
||||||
MessageId uint64
|
|
||||||
ChannelName string
|
|
||||||
Title string
|
|
||||||
Content string
|
|
||||||
CategoryName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func PanelHandler(ctx *gin.Context) {
|
func PanelHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
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
|
|
||||||
|
|
||||||
ctx.HTML(200, "manage/panels", gin.H{
|
ctx.HTML(200, "manage/panels", gin.H{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildId,
|
||||||
"csrf": store.Get("csrf").(string),
|
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"baseUrl": config.Conf.Server.BaseUrl,
|
"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) {
|
func SettingsHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer store.Save()
|
|
||||||
|
|
||||||
if utils.IsLoggedIn(store) {
|
|
||||||
userId := utils.GetUserId(store)
|
userId := utils.GetUserId(store)
|
||||||
|
|
||||||
// Verify the guild exists
|
// Verify the guild exists
|
||||||
@ -45,14 +40,6 @@ func SettingsHandler(ctx *gin.Context) {
|
|||||||
return
|
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)
|
namingSchemeChan := make(chan table.NamingScheme)
|
||||||
go table.GetTicketNamingScheme(guildId, namingSchemeChan)
|
go table.GetTicketNamingScheme(guildId, namingSchemeChan)
|
||||||
namingScheme := <-namingSchemeChan
|
namingScheme := <-namingSchemeChan
|
||||||
@ -83,25 +70,16 @@ func SettingsHandler(ctx *gin.Context) {
|
|||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildIdStr,
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"prefix": prefix,
|
|
||||||
"welcomeMessage": welcomeMessage,
|
|
||||||
"ticketLimit": limit,
|
|
||||||
"categories": categories,
|
"categories": categories,
|
||||||
"activecategory": categoryId,
|
|
||||||
"channels": channels,
|
"channels": channels,
|
||||||
"archivechannel": archiveChannel,
|
|
||||||
"invalidPrefix": invalidPrefix,
|
"invalidPrefix": invalidPrefix,
|
||||||
"invalidWelcomeMessage": invalidWelcomeMessage,
|
"invalidWelcomeMessage": invalidWelcomeMessage,
|
||||||
"invalidTicketLimit": invalidTicketLimit,
|
"invalidTicketLimit": invalidTicketLimit,
|
||||||
"csrf": store.Get("csrf").(string),
|
"csrf": store.Get("csrf").(string),
|
||||||
"pingEveryone": pingEveryone,
|
|
||||||
"paneltitle": panelSettings.Title,
|
"paneltitle": panelSettings.Title,
|
||||||
"panelcontent": panelSettings.Content,
|
"panelcontent": panelSettings.Content,
|
||||||
"panelcolour": strconv.FormatInt(int64(panelSettings.Colour), 16),
|
"panelcolour": strconv.FormatInt(int64(panelSettings.Colour), 16),
|
||||||
"usersCanClose": usersCanClose,
|
"usersCanClose": usersCanClose,
|
||||||
"namingScheme": string(namingScheme),
|
"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 (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"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/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TicketListHandler(ctx *gin.Context) {
|
func TicketListHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.HTML(200, "manage/ticketlist", gin.H{
|
ctx.HTML(200, "manage/ticketlist", gin.H{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildId,
|
||||||
"csrf": store.Get("csrf").(string),
|
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"baseUrl": config.Conf.Server.BaseUrl,
|
"baseUrl": config.Conf.Server.BaseUrl,
|
||||||
"tickets": ticketsFormatted,
|
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
ctx.Redirect(302, "/login")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,117 +1,20 @@
|
|||||||
package manage
|
package manage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"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/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rxdn/gdl/rest"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var MentionRegex, _ = regexp.Compile("<@(\\d+)>")
|
|
||||||
|
|
||||||
func TicketViewHandler(ctx *gin.Context) {
|
func TicketViewHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
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)
|
|
||||||
|
|
||||||
ctx.HTML(200, "manage/ticketview", gin.H{
|
ctx.HTML(200, "manage/ticketview", gin.H{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildId,
|
||||||
"csrf": store.Get("csrf").(string),
|
|
||||||
"avatar": store.Get("avatar").(string),
|
"avatar": store.Get("avatar").(string),
|
||||||
"baseUrl": config.Conf.Server.BaseUrl,
|
"baseUrl": config.Conf.Server.BaseUrl,
|
||||||
"isError": false,
|
"uuid": ctx.Param("uuid"),
|
||||||
"error": "",
|
|
||||||
"messages": messagesFormatted,
|
|
||||||
"ticketId": ticket.TicketId,
|
|
||||||
"uuid": ticket.Uuid,
|
|
||||||
"include_mock": true,
|
|
||||||
"premium": <-premium,
|
|
||||||
})
|
})
|
||||||
} 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
|
package manage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
|
||||||
"github.com/TicketsBot/GoPanel/database/table"
|
|
||||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/rxdn/gdl/rest"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
@ -47,12 +39,7 @@ type (
|
|||||||
|
|
||||||
func WebChatWs(ctx *gin.Context) {
|
func WebChatWs(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer store.Save()
|
|
||||||
|
|
||||||
if utils.IsLoggedIn(store) {
|
|
||||||
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
@ -105,9 +92,10 @@ func WebChatWs(ctx *gin.Context) {
|
|||||||
data := evnt.Data.(map[string]interface{})
|
data := evnt.Data.(map[string]interface{})
|
||||||
|
|
||||||
guildId = data["guild"].(string)
|
guildId = data["guild"].(string)
|
||||||
ticket, err = strconv.Atoi(data["ticket"].(string));
|
ticket, err = strconv.Atoi(data["ticket"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.Guild = guildId
|
socket.Guild = guildId
|
||||||
@ -135,81 +123,11 @@ func WebChatWs(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Verify the guild is premium
|
// Verify the guild is premium
|
||||||
premium := make(chan bool)
|
premium := make(chan bool)
|
||||||
go utils.IsPremiumGuild(store, guildIdParsed, premium)
|
go utils.IsPremiumGuild(guildIdParsed, premium)
|
||||||
if !<-premium {
|
if !<-premium {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
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) {
|
func IndexHandler(ctx *gin.Context) {
|
||||||
store := sessions.Default(ctx)
|
store := sessions.Default(ctx)
|
||||||
if store == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer store.Save()
|
|
||||||
|
|
||||||
if utils.IsLoggedIn(store) {
|
|
||||||
userId := utils.GetUserId(store)
|
userId := utils.GetUserId(store)
|
||||||
|
|
||||||
userGuilds := table.GetGuilds(userId)
|
userGuilds := table.GetGuilds(userId)
|
||||||
@ -38,12 +32,6 @@ func IndexHandler(ctx *gin.Context) {
|
|||||||
ctx.HTML(200, "main/index", gin.H{
|
ctx.HTML(200, "main/index", gin.H{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"baseurl": config.Conf.Server.BaseUrl,
|
"baseurl": config.Conf.Server.BaseUrl,
|
||||||
"servers": adminGuilds,
|
|
||||||
"empty": len(adminGuilds) == 0,
|
|
||||||
"isIndex": true,
|
|
||||||
"avatar": store.Get("avatar").(string),
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/app/http/endpoints/api"
|
||||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
|
"github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
|
||||||
"github.com/TicketsBot/GoPanel/app/http/endpoints/root"
|
"github.com/TicketsBot/GoPanel/app/http/endpoints/root"
|
||||||
|
"github.com/TicketsBot/GoPanel/app/http/middleware"
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
"github.com/gin-contrib/multitemplate"
|
"github.com/gin-contrib/multitemplate"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
@ -31,33 +33,66 @@ func StartServer() {
|
|||||||
// Handle static asset requests
|
// Handle static asset requests
|
||||||
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
|
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
|
||||||
|
|
||||||
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
// Register templates
|
// Register templates
|
||||||
router.HTMLRender = createRenderer()
|
router.HTMLRender = createRenderer()
|
||||||
|
|
||||||
router.GET("/", root.IndexHandler)
|
|
||||||
|
|
||||||
router.GET("/login", root.LoginHandler)
|
router.GET("/login", root.LoginHandler)
|
||||||
router.GET("/callback", root.CallbackHandler)
|
router.GET("/callback", root.CallbackHandler)
|
||||||
router.GET("/logout", root.LogoutHandler)
|
|
||||||
|
|
||||||
router.GET("/manage/:id/settings", manage.SettingsHandler)
|
router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect
|
||||||
router.POST("/manage/:id/settings", manage.UpdateSettingsHandler)
|
|
||||||
|
|
||||||
router.GET("/manage/:id/logs/page/:page", manage.LogsHandler)
|
authorized := router.Group("/", middleware.AuthenticateCookie)
|
||||||
router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler)
|
{
|
||||||
|
authorized.POST("/token", api.TokenHandler)
|
||||||
|
|
||||||
router.GET("/manage/:id/blacklist", manage.BlacklistHandler)
|
authenticateGuild := authorized.Group("/", middleware.AuthenticateGuild(false))
|
||||||
router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler)
|
|
||||||
|
|
||||||
router.GET("/manage/:id/panels", manage.PanelHandler)
|
authorized.GET("/", root.IndexHandler)
|
||||||
router.POST("/manage/:id/panels/create", manage.PanelCreateHandler)
|
authorized.GET("/logout", root.LogoutHandler)
|
||||||
router.GET("/manage/:id/panels/delete/:msg", manage.PanelDeleteHandler)
|
|
||||||
|
|
||||||
router.GET("/manage/:id/tickets", manage.TicketListHandler)
|
authenticateGuild.GET("/manage/:id/settings", manage.SettingsHandler)
|
||||||
router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler)
|
authenticateGuild.GET("/manage/:id/logs", manage.LogsHandler)
|
||||||
router.POST("/manage/:id/tickets/view/:uuid/close", manage.TicketCloseHandler)
|
authenticateGuild.GET("/manage/:id/blacklist", manage.BlacklistHandler)
|
||||||
router.POST("/manage/:id/tickets/view/:uuid", manage.SendMessage)
|
authenticateGuild.GET("/manage/:id/panels", manage.PanelHandler)
|
||||||
router.GET("/webchat", manage.WebChatWs)
|
|
||||||
|
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 {
|
if err := router.Run(config.Conf.Server.Host); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -22,6 +22,7 @@ type (
|
|||||||
MainSite string
|
MainSite string
|
||||||
Ratelimit Ratelimit
|
Ratelimit Ratelimit
|
||||||
Session Session
|
Session Session
|
||||||
|
Secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
Ratelimit struct {
|
Ratelimit struct {
|
||||||
|
@ -18,9 +18,8 @@ func UpdateArchiveChannel(guildId uint64, channelId uint64) {
|
|||||||
database.Database.Where(ArchiveChannel{Guild: guildId}).Assign(ArchiveChannel{Channel: channelId}).FirstOrCreate(&channel)
|
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
|
var channel ArchiveChannel
|
||||||
database.Database.Where(&ArchiveChannel{Guild: guildId}).First(&channel)
|
database.Database.Where(&ArchiveChannel{Guild: guildId}).First(&channel)
|
||||||
|
ch <- channel.Channel
|
||||||
return channel.Channel
|
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,7 @@ func AddBlacklist(guildId, userId uint64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RemoveBlacklist(guildId, userId uint64) {
|
func RemoveBlacklist(guildId, userId uint64) {
|
||||||
var node BlacklistNode
|
database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Delete(BlacklistNode{})
|
||||||
database.Database.Where(BlacklistNode{Guild: guildId, User: userId}).Take(&node)
|
|
||||||
database.Database.Delete(&node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBlacklistNodes(guildId uint64) []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{})
|
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
|
var category ChannelCategory
|
||||||
database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category)
|
database.Database.Where(&ChannelCategory{GuildId: guildId}).First(&category)
|
||||||
|
ch <- category.Category
|
||||||
return category.Category
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ type Panel struct {
|
|||||||
GuildId uint64 `gorm:"column:GUILDID"` // Might be useful in the future so we store it
|
GuildId uint64 `gorm:"column:GUILDID"` // Might be useful in the future so we store it
|
||||||
Title string `gorm:"column:TITLE;type:VARCHAR(255)"`
|
Title string `gorm:"column:TITLE;type:VARCHAR(255)"`
|
||||||
Content string `gorm:"column:CONTENT;type:TEXT"`
|
Content string `gorm:"column:CONTENT;type:TEXT"`
|
||||||
Colour int `gorm:"column:COLOUR`
|
Colour uint32 `gorm:"column:COLOUR`
|
||||||
TargetCategory uint64 `gorm:"column:TARGETCATEGORY"`
|
TargetCategory uint64 `gorm:"column:TARGETCATEGORY"`
|
||||||
ReactionEmote string `gorm:"column:REACTIONEMOTE;type:VARCHAR(32)"`
|
ReactionEmote string `gorm:"column:REACTIONEMOTE;type:VARCHAR(32)"`
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ func (Panel) TableName() string {
|
|||||||
return "panels"
|
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{
|
database.Database.Create(&Panel{
|
||||||
MessageId: messageId,
|
MessageId: messageId,
|
||||||
ChannelId: channelId,
|
ChannelId: channelId,
|
||||||
@ -45,6 +45,12 @@ func GetPanelsByGuild(guildId uint64, ch chan []Panel) {
|
|||||||
ch <- panels
|
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) {
|
func DeletePanel(msgId uint64) {
|
||||||
database.Database.Where(Panel{MessageId: msgId}).Delete(Panel{})
|
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{})
|
//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}
|
pingEveryone := PingEveryone{PingEveryone: true}
|
||||||
database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone)
|
database.Database.Where(&PingEveryone{GuildId: guildId}).First(&pingEveryone)
|
||||||
|
ch <- pingEveryone.PingEveryone
|
||||||
return pingEveryone.PingEveryone
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@ func UpdatePrefix(guildId uint64, prefix string) {
|
|||||||
database.Database.Where(&Prefix{GuildId: guildId}).Assign(&Prefix{Prefix: prefix}).FirstOrCreate(&Prefix{})
|
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!"}
|
prefix := Prefix{Prefix: "t!"}
|
||||||
database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix)
|
database.Database.Where(&Prefix{GuildId: guildId}).First(&prefix)
|
||||||
|
ch <- prefix.Prefix
|
||||||
return prefix.Prefix
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@ func UpdateTicketLimit(guildId uint64, limit int) {
|
|||||||
database.Database.Where(&TicketLimit{GuildId: guildId}).Assign(&TicketLimit{Limit: limit}).FirstOrCreate(&TicketLimit{})
|
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}
|
limit := TicketLimit{Limit: 5}
|
||||||
database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit)
|
database.Database.Where(&TicketLimit{GuildId: guildId}).First(&limit)
|
||||||
|
ch <- limit.Limit
|
||||||
return limit.Limit
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@ func UpdateWelcomeMessage(guildId uint64, message string) {
|
|||||||
database.Database.Where(&WelcomeMessage{GuildId: guildId}).Assign(&WelcomeMessage{Message: message}).FirstOrCreate(&WelcomeMessage{})
|
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"}
|
message := WelcomeMessage{Message: "No message specified"}
|
||||||
database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message)
|
database.Database.Where(&WelcomeMessage{GuildId: guildId}).First(&message)
|
||||||
|
ch <- message.Message
|
||||||
return message.Message
|
|
||||||
}
|
}
|
||||||
|
3
go.mod
3
go.mod
@ -7,6 +7,7 @@ require (
|
|||||||
github.com/TicketsBot/archiverclient v0.0.0-20200420161043-3532ff9ea943
|
github.com/TicketsBot/archiverclient v0.0.0-20200420161043-3532ff9ea943
|
||||||
github.com/apex/log v1.1.2
|
github.com/apex/log v1.1.2
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
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/multitemplate v0.0.0-20200226145339-3e397ee01bc6
|
||||||
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
|
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
|
||||||
github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607
|
github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607
|
||||||
@ -21,5 +22,5 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62
|
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62
|
||||||
github.com/rxdn/gdl v0.0.0-20200417164852-76b2d3c847c1
|
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;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-name {
|
#channel-name {
|
||||||
color: white;
|
color: white;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
@ -56,12 +56,6 @@
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: #2e3136 !important;
|
|
||||||
background-color: #2e3136 !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-input:focus {
|
.message-input:focus {
|
||||||
border-color: #2e3136 !important;
|
border-color: #2e3136 !important;
|
||||||
background-color: #2e3136 !important;
|
background-color: #2e3136 !important;
|
||||||
|
@ -3,10 +3,19 @@ body {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
background-color: #121212 !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #272727 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background: url("/assets/img/sidebar-2.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
}
|
}
|
||||||
@ -37,3 +46,68 @@ body {
|
|||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 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">
|
<link href="/assets/css/style.css" rel="stylesheet">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.avatar {
|
#avatar-sidebar {
|
||||||
background: url("{{.avatar}}?size=32");
|
background: url("{{.avatar}}?size=256") center center;
|
||||||
width: 28px;
|
width: 32px;
|
||||||
height: 29px;
|
height: 32px;
|
||||||
display: block;
|
display: block;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
float: right;
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</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 -->
|
<!-- Bootstrap -->
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
<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">
|
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;
|
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||||
ga('create', 'UA-161945537-1', 'auto');
|
ga('create', 'UA-161945537-1', 'auto');
|
||||||
ga('send', 'pageview');
|
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>
|
||||||
<script async src='https://www.google-analytics.com/analytics.js' type="80be96f83bbfbba3d4097e23-text/javascript"></script>
|
|
||||||
{{end}}
|
{{end}}
|
@ -1,23 +1,32 @@
|
|||||||
{{define "navbar"}}
|
{{define "navbar"}}
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" id="bg-dark">
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
||||||
<li class="nav-item">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
{{end}}
|
{{end}}
|
@ -1,12 +1,6 @@
|
|||||||
{{define "sidebar"}}
|
{{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="sidebar-wrapper">
|
||||||
<div class="logo">
|
|
||||||
<a href="https://ticketsbot.net" class="simple-text">
|
|
||||||
TicketsBot
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li class="nav-item sidebar-bottom">
|
<li class="nav-item sidebar-bottom">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
@ -23,7 +17,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item sidebar-bottom" style="bottom: 10px">
|
<li class="nav-item sidebar-bottom" style="bottom: 10px">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<i class="avatar"></i>
|
<i id="avatar-sidebar"></i>
|
||||||
<p>{{.name}}</p>
|
<p>{{.name}}</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="">
|
||||||
<head>
|
<head>
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{{template "sidebar" .}}
|
<div class="main-panel" style="width: 100% !important;">
|
||||||
|
|
||||||
<div class="main-panel">
|
|
||||||
{{template "navbar" .}}
|
{{template "navbar" .}}
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,27 +12,27 @@
|
|||||||
<div id="accordion">
|
<div id="accordion">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header collapsed filterCard" id="addBlacklistHeader" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
|
<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
|
<i class="fas fa-plus"></i> Add New User
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
|
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form>
|
<form onsubmit="blacklistUser(); return false;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 pr-1">
|
<div class="col-md-3 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-1 px-1">
|
<div class="col-md-2 px-1">
|
||||||
<label>Discriminator</label>
|
<label>Discriminator</label>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">#</div>
|
<div class="input-group-text">#</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,14 +59,7 @@
|
|||||||
<th>Remove</th>
|
<th>Remove</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="blacklisted-container">
|
||||||
{{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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -76,40 +69,73 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
<div style="position: absolute; right: 10px" id="toast-container">
|
||||||
{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -6,41 +6,56 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h4 class="card-title">Servers</h4>
|
<h4 class="card-title">Servers</h4>
|
||||||
{{if .empty}}
|
|
||||||
<p class="card-category">Select a server to manage below</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body" id="card">
|
||||||
{{if .empty}}
|
<div class="card-body table-responsive">
|
||||||
<p class="center-align" style="padding-top: 50px; font-size: 16px">
|
<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:
|
You are not the admin of any guilds that the bot is in. Click below to invite the bot:
|
||||||
<br/>
|
<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>
|
</p>
|
||||||
{{else}}
|
<table class="table table-hover table-striped" id="guild-table" style="display: none">
|
||||||
<div class="card-body table-responsive">
|
|
||||||
<table class="table table-hover table-striped">
|
|
||||||
<thead>
|
<thead>
|
||||||
<th>Server Name</th>
|
<th>Server Name</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="guild-container">
|
||||||
{{range .servers}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="/manage/{{.Id}}/settings">
|
|
||||||
{{.Name}}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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}}
|
{{end}}
|
@ -6,37 +6,37 @@
|
|||||||
<div id="accordion">
|
<div id="accordion">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header collapsed filterCard" id="filterHeader" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
<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
|
<i class="fas fa-search"></i> Filter Logs
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
|
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="/manage/{{.guildId}}/logs/page/1">
|
<form onsubmit="filterLogs(); return false;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 pr-1">
|
<div class="col-md-4 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Ticket ID</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-4 px-1">
|
<div class="col-md-4 px-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-4 px-1">
|
<div class="col-md-4 px-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>User ID</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<th>Log URL</th>
|
<th>Log URL</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="log-container">
|
||||||
{{range .logs}}
|
{{range .logs}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{.ticketid}}</td>
|
<td>{{.ticketid}}</td>
|
||||||
@ -79,14 +79,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination justify-content-center">
|
||||||
{{if .isPageOne}}
|
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||||
<li class="disabled"><a href="#"><i class="fas fa-chevron-left"></i></a></li>
|
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
|
||||||
{{else}}
|
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||||
<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>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,5 +91,89 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -8,11 +8,8 @@
|
|||||||
<h4 class="card-title">Reaction Panels</h4>
|
<h4 class="card-title">Reaction Panels</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>Your panel quota: <b>{{.panelcount}} / {{if .premium}}∞{{else}}1{{end}}</b></p>
|
<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>
|
||||||
{{if not .premium}}
|
|
||||||
<p>Note: You can expand your panel quote by purchasing premium</p>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<table class="table table-hover table-striped">
|
<table class="table table-hover table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
@ -24,16 +21,7 @@
|
|||||||
<th>Delete</th>
|
<th>Delete</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="panel-container">
|
||||||
{{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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -43,19 +31,19 @@
|
|||||||
<h4 class="card-title">Create A Panel</h4>
|
<h4 class="card-title">Create A Panel</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post" action="/manage/{{.guildId}}/panels/create">
|
<form onsubmit="createPanel(); return false;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 pr-1">
|
<div class="col-md-4 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black">Panel Title</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-8 pr-1">
|
<div class="col-md-8 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black">Panel Content</label>
|
<label class="black">Panel Content</label>
|
||||||
<textarea name="content" type="text" class="form-control"
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +51,7 @@
|
|||||||
<div class="col-md-3 pr-1">
|
<div class="col-md-3 pr-1">
|
||||||
<label class="black">Panel Colour</label>
|
<label class="black">Panel Colour</label>
|
||||||
<div class="input-group mb-3">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -73,12 +61,7 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">#</div>
|
<div class="input-group-text">#</div>
|
||||||
</div>
|
</div>
|
||||||
<select class="form-control" name="channel">
|
<select class="form-control" name="channel" id="channel-container">
|
||||||
{{range .channels}}
|
|
||||||
{{if eq .Type 0}} <!-- Check the channel is a text channel -->
|
|
||||||
<option value="{{.Id}}">{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -89,12 +72,7 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">#</div>
|
<div class="input-group-text">#</div>
|
||||||
</div>
|
</div>
|
||||||
<select class="form-control" name="categories">
|
<select class="form-control" name="categories" id="category-container">
|
||||||
{{range .channels}}
|
|
||||||
{{if eq .Type 4}} <!-- Check the channel is a category -->
|
|
||||||
<option value="{{.Id}}">{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,16 +80,15 @@
|
|||||||
<div class="col-md-3 pr-1">
|
<div class="col-md-3 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="black">Reaction Emote</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input name="csrf" type="hidden" value="{{.csrf}}">
|
|
||||||
<div class="col-md-2 pr-1 offset-md-5">
|
<div class="col-md-2 pr-1 offset-md-5">
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -123,119 +100,148 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
<div style="position: absolute; right: 10px" id="toast-container">
|
||||||
{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -11,25 +11,25 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post">
|
<form onsubmit="updateSettings(); return false;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5 pr-1">
|
<div class="col-md-5 pr-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Prefix (Max len. 8)</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-5 px-1">
|
<div class="col-md-5 px-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Ticket Limit (1-10)</label>
|
<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>
|
</div>
|
||||||
<div class="col-md-2 px-1">
|
<div class="col-md-2 px-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Ping @everyone on ticket open</label>
|
<label>Ping @everyone on ticket open</label>
|
||||||
<div class="form-check">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label>Welcome Message (Max len. 1000)</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -49,10 +50,7 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">#</div>
|
<div class="input-group-text">#</div>
|
||||||
</div>
|
</div>
|
||||||
<select class="form-control" name="archivechannel">
|
<select class="form-control" name="archive_channel" id="archive_channel">
|
||||||
{{range .channels}}
|
|
||||||
<option {{if eq .Id $.archivechannel }}selected{{end}} value="{{.Id}}">{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,10 +58,7 @@
|
|||||||
<div class="col-md-5 px-1">
|
<div class="col-md-5 px-1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Channel Category</label>
|
<label>Channel Category</label>
|
||||||
<select class="form-control" name="category">
|
<select class="form-control" name="category" id="category">
|
||||||
{{range .categories}}
|
|
||||||
<option {{if eq $.activecategory .Id}}selected{{end}} value="{{.Id}}">{{.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +67,8 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Allow users to close tickets</label>
|
<label>Allow users to close tickets</label>
|
||||||
<div class="form-check">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -80,23 +76,25 @@
|
|||||||
|
|
||||||
<label>Ticket Naming Scheme</label>
|
<label>Ticket Naming Scheme</label>
|
||||||
<div class="form-check">
|
<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}}>
|
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-id" value="id">
|
||||||
<label class="form-check-label" for="naming-by-id">
|
<label class="form-check-label white" for="naming-by-id">
|
||||||
Ticket ID
|
Ticket ID (#ticket-1)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<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}}>
|
<input class="form-check-input" type="radio" name="namingscheme" id="naming-by-username"
|
||||||
<label class="form-check-label" for="naming-by-username">
|
value="username">
|
||||||
Username
|
<label class="form-check-label white" for="naming-by-username">
|
||||||
|
Username (#ryan-1)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input name="csrf" type="hidden" value="{{.csrf}}">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-1 pr-1">
|
<div class="col-md-2 pr-1">
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -107,53 +105,137 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
<div aria-live="polite" aria-atomic="true" style="position: relative;">
|
||||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
<div style="position: absolute; right: 10px" id="toast-container">
|
||||||
{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$('.toast').toast('show');
|
async function getSettings() {
|
||||||
$('#pingeveryone').prop('indeterminate', {{.pingEveryone}});
|
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>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -18,15 +18,7 @@
|
|||||||
<th>View</th>
|
<th>View</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="ticket-container">
|
||||||
{{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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -37,7 +29,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -7,11 +7,10 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">Close Ticket</h4>
|
<h4 class="card-title">Close Ticket</h4>
|
||||||
<div class="close-container">
|
<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 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">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -19,25 +18,13 @@
|
|||||||
<h4 class="card-title">View Ticket</h4>
|
<h4 class="card-title">View Ticket</h4>
|
||||||
<div class="discord-container">
|
<div class="discord-container">
|
||||||
<div class="channel-header">
|
<div class="channel-header">
|
||||||
<span class="channel-name">#ticket-{{.ticketId}}</span>
|
<span id="channel-name"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="message-container">
|
<div id="message-container">
|
||||||
{{range .messages}}
|
|
||||||
<div class="message">
|
|
||||||
<b>{{.username}}</b>
|
|
||||||
{{.content}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<form action="javascript:sendMessage()">
|
<form onsubmit="sendMessage(); return false;">
|
||||||
{{if .premium}}
|
<input type="text" class="form-control message-input" id="message" name="message">
|
||||||
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,76 +34,115 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
<div style="position: absolute; right: 10px; min-width: 300px" id="toast-container">
|
||||||
{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Scroll to bottom
|
// Scroll to bottom
|
||||||
let container = document.getElementById("message-container");
|
const container = document.getElementById("message-container");
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
</script>
|
|
||||||
|
|
||||||
{{if .premium}}
|
async function startWebsocket(ticketId) {
|
||||||
<script>
|
//const ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
|
||||||
let ws = new WebSocket("wss://panel.ticketsbot.net/webchat");
|
const ws = new WebSocket("ws://localhost:3000/webchat");
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
"type": "auth",
|
"type": "auth",
|
||||||
"data": {
|
"data": {
|
||||||
"guild": "{{.guildId}}",
|
"guild": "{{.guildId}}",
|
||||||
"ticket": "{{.ticketId}}"
|
"ticket": `${ticketId}`
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (evt) => {
|
ws.onmessage = (evt) => {
|
||||||
let data = JSON.parse(evt.data);
|
const data = JSON.parse(evt.data);
|
||||||
|
appendMessages([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
|
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function sendMessage() {
|
async function sendMessage() {
|
||||||
let msg = document.getElementById("message").value;
|
const msg = document.getElementById("message").value;
|
||||||
document.getElementById("message").value = "";
|
document.getElementById("message").value = "";
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
const res = await axios.post('/api/{{.guildId}}/tickets/{{.uuid}}', {message: msg});
|
||||||
"type": "send",
|
if (res.status !== 200 || !res.data.success) {
|
||||||
"data": msg
|
showToast('Error', res.data.error);
|
||||||
}))
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/TicketsBot/GoPanel/database/table"
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
|
||||||
gocache "github.com/robfig/go-cache"
|
gocache "github.com/robfig/go-cache"
|
||||||
"github.com/rxdn/gdl/rest"
|
"github.com/rxdn/gdl/rest"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -23,7 +22,7 @@ type ProxyResponse struct {
|
|||||||
|
|
||||||
var premiumCache = gocache.New(10 * time.Minute, 10 * time.Minute)
|
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)
|
guildIdRaw := strconv.FormatUint(guildId, 10)
|
||||||
|
|
||||||
if premium, ok := premiumCache.Get(guildIdRaw); ok {
|
if premium, ok := premiumCache.Get(guildIdRaw); ok {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user