Start work on panel
This commit is contained in:
parent
8f41184f74
commit
9b56c488ac
@ -56,38 +56,37 @@ func BlacklistHandler(ctx *gin.Context) {
|
||||
blacklistedIds = append(blacklistedIds, user.User)
|
||||
}
|
||||
|
||||
usernames := table.GetUsernames(blacklistedIds)
|
||||
nodes := table.GetUserNodes(blacklistedIds)
|
||||
|
||||
var blacklisted []map[string]interface{}
|
||||
for _, node := range blacklistedUsers {
|
||||
for _, node := range nodes {
|
||||
blacklisted = append(blacklisted, map[string]interface{}{
|
||||
"userId": node.User,
|
||||
"username": usernames[node.User],
|
||||
"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
|
||||
targetIdStr := ctx.Query("userid")
|
||||
targetId, err := strconv.ParseInt(targetIdStr, 10, 64)
|
||||
username := ctx.Query("username")
|
||||
discrim := ctx.Query("discrim")
|
||||
|
||||
if err != nil {
|
||||
userNotFound = true
|
||||
} else {
|
||||
// Verify that the user ID is real and in a shared guild
|
||||
username := table.GetUsername(targetId)
|
||||
exists := username != ""
|
||||
targetId := table.GetUserId(username, discrim)
|
||||
exists := targetId != 0
|
||||
|
||||
if exists {
|
||||
if guild.OwnerId == targetIdStr || table.IsSupport(guildId, targetId) { // Prevent users from blacklisting staff
|
||||
if guild.OwnerId == strconv.Itoa(int(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": targetIdStr,
|
||||
"userId": targetId,
|
||||
"username": username,
|
||||
"discrim": discrim,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -95,7 +94,6 @@ func BlacklistHandler(ctx *gin.Context) {
|
||||
userNotFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Respond(ctx, template.TemplateBlacklist.Render(map[string]interface{}{
|
||||
"name": store.Get("name").(string),
|
||||
|
@ -210,6 +210,39 @@ func SettingsHandler(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
panelSettings := table.GetPanelSettings(guildId)
|
||||
panelUpdated := false
|
||||
|
||||
// Get panel title
|
||||
panelTitle := ctx.Query("paneltitle")
|
||||
if panelTitle == "" || len(panelTitle) > 255 || !csrfCorrect {
|
||||
panelTitle = panelSettings.Title
|
||||
} else {
|
||||
panelUpdated = true
|
||||
}
|
||||
|
||||
// Get panel content
|
||||
panelContent := ctx.Query("panelcontent")
|
||||
if panelContent == "" || len(panelContent) > 255 || !csrfCorrect {
|
||||
panelContent = panelSettings.Content
|
||||
} else {
|
||||
panelUpdated = true
|
||||
}
|
||||
|
||||
// Get panel colour
|
||||
var panelColour uint64
|
||||
panelColourHex := ctx.Query("panelcolour")
|
||||
if panelColourHex == "" || len(panelColourHex) > 255 || !csrfCorrect {
|
||||
panelColour = uint64(panelSettings.Colour)
|
||||
} else {
|
||||
panelUpdated = true
|
||||
panelColour, err = strconv.ParseUint(panelColourHex, 16, 32)
|
||||
}
|
||||
|
||||
if panelUpdated {
|
||||
go table.UpdatePanelSettings(guildId, panelTitle, panelContent, int(panelColour))
|
||||
}
|
||||
|
||||
utils.Respond(ctx, template.TemplateSettings.Render(map[string]interface{}{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
@ -224,6 +257,9 @@ func SettingsHandler(ctx *gin.Context) {
|
||||
"invalidTicketLimit": invalidTicketLimit,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"pingEveryone": pingEveryone,
|
||||
"paneltitle": panelTitle,
|
||||
"panelcontent": panelContent,
|
||||
"panelcolour": strconv.FormatInt(int64(panelColour), 16),
|
||||
}))
|
||||
} else {
|
||||
ctx.Redirect(302, "/login")
|
||||
|
110
app/http/endpoints/manage/ticketlist.go
Normal file
110
app/http/endpoints/manage/ticketlist.go
Normal file
@ -0,0 +1,110 @@
|
||||
package manage
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/app/http/template"
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/GoPanel/utils/discord/objects"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TicketListHandler(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer store.Save()
|
||||
|
||||
if utils.IsLoggedIn(store) {
|
||||
userIdStr := store.Get("userid").(string)
|
||||
userId, err := utils.GetUserId(store)
|
||||
if err != nil {
|
||||
ctx.String(500, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the guild exists
|
||||
guildIdStr := ctx.Param("id")
|
||||
guildId, err := strconv.ParseInt(guildIdStr, 10, 64)
|
||||
if err != nil {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
|
||||
return
|
||||
}
|
||||
|
||||
// Get object for selected guild
|
||||
var guild objects.Guild
|
||||
for _, g := range table.GetGuilds(userIdStr) {
|
||||
if g.Id == guildIdStr {
|
||||
guild = g
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the user has permissions to be here
|
||||
if !guild.Owner && !table.IsAdmin(guildId, userId) {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
|
||||
return
|
||||
}
|
||||
|
||||
tickets := table.GetOpenTickets(guildId)
|
||||
|
||||
var toFetch []int64
|
||||
for _, ticket := range tickets {
|
||||
toFetch = append(toFetch, ticket.Owner)
|
||||
|
||||
for _, idStr := range strings.Split(ticket.Members, ",") {
|
||||
if memberId, err := strconv.ParseInt(idStr, 10, 64); err == nil {
|
||||
toFetch = append(toFetch, memberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes := make(map[int64]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.ParseInt(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": utils.Base64Decode(nodes[memberId].Name),
|
||||
"discrim": nodes[memberId].Discriminator,
|
||||
"sep": separator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ticketsFormatted = append(ticketsFormatted, map[string]interface{}{
|
||||
"uuid": ticket.Uuid,
|
||||
"ticketId": ticket.TicketId,
|
||||
"username": utils.Base64Decode(nodes[ticket.Owner].Name),
|
||||
"discrim": nodes[ticket.Owner].Discriminator,
|
||||
"members": membersFormatted,
|
||||
})
|
||||
}
|
||||
|
||||
utils.Respond(ctx, template.TemplateTicketList.Render(map[string]interface{}{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"avatar": store.Get("avatar").(string),
|
||||
"baseUrl": config.Conf.Server.BaseUrl,
|
||||
"tickets": ticketsFormatted,
|
||||
}))
|
||||
}
|
||||
}
|
98
app/http/endpoints/manage/ticketview.go
Normal file
98
app/http/endpoints/manage/ticketview.go
Normal file
@ -0,0 +1,98 @@
|
||||
package manage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/app/http/template"
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"github.com/TicketsBot/GoPanel/database/table"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/GoPanel/utils/discord/endpoints/channel"
|
||||
"github.com/TicketsBot/GoPanel/utils/discord/objects"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func TicketViewHandler(ctx *gin.Context) {
|
||||
store := sessions.Default(ctx)
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
defer store.Save()
|
||||
|
||||
if utils.IsLoggedIn(store) {
|
||||
userIdStr := store.Get("userid").(string)
|
||||
userId, err := utils.GetUserId(store)
|
||||
if err != nil {
|
||||
ctx.String(500, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the guild exists
|
||||
guildIdStr := ctx.Param("id")
|
||||
guildId, err := strconv.ParseInt(guildIdStr, 10, 64)
|
||||
if err != nil {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
|
||||
return
|
||||
}
|
||||
|
||||
// Get object for selected guild
|
||||
var guild objects.Guild
|
||||
for _, g := range table.GetGuilds(userIdStr) {
|
||||
if g.Id == guildIdStr {
|
||||
guild = g
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the user has permissions to be here
|
||||
if !guild.Owner && !table.IsAdmin(guildId, userId) {
|
||||
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
|
||||
return
|
||||
}
|
||||
|
||||
// Get ticket UUID from URL and verify it exists
|
||||
uuid := ctx.Param("uuid")
|
||||
ticket := table.GetTicket(uuid)
|
||||
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
|
||||
}
|
||||
|
||||
// Get messages
|
||||
var messages []objects.Message
|
||||
// We want to show users error messages so they can report them
|
||||
isError := false
|
||||
var errorMessage string
|
||||
|
||||
endpoint := channel.GetChannelMessages(int(ticket.Channel))
|
||||
if err = endpoint.Request(store, nil, nil, &messages); err != nil {
|
||||
isError = true
|
||||
errorMessage = err.Error()
|
||||
}
|
||||
|
||||
// Format messages, exclude unneeded data
|
||||
var messagesFormatted []map[string]interface{}
|
||||
for _, message := range utils.Reverse(messages) {
|
||||
messagesFormatted = append(messagesFormatted, map[string]interface{}{
|
||||
"username": message.Author.Username,
|
||||
"content": message.Content,
|
||||
})
|
||||
}
|
||||
|
||||
utils.Respond(ctx, template.TemplateTicketView.Render(map[string]interface{}{
|
||||
"name": store.Get("name").(string),
|
||||
"guildId": guildIdStr,
|
||||
"csrf": store.Get("csrf").(string),
|
||||
"avatar": store.Get("avatar").(string),
|
||||
"baseUrl": config.Conf.Server.BaseUrl,
|
||||
"isError": isError,
|
||||
"error": errorMessage,
|
||||
"messages": messagesFormatted,
|
||||
"ticketId": ticket.TicketId,
|
||||
}))
|
||||
}
|
||||
}
|
@ -62,6 +62,12 @@ func StartServer() {
|
||||
// /manage/:id/blacklist/remove/:user
|
||||
router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler)
|
||||
|
||||
// /manage/:id/tickets
|
||||
router.GET("/manage/:id/tickets", manage.TicketListHandler)
|
||||
|
||||
// /manage/:id/tickets/view/:uuid
|
||||
//router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler)
|
||||
|
||||
if err := router.Run(config.Conf.Server.Host); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ var (
|
||||
TemplateLogs Template
|
||||
TemplateSettings Template
|
||||
TemplateBlacklist Template
|
||||
TemplateTicketList Template
|
||||
TemplateTicketView Template
|
||||
)
|
||||
|
||||
func (t *Template) Render(context ...interface{}) string {
|
||||
@ -54,6 +56,14 @@ func LoadTemplates() {
|
||||
compiled: loadTemplate("blacklist"),
|
||||
Layout: LayoutManage,
|
||||
}
|
||||
TemplateTicketList = Template{
|
||||
compiled: loadTemplate("ticketlist"),
|
||||
Layout: LayoutManage,
|
||||
}
|
||||
TemplateTicketView = Template{
|
||||
compiled: loadTemplate("ticketview"),
|
||||
Layout: LayoutManage,
|
||||
}
|
||||
}
|
||||
|
||||
func loadLayout(name string) *mustache.Template {
|
||||
|
37
database/table/panelsettings.go
Normal file
37
database/table/panelsettings.go
Normal file
@ -0,0 +1,37 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
)
|
||||
|
||||
type PanelSettings struct {
|
||||
GuildId int64 `gorm:"column:GUILDID"`
|
||||
Title string `gorm:"column:TITLE;type:VARCHAR(255)"`
|
||||
Content string `gorm:"column:CONTENT;type:TEXT"`
|
||||
Colour int `gorm:"column:COLOUR`
|
||||
}
|
||||
|
||||
func (PanelSettings) TableName() string {
|
||||
return "panelsettings"
|
||||
}
|
||||
|
||||
func UpdatePanelSettings(guildId int64, title string, content string, colour int) {
|
||||
settings := PanelSettings{
|
||||
Title: title,
|
||||
Content: content,
|
||||
Colour: colour,
|
||||
}
|
||||
|
||||
database.Database.Where(&PanelSettings{GuildId: guildId}).Assign(&settings).FirstOrCreate(&PanelSettings{})
|
||||
}
|
||||
|
||||
func GetPanelSettings(guildId int64) PanelSettings {
|
||||
settings := PanelSettings{
|
||||
Title: "Open A Ticket",
|
||||
Content: "React with :envelope_with_arrow: to open a ticket",
|
||||
Colour: 2335514,
|
||||
}
|
||||
database.Database.Where(PanelSettings{GuildId: guildId}).First(&settings)
|
||||
|
||||
return settings
|
||||
}
|
36
database/table/tickets.go
Normal file
36
database/table/tickets.go
Normal file
@ -0,0 +1,36 @@
|
||||
package table
|
||||
|
||||
import "github.com/TicketsBot/GoPanel/database"
|
||||
|
||||
type Ticket struct {
|
||||
Uuid string `gorm:"column:UUID;type:varchar(36);primary_key"`
|
||||
TicketId int `gorm:"column:ID"`
|
||||
Guild int64 `gorm:"column:GUILDID"`
|
||||
Channel int64 `gorm:"column:CHANNELID"`
|
||||
Owner int64 `gorm:"column:OWNERID"`
|
||||
Members string `gorm:"column:MEMBERS;type:text"`
|
||||
IsOpen bool `gorm:"column:OPEN"`
|
||||
OpenTime int64 `gorm:"column:OPENTIME"`
|
||||
}
|
||||
|
||||
func (Ticket) TableName() string {
|
||||
return "tickets"
|
||||
}
|
||||
|
||||
func GetTickets(guild int64) []Ticket {
|
||||
var tickets []Ticket
|
||||
database.Database.Where(&Ticket{Guild: guild}).Order("ID asc").Find(&tickets)
|
||||
return tickets
|
||||
}
|
||||
|
||||
func GetOpenTickets(guild int64) []Ticket {
|
||||
var tickets []Ticket
|
||||
database.Database.Where(&Ticket{Guild: guild, IsOpen: true}).Order("ID asc").Find(&tickets)
|
||||
return tickets
|
||||
}
|
||||
|
||||
func GetTicket(uuid string) Ticket {
|
||||
var ticket Ticket
|
||||
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
|
||||
return ticket
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
)
|
||||
|
||||
type UsernameNode struct {
|
||||
Id int64 `gorm:"column:USERID;primary_key"`
|
||||
Name string `gorm:"column:USERNAME;type:text"` // Base 64 encoded
|
||||
Discriminator string `gorm:"column:DISCRIM;type:varchar(4)"`
|
||||
Avatar string `gorm:"column:AVATARHASH;type:varchar(100)"`
|
||||
}
|
||||
|
||||
func (UsernameNode) TableName() string {
|
||||
@ -17,24 +19,17 @@ func (UsernameNode) TableName() string {
|
||||
func GetUsername(id int64) string {
|
||||
node := UsernameNode{Name: "Unknown"}
|
||||
database.Database.Where(&UsernameNode{Id: id}).First(&node)
|
||||
return base64Decode(node.Name)
|
||||
return utils.Base64Decode(node.Name)
|
||||
}
|
||||
|
||||
func GetUsernames(ids []int64) map[int64]string {
|
||||
func GetUserNodes(ids []int64) []UsernameNode {
|
||||
var nodes []UsernameNode
|
||||
database.Database.Where(ids).Find(&nodes)
|
||||
|
||||
m := make(map[int64]string)
|
||||
for _, node := range nodes {
|
||||
m[node.Id] = base64Decode(node.Name)
|
||||
}
|
||||
|
||||
return m
|
||||
return nodes
|
||||
}
|
||||
|
||||
func base64Decode(s string) string {
|
||||
b, err := base64.StdEncoding.DecodeString(s); if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
func GetUserId(name, discrim string) int64 {
|
||||
var node UsernameNode
|
||||
database.Database.Where(&UsernameNode{Name: utils.Base64Encode(name), Discriminator: discrim}).First(&node)
|
||||
return node.Id
|
||||
}
|
||||
|
31
public/static/css/discordmock.css
Normal file
31
public/static/css/discordmock.css
Normal file
@ -0,0 +1,31 @@
|
||||
@font-face{font-family:Whitney;font-style:light;font-weight:300;src:url('https://discordapp.com/assets/6c6374bad0b0b6d204d8d6dc4a18d820.woff') format('woff')}
|
||||
@font-face{font-family:Whitney;font-style:normal;font-weight:500;src:url('https://discordapp.com/assets/e8acd7d9bf6207f99350ca9f9e23b168.woff') format('woff')}
|
||||
@font-face{font-family:Whitney;font-style:medium;font-weight:600;src:url('https://discordapp.com/assets/3bdef1251a424500c1b3a78dea9b7e57.woff') format('woff')}
|
||||
@font-face{font-family:WhitneyMedium;font-style:medium;font-weight:600;src:url('https://discordapp.com/assets/be0060dafb7a0e31d2a1ca17c0708636.woff') format('woff')}
|
||||
@font-face{font-family:Whitney;font-style:bold;font-weight:700;src:url('https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff') format('woff')}
|
||||
|
||||
.discord-container {
|
||||
background-color: #2e3136;
|
||||
border-radius: 25px;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Whitney', sans-serif !important;
|
||||
}
|
||||
|
||||
.channel-header {
|
||||
background-color: #1e2124;
|
||||
height: 5vh;
|
||||
width: 100%;
|
||||
border-radius: 25px 25px 0 0;
|
||||
position: relative;
|
||||
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
color: white;
|
||||
padding-left: 20px;
|
||||
}
|
@ -62,6 +62,10 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Discord theme -->
|
||||
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
|
||||
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||
</head>
|
||||
@ -112,6 +116,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{guildId}}/blacklist">Blacklist</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage/{{guildId}}/tickets">Ticket List</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -19,16 +19,19 @@
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-md-8 pr-1">
|
||||
<div class="col-md-3 pr-1">
|
||||
<div class="form-group">
|
||||
<label>User ID</label>
|
||||
<input name="userid" type="text" class="form-control" placeholder="User ID">
|
||||
<label>Username</label>
|
||||
<input name="username" type="text" class="form-control" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 px-1">
|
||||
<label></label>
|
||||
<div class="alert alert-success" role="alert">
|
||||
Click <a href="https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-">here</a> for a guide on how to retreive user IDs.
|
||||
<div class="col-md-1 px-1">
|
||||
<label>Discriminator</label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<input name="discrim" type="text" class="form-control" placeholder="0000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,7 +54,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Username</th>
|
||||
<th>Username#Discrim</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -59,7 +62,7 @@
|
||||
{{#blacklisted}}
|
||||
<tr>
|
||||
<td>{{userId}}</td>
|
||||
<td>{{username}}</td>
|
||||
<td>{{username}}#{{discrim}}</td>
|
||||
<td><a href="{{baseUrl}}/manage/{{guildId}}/blacklist/remove/{{userId}}?c={{csrf}}">Remove</a></td>
|
||||
</tr>
|
||||
{{/blacklisted}}
|
||||
|
@ -68,6 +68,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 pr-1">
|
||||
<label>Panel Title</label>
|
||||
<input name="paneltitle" type="text" class="form-control" placeholder="Open A Ticket" value="{{paneltitle}}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 pr-1">
|
||||
<label>Panel Content</label>
|
||||
<input name="panelcontent" type="text" class="form-control" placeholder="React with :envelope_with_arrow: to open a ticket" value="{{panelcontent}}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 pr-1">
|
||||
<label>Panel Colour (Hex)</label>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">#</div>
|
||||
</div>
|
||||
<input name="panelcolour" type="text" class="form-control" placeholder="23A31A" value="{{panelcolour}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input name="csrf" type="hidden" value="{{csrf}}">
|
||||
<div class="row">
|
||||
<div class="col-md-1 pr-1">
|
||||
|
42
public/templates/views/ticketlist.mustache
Normal file
42
public/templates/views/ticketlist.mustache
Normal file
@ -0,0 +1,42 @@
|
||||
<div class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Ticket List</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body table-responsive">
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket ID</th>
|
||||
<th>User</th>
|
||||
<th>Additional Members</th>
|
||||
<th>View</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#tickets}}
|
||||
<tr>
|
||||
<td>{{ticketId}}</td>
|
||||
<td>{{username}}#{{discrim}}</td>
|
||||
<td>{{#members}}{{username}}#{{discrim}}{{sep}}{{/members}}</td>
|
||||
<!--<td><a class="btn btn-primary btn-sm" role="button" href="/manage/{{guildId}}/tickets/view/{{uuid}}">View</a></td>-->
|
||||
<td>Coming soon</td>
|
||||
</tr>
|
||||
{{/tickets}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
</script>
|
||||
</div>
|
50
public/templates/views/ticketview.mustache
Normal file
50
public/templates/views/ticketview.mustache
Normal file
@ -0,0 +1,50 @@
|
||||
<div class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Ticket List</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="discord-container">
|
||||
<div class="channel-header">
|
||||
<span class="channel-name">#ticket-{{ticketId}}</span>
|
||||
</div>
|
||||
<div class="message-container">
|
||||
<div class="message">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
|
||||
<div style="position: absolute; right: 10px; min-width: 300px">
|
||||
{{#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>
|
||||
{{/isError}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.toast').toast('show');
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
14
utils/discord/endpoints/channel/GetChannelMessages.go
Normal file
14
utils/discord/endpoints/channel/GetChannelMessages.go
Normal file
@ -0,0 +1,14 @@
|
||||
package channel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/utils/discord"
|
||||
)
|
||||
|
||||
func GetChannelMessages(id int) discord.Endpoint {
|
||||
return discord.Endpoint{
|
||||
RequestType: discord.GET,
|
||||
AuthorizationType: discord.BOT,
|
||||
Endpoint: fmt.Sprintf("/channels/%d/messages", id),
|
||||
}
|
||||
}
|
11
utils/discord/objects/attachment.go
Normal file
11
utils/discord/objects/attachment.go
Normal file
@ -0,0 +1,11 @@
|
||||
package objects
|
||||
|
||||
type Attachment struct {
|
||||
Id string
|
||||
Filename string
|
||||
Size int
|
||||
url string
|
||||
ProxyUrl string
|
||||
height int
|
||||
Width int
|
||||
}
|
17
utils/discord/objects/embed.go
Normal file
17
utils/discord/objects/embed.go
Normal file
@ -0,0 +1,17 @@
|
||||
package objects
|
||||
|
||||
type Embed struct {
|
||||
Title string
|
||||
Type string
|
||||
Description string
|
||||
Url string
|
||||
Timestamp string
|
||||
Color int
|
||||
Footer EmbedField
|
||||
Image EmbedImage
|
||||
Thumbnail EmbedThumbnail
|
||||
Video EmbedVideo
|
||||
Provider EmbedProvider
|
||||
Author EmbedAuthor
|
||||
Fields []EmbedField
|
||||
}
|
8
utils/discord/objects/embedauthor.go
Normal file
8
utils/discord/objects/embedauthor.go
Normal file
@ -0,0 +1,8 @@
|
||||
package objects
|
||||
|
||||
type EmbedAuthor struct {
|
||||
Name string
|
||||
Url string
|
||||
IconUrl string
|
||||
ProxyIconUrl string
|
||||
}
|
7
utils/discord/objects/embedfield.go
Normal file
7
utils/discord/objects/embedfield.go
Normal file
@ -0,0 +1,7 @@
|
||||
package objects
|
||||
|
||||
type EmbedField struct {
|
||||
Name string
|
||||
Value string
|
||||
Inline bool
|
||||
}
|
7
utils/discord/objects/embedfooter.go
Normal file
7
utils/discord/objects/embedfooter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package objects
|
||||
|
||||
type EmbedFooter struct {
|
||||
Text string
|
||||
IconUrl string
|
||||
ProxyIconUrl string
|
||||
}
|
8
utils/discord/objects/embedimage.go
Normal file
8
utils/discord/objects/embedimage.go
Normal file
@ -0,0 +1,8 @@
|
||||
package objects
|
||||
|
||||
type EmbedImage struct {
|
||||
Url string
|
||||
ProxyUrl string
|
||||
Height int
|
||||
Width int
|
||||
}
|
6
utils/discord/objects/embedprovider.go
Normal file
6
utils/discord/objects/embedprovider.go
Normal file
@ -0,0 +1,6 @@
|
||||
package objects
|
||||
|
||||
type EmbedProvider struct {
|
||||
Name string
|
||||
Url string
|
||||
}
|
8
utils/discord/objects/embedthumbnail.go
Normal file
8
utils/discord/objects/embedthumbnail.go
Normal file
@ -0,0 +1,8 @@
|
||||
package objects
|
||||
|
||||
type EmbedThumbnail struct {
|
||||
Url string
|
||||
ProxyUrl string
|
||||
Height int
|
||||
Width int
|
||||
}
|
7
utils/discord/objects/embedvideo.go
Normal file
7
utils/discord/objects/embedvideo.go
Normal file
@ -0,0 +1,7 @@
|
||||
package objects
|
||||
|
||||
type EmbedVideo struct {
|
||||
Url string
|
||||
Height int
|
||||
Width int
|
||||
}
|
25
utils/discord/objects/message.go
Normal file
25
utils/discord/objects/message.go
Normal file
@ -0,0 +1,25 @@
|
||||
package objects
|
||||
|
||||
type Message struct {
|
||||
Id string
|
||||
ChannelId string
|
||||
GuildId string
|
||||
Author User
|
||||
Member Member
|
||||
Content string
|
||||
Timestamp string
|
||||
EditedTimestamp string
|
||||
Tts bool
|
||||
MentionEveryone bool
|
||||
Mentions []interface{}
|
||||
MentionsRoles []int64
|
||||
Attachments []Attachment
|
||||
Embeds []Embed
|
||||
Reactions []Reaction
|
||||
Nonce string
|
||||
Pinned bool
|
||||
WebhookId string
|
||||
Type int
|
||||
Activity MessageActivity
|
||||
Application MessageApplication
|
||||
}
|
6
utils/discord/objects/messageactivity.go
Normal file
6
utils/discord/objects/messageactivity.go
Normal file
@ -0,0 +1,6 @@
|
||||
package objects
|
||||
|
||||
type MessageActivity struct {
|
||||
Type int
|
||||
PartyId string
|
||||
}
|
9
utils/discord/objects/messageapplication.go
Normal file
9
utils/discord/objects/messageapplication.go
Normal file
@ -0,0 +1,9 @@
|
||||
package objects
|
||||
|
||||
type MessageApplication struct {
|
||||
Id string
|
||||
CoverImage string
|
||||
Description string
|
||||
Icon string
|
||||
Name string
|
||||
}
|
7
utils/discord/objects/reaction.go
Normal file
7
utils/discord/objects/reaction.go
Normal file
@ -0,0 +1,7 @@
|
||||
package objects
|
||||
|
||||
type Reaction struct {
|
||||
Count int
|
||||
Me bool
|
||||
Emoji Emoji
|
||||
}
|
@ -32,3 +32,11 @@ func Insert(slice []objects.Guild, index int, value objects.Guild) []objects.Gui
|
||||
// Return the result.
|
||||
return slice
|
||||
}
|
||||
|
||||
func Reverse(slice []objects.Message) []objects.Message {
|
||||
for i := len(slice)/2-1; i >= 0; i-- {
|
||||
opp := len(slice)-1-i
|
||||
slice[i], slice[opp] = slice[opp], slice[i]
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
@ -19,3 +20,14 @@ func IsInt(str string) bool {
|
||||
_, err := strconv.ParseInt(str, 10, 64)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func Base64Decode(s string) string {
|
||||
b, err := base64.StdEncoding.DecodeString(s); if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func Base64Encode(s string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(s))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user