Modmail
This commit is contained in:
parent
64c19c4dcf
commit
6e457bc1a4
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
"github.com/TicketsBot/GoPanel/database/table"
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
"github.com/TicketsBot/GoPanel/rpc/ratelimit"
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -56,9 +57,8 @@ func GetTicket(ctx *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan string)
|
user, _ := cache.Instance.GetUser(mentionedId)
|
||||||
go table.GetUsername(mentionedId, ch)
|
content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", user.Username))
|
||||||
content = strings.ReplaceAll(content, fmt.Sprintf("<@%d>", mentionedId), fmt.Sprintf("@%s", <-ch))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
81
app/http/endpoints/api/modmaillogslist.go
Normal file
81
app/http/endpoints/api/modmaillogslist.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var modmailPathRegex = regexp.MustCompile(`(\d+)\/modmail\/(?:free-)?([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})`)
|
||||||
|
|
||||||
|
type wrappedModLog struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
GuildId uint64 `json:"guild_id,string"`
|
||||||
|
UserId uint64 `json:"user_id,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModmailLogs(ctx *gin.Context) {
|
||||||
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
|
|
||||||
|
page, err := strconv.Atoi(ctx.Param("page"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithStatusJSON(400, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter
|
||||||
|
var userId uint64
|
||||||
|
|
||||||
|
if userIdRaw, filterByUserId := ctx.GetQuery("userid"); filterByUserId {
|
||||||
|
userId, err = strconv.ParseUint(userIdRaw, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithStatusJSON(400, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": "Invalid user ID",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if username, filterByUsername := ctx.GetQuery("username"); filterByUsername {
|
||||||
|
if err := cache.Instance.QueryRow(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).Scan(&userId); err != nil {
|
||||||
|
ctx.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": "User not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldFilter := userId > 0
|
||||||
|
|
||||||
|
start := pageLimit * (page - 1)
|
||||||
|
end := start + pageLimit - 1
|
||||||
|
|
||||||
|
wrapped := make([]wrappedModLog, 0)
|
||||||
|
|
||||||
|
var archives []table.ModMailArchive
|
||||||
|
if shouldFilter {
|
||||||
|
archivesCh := make(chan []table.ModMailArchive)
|
||||||
|
go table.GetModmailArchivesByUser(userId, guildId, archivesCh)
|
||||||
|
archives = <-archivesCh
|
||||||
|
} else {
|
||||||
|
archivesCh := make(chan []table.ModMailArchive)
|
||||||
|
go table.GetModmailArchivesByGuild(guildId, archivesCh)
|
||||||
|
archives = <-archivesCh
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := start; i < end && i < len(archives); i++ {
|
||||||
|
wrapped = append(wrapped, wrappedModLog{
|
||||||
|
Uuid: archives[i].Uuid,
|
||||||
|
GuildId: archives[i].Guild,
|
||||||
|
UserId: archives[i].User,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(200, wrapped)
|
||||||
|
}
|
36
app/http/endpoints/api/user.go
Normal file
36
app/http/endpoints/api/user.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserHandler(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": "Invalid user ID",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var username string
|
||||||
|
if err := cache.Instance.QueryRow(context.Background(), `SELECT "data"->>'Username' FROM users WHERE users.user_id=$1 AND EXISTS(SELECT FROM members WHERE members.guild_id=$2);`, userId, guildId).Scan(&username); err != nil {
|
||||||
|
ctx.JSON(404, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": "Not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(200, gin.H{
|
||||||
|
"user_id": userId,
|
||||||
|
"guild_id": guildId,
|
||||||
|
"username": username,
|
||||||
|
})
|
||||||
|
}
|
@ -67,14 +67,14 @@ func LogViewHandler(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.String(500, fmt.Sprintf("Failed to archives - please contact the developers: %s", err.Error()))
|
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// format to html
|
// format to html
|
||||||
html, err := Archiver.Encode(messages, ticketId)
|
html, err := Archiver.Encode(messages, fmt.Sprintf("ticket-%d", ticketId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.String(500, fmt.Sprintf("Failed to archives - please contact the developers: %s", err.Error()))
|
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
app/http/endpoints/manage/modmaillogslist.go
Normal file
19
app/http/endpoints/manage/modmaillogslist.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package manage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ModmailLogsHandler(ctx *gin.Context) {
|
||||||
|
store := sessions.Default(ctx)
|
||||||
|
guildId := ctx.Keys["guildid"].(uint64)
|
||||||
|
|
||||||
|
ctx.HTML(200, "manage/modmaillogs", gin.H{
|
||||||
|
"name": store.Get("name").(string),
|
||||||
|
"guildId": guildId,
|
||||||
|
"avatar": store.Get("avatar").(string),
|
||||||
|
"baseUrl": config.Conf.Server.BaseUrl,
|
||||||
|
})
|
||||||
|
}
|
86
app/http/endpoints/manage/modmaillogsview.go
Normal file
86
app/http/endpoints/manage/modmaillogsview.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package manage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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/TicketsBot/archiverclient"
|
||||||
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ModmailLogViewHandler(ctx *gin.Context) {
|
||||||
|
store := sessions.Default(ctx)
|
||||||
|
if store == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsLoggedIn(store) {
|
||||||
|
userId := utils.GetUserId(store)
|
||||||
|
|
||||||
|
// Verify the guild exists
|
||||||
|
guildId, err := strconv.ParseUint(ctx.Param("id"), 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)
|
||||||
|
|
||||||
|
// get ticket UUID
|
||||||
|
uuid := ctx.Param("uuid")
|
||||||
|
|
||||||
|
// get ticket object
|
||||||
|
archiveCh := make(chan table.ModMailArchive)
|
||||||
|
go table.GetModmailArchive(uuid, archiveCh)
|
||||||
|
archive := <-archiveCh
|
||||||
|
|
||||||
|
// Verify this is a valid ticket and it is closed
|
||||||
|
if archive.Uuid == "" {
|
||||||
|
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify this modmail ticket was for this guild
|
||||||
|
if archive.Guild != guildId {
|
||||||
|
ctx.Redirect(302, fmt.Sprintf("/manage/%d/logs/modmail", guild.Id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the user has permissions to be here
|
||||||
|
isAdmin := make(chan bool)
|
||||||
|
go utils.IsAdmin(guild, userId, isAdmin)
|
||||||
|
if !<-isAdmin && archive.User != userId {
|
||||||
|
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve ticket messages from bucket
|
||||||
|
messages, err := Archiver.GetModmail(guildId, uuid)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, archiverclient.ErrExpired) {
|
||||||
|
ctx.String(200, "Archives expired: Purchase premium for permanent log storage") // TODO: Actual error page
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// format to html
|
||||||
|
html, err := Archiver.Encode(messages, fmt.Sprintf("modmail-%s", uuid))
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(500, fmt.Sprintf("Failed to retrieve archive - please contact the developers: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data(200, gin.MIMEHTML, html)
|
||||||
|
} else {
|
||||||
|
ctx.Redirect(302, fmt.Sprintf("/login?noguilds&state=viewlog.%s.%s", ctx.Param("id"), ctx.Param("ticket")))
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,11 @@ import (
|
|||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartServer() {
|
func StartServer() {
|
||||||
@ -34,6 +38,7 @@ func StartServer() {
|
|||||||
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
|
router.Use(static.Serve("/assets/", static.LocalFile("./public/static", false)))
|
||||||
|
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
router.Use(createLimiter())
|
||||||
|
|
||||||
// Register templates
|
// Register templates
|
||||||
router.HTMLRender = createRenderer()
|
router.HTMLRender = createRenderer()
|
||||||
@ -42,6 +47,7 @@ func StartServer() {
|
|||||||
router.GET("/callback", root.CallbackHandler)
|
router.GET("/callback", root.CallbackHandler)
|
||||||
|
|
||||||
router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect
|
router.GET("/manage/:id/logs/view/:ticket", manage.LogViewHandler) // we check in the actual handler bc of a custom redirect
|
||||||
|
router.GET("/manage/:id/logs/modmail/view/:uuid", manage.ModmailLogViewHandler) // we check in the actual handler bc of a custom redirect
|
||||||
|
|
||||||
authorized := router.Group("/", middleware.AuthenticateCookie)
|
authorized := router.Group("/", middleware.AuthenticateCookie)
|
||||||
{
|
{
|
||||||
@ -54,6 +60,7 @@ func StartServer() {
|
|||||||
|
|
||||||
authenticateGuild.GET("/manage/:id/settings", manage.SettingsHandler)
|
authenticateGuild.GET("/manage/:id/settings", manage.SettingsHandler)
|
||||||
authenticateGuild.GET("/manage/:id/logs", manage.LogsHandler)
|
authenticateGuild.GET("/manage/:id/logs", manage.LogsHandler)
|
||||||
|
authenticateGuild.GET("/manage/:id/logs/modmail", manage.ModmailLogsHandler)
|
||||||
authenticateGuild.GET("/manage/:id/blacklist", manage.BlacklistHandler)
|
authenticateGuild.GET("/manage/:id/blacklist", manage.BlacklistHandler)
|
||||||
authenticateGuild.GET("/manage/:id/panels", manage.PanelHandler)
|
authenticateGuild.GET("/manage/:id/panels", manage.PanelHandler)
|
||||||
|
|
||||||
@ -69,6 +76,7 @@ func StartServer() {
|
|||||||
{
|
{
|
||||||
guildAuthApi.GET("/:id/channels", api.ChannelsHandler)
|
guildAuthApi.GET("/:id/channels", api.ChannelsHandler)
|
||||||
guildAuthApi.GET("/:id/premium", api.PremiumHandler)
|
guildAuthApi.GET("/:id/premium", api.PremiumHandler)
|
||||||
|
guildAuthApi.GET("/:id/user/:user", api.UserHandler)
|
||||||
|
|
||||||
guildAuthApi.GET("/:id/settings", api.GetSettingsHandler)
|
guildAuthApi.GET("/:id/settings", api.GetSettingsHandler)
|
||||||
guildAuthApi.POST("/:id/settings", api.UpdateSettingsHandler)
|
guildAuthApi.POST("/:id/settings", api.UpdateSettingsHandler)
|
||||||
@ -82,6 +90,7 @@ func StartServer() {
|
|||||||
guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel)
|
guildAuthApi.DELETE("/:id/panels/:message", api.DeletePanel)
|
||||||
|
|
||||||
guildAuthApi.GET("/:id/logs/:page", api.GetLogs)
|
guildAuthApi.GET("/:id/logs/:page", api.GetLogs)
|
||||||
|
guildAuthApi.GET("/:id/modmail/logs/:page", api.GetModmailLogs)
|
||||||
|
|
||||||
guildAuthApi.GET("/:id/tickets", api.GetTickets)
|
guildAuthApi.GET("/:id/tickets", api.GetTickets)
|
||||||
guildAuthApi.GET("/:id/tickets/:uuid", api.GetTicket)
|
guildAuthApi.GET("/:id/tickets/:uuid", api.GetTicket)
|
||||||
@ -106,6 +115,7 @@ func createRenderer() multitemplate.Renderer {
|
|||||||
|
|
||||||
r = addManageTemplate(r, "blacklist")
|
r = addManageTemplate(r, "blacklist")
|
||||||
r = addManageTemplate(r, "logs")
|
r = addManageTemplate(r, "logs")
|
||||||
|
r = addManageTemplate(r, "modmaillogs")
|
||||||
r = addManageTemplate(r, "settings")
|
r = addManageTemplate(r, "settings")
|
||||||
r = addManageTemplate(r, "ticketlist")
|
r = addManageTemplate(r, "ticketlist")
|
||||||
r = addManageTemplate(r, "ticketview")
|
r = addManageTemplate(r, "ticketview")
|
||||||
@ -135,3 +145,12 @@ func addManageTemplate(renderer multitemplate.Renderer, name string) multitempla
|
|||||||
return renderer
|
return renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createLimiter() func(*gin.Context) {
|
||||||
|
store := memory.NewStore()
|
||||||
|
rate := limiter.Rate{
|
||||||
|
Period: time.Minute * 10,
|
||||||
|
Limit: 600,
|
||||||
|
}
|
||||||
|
|
||||||
|
return mgin.NewMiddleware(limiter.New(store, rate))
|
||||||
|
}
|
||||||
|
@ -33,7 +33,7 @@ func main() {
|
|||||||
database.ConnectToDatabase()
|
database.ConnectToDatabase()
|
||||||
cache.Instance = cache.NewCache()
|
cache.Instance = cache.NewCache()
|
||||||
|
|
||||||
manage.Archiver = archiverclient.NewArchiverClient(config.Conf.Bot.ObjectStore)
|
manage.Archiver = archiverclient.NewArchiverClientWithTimeout(config.Conf.Bot.ObjectStore, time.Second * 15)
|
||||||
|
|
||||||
utils.LoadEmoji()
|
utils.LoadEmoji()
|
||||||
|
|
||||||
|
39
database/table/modmailarchive.go
Normal file
39
database/table/modmailarchive.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TicketsBot/GoPanel/database"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModMailArchive struct {
|
||||||
|
Uuid string `gorm:"column:UUID;type:varchar(36);unique;primary_key"`
|
||||||
|
Guild uint64 `gorm:"column:GUILDID"`
|
||||||
|
User uint64 `gorm:"column:USERID"`
|
||||||
|
CloseTime time.Time `gorm:"column:CLOSETIME"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ModMailArchive) TableName() string {
|
||||||
|
return "modmail_archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ModMailArchive) Store() {
|
||||||
|
database.Database.Create(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModmailArchive(uuid string, ch chan ModMailArchive) {
|
||||||
|
var row ModMailArchive
|
||||||
|
database.Database.Where(ModMailArchive{Uuid: uuid}).Take(&row)
|
||||||
|
ch <- row
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModmailArchivesByUser(userId, guildId uint64, ch chan []ModMailArchive) {
|
||||||
|
var rows []ModMailArchive
|
||||||
|
database.Database.Where(ModMailArchive{User: userId, Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
|
||||||
|
ch <- rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModmailArchivesByGuild(guildId uint64, ch chan []ModMailArchive) {
|
||||||
|
var rows []ModMailArchive
|
||||||
|
database.Database.Where(ModMailArchive{Guild: guildId}).Order("CLOSETIME desc").Find(&rows)
|
||||||
|
ch <- rows
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TicketsBot/GoPanel/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UsernameNode struct {
|
|
||||||
Id uint64 `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 {
|
|
||||||
return "usernames"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUsername(id uint64, ch chan string) {
|
|
||||||
node := UsernameNode{Name: "Unknown"}
|
|
||||||
database.Database.Where(&UsernameNode{Id: id}).First(&node)
|
|
||||||
ch <- node.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserNodes(ids []uint64) []UsernameNode {
|
|
||||||
var nodes []UsernameNode
|
|
||||||
database.Database.Where(ids).Find(&nodes)
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserId(name, discrim string) uint64 {
|
|
||||||
var node UsernameNode
|
|
||||||
database.Database.Where(&UsernameNode{Name: name, Discriminator: discrim}).First(&node)
|
|
||||||
return node.Id
|
|
||||||
}
|
|
5
go.mod
5
go.mod
@ -4,7 +4,7 @@ go 1.14
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/TicketsBot/archiverclient v0.0.0-20200420161043-3532ff9ea943
|
github.com/TicketsBot/archiverclient v0.0.0-20200425115930-0ca198cc8306
|
||||||
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/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
@ -21,6 +21,7 @@ require (
|
|||||||
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
|
||||||
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-20200421193445-f200b9f466d7
|
||||||
|
github.com/ulule/limiter/v3 v3.5.0
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/manage/{{.guildId}}/logs"><i class="fas fa-copy icon"></i>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">
|
||||||
|
<a class="nav-link" href="/manage/{{.guildId}}/logs/modmail"><i class="fas fa-copy icon"></i>Modmail Logs</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/manage/{{.guildId}}/blacklist"><i class="fas fa-ban icon"></i>Blacklist</a>
|
<a class="nav-link" href="/manage/{{.guildId}}/blacklist"><i class="fas fa-ban icon"></i>Blacklist</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -65,14 +65,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="log-container">
|
<tbody id="log-container">
|
||||||
{{range .logs}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.ticketid}}</td>
|
|
||||||
<td>{{.username}}</td>
|
|
||||||
<td>{{.userid}}</td>
|
|
||||||
<td><a href="/manage/{{$.guildId}}/logs/view/{{.ticketid}}">View</a></td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@ -80,7 +72,7 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination justify-content-center">
|
||||||
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||||
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page {{.page}}</p>
|
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page <span id="page-number">1</span></p>
|
||||||
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -110,10 +102,6 @@
|
|||||||
appendTd(tr, log.userid);
|
appendTd(tr, log.userid);
|
||||||
appendButton(tr, 'View', () => { location.href = '/manage/{{.guildId}}/logs/view/' + log.ticketid });
|
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);
|
container.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +123,8 @@
|
|||||||
for (log of res.data) {
|
for (log of res.data) {
|
||||||
appendLog(log);
|
appendLog(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('page-number').innerText = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
|
166
public/templates/views/modmaillogs.tmpl
Normal file
166
public/templates/views/modmaillogs.tmpl
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<div class="content">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="accordion">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header collapsed filterCard" id="filterHeader" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
||||||
|
<span class="align-middle white" data-toggle="collapse" data-target="#filterLogs" aria-expanded="false" aria-controls="filterLogs">
|
||||||
|
<i class="fas fa-search"></i> Filter Logs
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="filterLogs" class="collapse" aria-labelledby="filterHeader" data-parent="#accordion">
|
||||||
|
<div class="card-body">
|
||||||
|
<form onsubmit="filterLogs(); return false;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 px-1">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Username</label>
|
||||||
|
<input name="username" type="text" class="form-control" placeholder="Username" id="username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 px-1">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>User ID</label>
|
||||||
|
<input name="userid" type="text" class="form-control" placeholder="User ID" id="userid">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary mx-auto btn-fill"><i class="fas fa-paper-plane"></i> Filter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h4 class="card-title">Logs</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-body table-responsive">
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>User ID</th>
|
||||||
|
<th>Archive</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="log-container">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<li class="waves-effect"><a href="#" onclick="previous()"><i class="fas fa-chevron-left"></i></a></li>
|
||||||
|
<p class="center-align white" style="padding-left: 10px; padding-right: 10px;">Page <span id="page-number">1</span></p>
|
||||||
|
<li class="waves-effect"><a href="#" onclick="next()"><i class="fas fa-chevron-right"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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;
|
||||||
|
|
||||||
|
async function getUsername(userId) {
|
||||||
|
const res = await axios.get('/api/{{.guildId}}/user/' + userId);
|
||||||
|
return res.data.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appendLog(log) {
|
||||||
|
const username = await getUsername(log.user_id);
|
||||||
|
|
||||||
|
const container = document.getElementById('log-container');
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
|
||||||
|
appendTd(tr, username);
|
||||||
|
appendTd(tr, log.user_id);
|
||||||
|
appendButton(tr, 'View', () => { location.href = '/manage/{{.guildId}}/logs/modmail/view/' + log.uuid });
|
||||||
|
|
||||||
|
container.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPage(page, ticketId, username, userId) {
|
||||||
|
const container = document.getElementById('log-container');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
let url = '/api/{{.guildId}}/modmail/logs/' + page;
|
||||||
|
|
||||||
|
if (username !== undefined) {
|
||||||
|
url += `?username=${username}`;
|
||||||
|
} else if (userId !== undefined) {
|
||||||
|
url += `?userid=${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await axios.get(url);
|
||||||
|
if (res.status === 200) {
|
||||||
|
for (log of res.data) {
|
||||||
|
await appendLog(log);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast('Error', res.data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('page-number').innerText = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 username = document.getElementById('username').value;
|
||||||
|
const userId = document.getElementById('userid').value;
|
||||||
|
|
||||||
|
if (username !== "") {
|
||||||
|
await loadPage(1, undefined, username);
|
||||||
|
} else if (userId !== "") {
|
||||||
|
await loadPage(1, undefined, undefined, userId);
|
||||||
|
} else {
|
||||||
|
await loadPage(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
@ -111,7 +111,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getChannelName(channels, channelId) {
|
function getChannelName(channels, channelId) {
|
||||||
return channels.find(ch => ch.id === channelId);
|
const ch = channels.find(ch => ch.id === channelId);
|
||||||
|
if (ch === undefined) {
|
||||||
|
return "Unknown";
|
||||||
|
} else {
|
||||||
|
return ch.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deletePanel(messageId) {
|
async function deletePanel(messageId) {
|
||||||
@ -200,10 +205,10 @@
|
|||||||
const tr = document.createElement('tr');
|
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
|
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, `#${getChannelName(channels, panel.channel_id)}`);
|
||||||
appendTd(tr, panel.title);
|
appendTd(tr, panel.title);
|
||||||
appendTd(tr, panel.content);
|
appendTd(tr, panel.content);
|
||||||
appendTd(tr, getChannelName(channels, panel.category_id).name);
|
appendTd(tr, getChannelName(channels, panel.category_id));
|
||||||
|
|
||||||
// build remove button
|
// build remove button
|
||||||
const deleteTd = document.createElement('td');
|
const deleteTd = document.createElement('td');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user