Web chat
This commit is contained in:
parent
fcb7e51631
commit
21e1a854ac
76
app/http/endpoints/manage/sendmessage.go
Normal file
76
app/http/endpoints/manage/sendmessage.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package manage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils/discord"
|
||||||
|
"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 SendMessage(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
|
||||||
|
ticketChan := make(chan table.Ticket)
|
||||||
|
go table.GetTicket(ctx.Param("uuid"), ticketChan)
|
||||||
|
ticket := <-ticketChan
|
||||||
|
exists := ticket != table.Ticket{}
|
||||||
|
|
||||||
|
contentType := discord.ApplicationJson
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
content := fmt.Sprintf("**%s**: %s", store.Get("name").(string), ctx.PostForm("message"))
|
||||||
|
if len(content) > 2000 {
|
||||||
|
content = content[0:1999]
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := channel.CreateMessage(int(ticket.Channel))
|
||||||
|
err = endpoint.Request(store, &contentType, channel.CreateMessageBody{
|
||||||
|
Content: content,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(301, ctx.Request.URL.String())
|
||||||
|
}
|
@ -10,9 +10,13 @@ import (
|
|||||||
"github.com/TicketsBot/GoPanel/utils/discord/objects"
|
"github.com/TicketsBot/GoPanel/utils/discord/objects"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"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 {
|
if store == nil {
|
||||||
@ -53,7 +57,9 @@ func TicketViewHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Get ticket UUID from URL and verify it exists
|
// Get ticket UUID from URL and verify it exists
|
||||||
uuid := ctx.Param("uuid")
|
uuid := ctx.Param("uuid")
|
||||||
ticket := table.GetTicket(uuid)
|
ticketChan := make(chan table.Ticket)
|
||||||
|
go table.GetTicket(uuid, ticketChan)
|
||||||
|
ticket := <-ticketChan
|
||||||
exists := ticket != table.Ticket{}
|
exists := ticket != table.Ticket{}
|
||||||
|
|
||||||
// If invalid ticket UUID, take user to ticket list
|
// If invalid ticket UUID, take user to ticket list
|
||||||
@ -77,12 +83,31 @@ func TicketViewHandler(ctx *gin.Context) {
|
|||||||
// Format messages, exclude unneeded data
|
// Format messages, exclude unneeded data
|
||||||
var messagesFormatted []map[string]interface{}
|
var messagesFormatted []map[string]interface{}
|
||||||
for _, message := range utils.Reverse(messages) {
|
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.ParseInt(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{}{
|
messagesFormatted = append(messagesFormatted, map[string]interface{}{
|
||||||
"username": message.Author.Username,
|
"username": message.Author.Username,
|
||||||
"content": message.Content,
|
"content": content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
premium := make(chan bool)
|
||||||
|
go utils.IsPremiumGuild(store, guildIdStr, premium)
|
||||||
|
|
||||||
utils.Respond(ctx, template.TemplateTicketView.Render(map[string]interface{}{
|
utils.Respond(ctx, template.TemplateTicketView.Render(map[string]interface{}{
|
||||||
"name": store.Get("name").(string),
|
"name": store.Get("name").(string),
|
||||||
"guildId": guildIdStr,
|
"guildId": guildIdStr,
|
||||||
@ -93,6 +118,8 @@ func TicketViewHandler(ctx *gin.Context) {
|
|||||||
"error": errorMessage,
|
"error": errorMessage,
|
||||||
"messages": messagesFormatted,
|
"messages": messagesFormatted,
|
||||||
"ticketId": ticket.TicketId,
|
"ticketId": ticket.TicketId,
|
||||||
|
"include_mock": true,
|
||||||
|
"premium": <-premium,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
176
app/http/endpoints/manage/webchatws.go
Normal file
176
app/http/endpoints/manage/webchatws.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package manage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils/discord"
|
||||||
|
"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"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
var SocketsLock sync.Mutex
|
||||||
|
var Sockets []*Socket
|
||||||
|
|
||||||
|
type (
|
||||||
|
Socket struct {
|
||||||
|
Ws *websocket.Conn
|
||||||
|
Guild string
|
||||||
|
Ticket int
|
||||||
|
}
|
||||||
|
|
||||||
|
WsEvent struct {
|
||||||
|
Type string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthEvent struct {
|
||||||
|
Guild string
|
||||||
|
Ticket string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func WebChatWs(ctx *gin.Context) {
|
||||||
|
store := sessions.Default(ctx)
|
||||||
|
if store == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer store.Save()
|
||||||
|
|
||||||
|
if utils.IsLoggedIn(store) {
|
||||||
|
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socket := &Socket{
|
||||||
|
Ws: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetCloseHandler(func(code int, text string) error {
|
||||||
|
i := -1
|
||||||
|
SocketsLock.Lock()
|
||||||
|
|
||||||
|
for index, element := range Sockets {
|
||||||
|
if element == socket {
|
||||||
|
i = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != -1 {
|
||||||
|
Sockets = Sockets[:i+copy(Sockets[i:], Sockets[i+1:])]
|
||||||
|
}
|
||||||
|
SocketsLock.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
SocketsLock.Lock()
|
||||||
|
Sockets = append(Sockets, socket)
|
||||||
|
SocketsLock.Unlock()
|
||||||
|
|
||||||
|
userIdStr := store.Get("userid").(string)
|
||||||
|
userId, err := utils.GetUserId(store)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var guildId string
|
||||||
|
var guildIdParsed int64
|
||||||
|
var ticket int
|
||||||
|
|
||||||
|
for {
|
||||||
|
var evnt WsEvent
|
||||||
|
err := conn.ReadJSON(&evnt)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if guildId == "" && evnt.Type != "auth" {
|
||||||
|
conn.Close()
|
||||||
|
break
|
||||||
|
} else if evnt.Type == "auth" {
|
||||||
|
data := evnt.Data.(map[string]interface{})
|
||||||
|
|
||||||
|
guildId = data["guild"].(string)
|
||||||
|
ticket, err = strconv.Atoi(data["ticket"].(string)); if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.Guild = guildId
|
||||||
|
socket.Ticket = ticket
|
||||||
|
|
||||||
|
// Verify the guild exists
|
||||||
|
guildIdParsed, err = strconv.ParseInt(guildId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get object for selected guild
|
||||||
|
var guild objects.Guild
|
||||||
|
for _, g := range table.GetGuilds(userIdStr) {
|
||||||
|
if g.Id == guildId {
|
||||||
|
guild = g
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the user has permissions to be here
|
||||||
|
if !guild.Owner && !table.IsAdmin(guildIdParsed, userId) {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the guild is premium
|
||||||
|
premium := make(chan bool)
|
||||||
|
go utils.IsPremiumGuild(store, guildId, premium)
|
||||||
|
if !<-premium {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if evnt.Type == "send" {
|
||||||
|
data := evnt.Data.(string)
|
||||||
|
|
||||||
|
if data == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ticket UUID from URL and verify it exists
|
||||||
|
ticketChan := make(chan table.Ticket)
|
||||||
|
go table.GetTicketById(guildIdParsed, ticket, ticketChan)
|
||||||
|
ticket := <-ticketChan
|
||||||
|
exists := ticket != table.Ticket{}
|
||||||
|
|
||||||
|
contentType := discord.ApplicationJson
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
content := fmt.Sprintf("**%s**: %s", store.Get("name").(string), data)
|
||||||
|
if len(content) > 2000 {
|
||||||
|
content = content[0:1999]
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := channel.CreateMessage(int(ticket.Channel))
|
||||||
|
err = endpoint.Request(store, &contentType, channel.CreateMessageBody{
|
||||||
|
Content: content,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -66,7 +66,12 @@ func StartServer() {
|
|||||||
router.GET("/manage/:id/tickets", manage.TicketListHandler)
|
router.GET("/manage/:id/tickets", manage.TicketListHandler)
|
||||||
|
|
||||||
// /manage/:id/tickets/view/:uuid
|
// /manage/:id/tickets/view/:uuid
|
||||||
//router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler)
|
router.GET("/manage/:id/tickets/view/:uuid", manage.TicketViewHandler)
|
||||||
|
|
||||||
|
// POST /manage/:id/tickets/view/:uuid
|
||||||
|
router.POST("/manage/:id/tickets/view/:uuid", manage.SendMessage)
|
||||||
|
|
||||||
|
router.GET("/webchat", manage.WebChatWs)
|
||||||
|
|
||||||
if err := router.Run(config.Conf.Server.Host); err != nil {
|
if err := router.Run(config.Conf.Server.Host); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
25
cache/redis.go
vendored
Normal file
25
cache/redis.go
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedisClient struct {
|
||||||
|
*redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var Client RedisClient
|
||||||
|
|
||||||
|
func NewRedisClient() RedisClient {
|
||||||
|
client := redis.NewClient(&redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", config.Conf.Redis.Host, config.Conf.Redis.Port),
|
||||||
|
Password: config.Conf.Redis.Password,
|
||||||
|
PoolSize: config.Conf.Redis.Threads,
|
||||||
|
})
|
||||||
|
|
||||||
|
return RedisClient{
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
}
|
21
cache/uriparser.go
vendored
Normal file
21
cache/uriparser.go
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
type RedisURI struct {
|
||||||
|
Addr string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseURI(raw string) RedisURI {
|
||||||
|
parsed, err := url.Parse(raw); if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
passwd, _ := parsed.User.Password()
|
||||||
|
|
||||||
|
return RedisURI{
|
||||||
|
Addr: parsed.Host,
|
||||||
|
Password: passwd,
|
||||||
|
}
|
||||||
|
}
|
41
cache/webchat.go
vendored
Normal file
41
cache/webchat.go
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/app/http/endpoints/manage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TicketMessage struct {
|
||||||
|
GuildId string `json:"guild"`
|
||||||
|
TicketId int `json:"ticket"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RedisClient) ListenForMessages() {
|
||||||
|
pubsub := c.Subscribe("tickets:webchat:inboundmessage")
|
||||||
|
|
||||||
|
for {
|
||||||
|
msg, err := pubsub.ReceiveMessage(); if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded TicketMessage
|
||||||
|
if err := json.Unmarshal([]byte(msg.Payload), &decoded); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
manage.SocketsLock.Lock()
|
||||||
|
for _, socket := range manage.Sockets {
|
||||||
|
if socket.Guild == decoded.GuildId && socket.Ticket == decoded.TicketId {
|
||||||
|
if err := socket.Ws.WriteJSON(decoded); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manage.SocketsLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/app/http"
|
"github.com/TicketsBot/GoPanel/app/http"
|
||||||
|
"github.com/TicketsBot/GoPanel/cache"
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
"github.com/TicketsBot/GoPanel/database"
|
"github.com/TicketsBot/GoPanel/database"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -13,5 +14,9 @@ func main() {
|
|||||||
|
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
database.ConnectToDatabase()
|
database.ConnectToDatabase()
|
||||||
|
|
||||||
|
cache.Client = cache.NewRedisClient()
|
||||||
|
go cache.Client.ListenForMessages()
|
||||||
|
|
||||||
http.StartServer()
|
http.StartServer()
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,11 @@ threads=5
|
|||||||
|
|
||||||
[bot]
|
[bot]
|
||||||
token=""
|
token=""
|
||||||
|
premium-lookup-proxy-url="http://localhost:3000"
|
||||||
|
premium-lookup-proxy-key=""
|
||||||
|
|
||||||
[redis]
|
[redis]
|
||||||
host="127.0.0.1"
|
host="127.0.0.1"
|
||||||
port=6379
|
port=6379
|
||||||
password=""
|
password=""
|
||||||
|
threads=5
|
||||||
|
@ -48,12 +48,15 @@ type (
|
|||||||
|
|
||||||
Bot struct {
|
Bot struct {
|
||||||
Token string
|
Token string
|
||||||
|
PremiumLookupProxyUrl string `toml:"premium-lookup-proxy-url"`
|
||||||
|
PremiumLookupProxyKey string `toml:"premium-lookup-proxy-key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Redis struct {
|
Redis struct {
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
Password string
|
Password string
|
||||||
|
Threads int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
72
database/table/premiumguilds.go
Normal file
72
database/table/premiumguilds.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TicketsBot/GoPanel/database"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PremiumGuilds struct {
|
||||||
|
Guild int64 `gorm:"column:GUILDID;unique;primary_key"`
|
||||||
|
Expiry int64 `gorm:"column:EXPIRY"`
|
||||||
|
User int64 `gorm:"column:USERID"`
|
||||||
|
ActivatedBy int64 `gorm:"column:ACTIVATEDBY"`
|
||||||
|
Keys string `gorm:"column:KEYSUSED"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PremiumGuilds) TableName() string {
|
||||||
|
return "premiumguilds"
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPremium(guild int64, ch chan bool) {
|
||||||
|
var node PremiumGuilds
|
||||||
|
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
|
||||||
|
|
||||||
|
if node.Expiry == 0 {
|
||||||
|
ch <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
current := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
ch <- node.Expiry > current
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPremium(key string, guild, userId, length, activatedBy int64) {
|
||||||
|
var expiry int64
|
||||||
|
|
||||||
|
hasPrem := make(chan bool)
|
||||||
|
go IsPremium(guild, hasPrem)
|
||||||
|
isPremium := <- hasPrem
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
expiryChan := make(chan int64)
|
||||||
|
go GetExpiry(guild, expiryChan)
|
||||||
|
currentExpiry := <- expiryChan
|
||||||
|
|
||||||
|
expiry = currentExpiry + length
|
||||||
|
} else {
|
||||||
|
current := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
expiry = current + length
|
||||||
|
}
|
||||||
|
|
||||||
|
keysChan := make(chan []string)
|
||||||
|
go GetKeysUsed(guild, keysChan)
|
||||||
|
keys := <- keysChan
|
||||||
|
keys = append(keys, key)
|
||||||
|
keysStr := strings.Join(keys,",")
|
||||||
|
|
||||||
|
var node PremiumGuilds
|
||||||
|
database.Database.Where(PremiumGuilds{Guild: guild}).Assign(PremiumGuilds{Expiry: expiry, User: userId, ActivatedBy: activatedBy, Keys: keysStr}).FirstOrCreate(&node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExpiry(guild int64, ch chan int64) {
|
||||||
|
var node PremiumGuilds
|
||||||
|
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
|
||||||
|
ch <- node.Expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKeysUsed(guild int64, ch chan []string) {
|
||||||
|
var node PremiumGuilds
|
||||||
|
database.Database.Where(PremiumGuilds{Guild: guild}).First(&node)
|
||||||
|
ch <- strings.Split(node.Keys, ",")
|
||||||
|
}
|
@ -29,8 +29,14 @@ func GetOpenTickets(guild int64) []Ticket {
|
|||||||
return tickets
|
return tickets
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTicket(uuid string) Ticket {
|
func GetTicket(uuid string, ch chan Ticket) {
|
||||||
var ticket Ticket
|
var ticket Ticket
|
||||||
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
|
database.Database.Where(&Ticket{Uuid: uuid}).First(&ticket)
|
||||||
return ticket
|
ch <- ticket
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTicketById(guild int64, id int, ch chan Ticket) {
|
||||||
|
var ticket Ticket
|
||||||
|
database.Database.Where(&Ticket{Guild: guild, TicketId: id}).First(&ticket)
|
||||||
|
ch <- ticket
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package table
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TicketsBot/GoPanel/database"
|
"github.com/TicketsBot/GoPanel/database"
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsernameNode struct {
|
type UsernameNode struct {
|
||||||
@ -16,10 +15,10 @@ func (UsernameNode) TableName() string {
|
|||||||
return "usernames"
|
return "usernames"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUsername(id int64) string {
|
func GetUsername(id int64, ch chan string) {
|
||||||
node := UsernameNode{Name: "Unknown"}
|
node := UsernameNode{Name: "Unknown"}
|
||||||
database.Database.Where(&UsernameNode{Id: id}).First(&node)
|
database.Database.Where(&UsernameNode{Id: id}).First(&node)
|
||||||
return utils.Base64Decode(node.Name)
|
ch <- node.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserNodes(ids []int64) []UsernameNode {
|
func GetUserNodes(ids []int64) []UsernameNode {
|
||||||
@ -30,6 +29,6 @@ func GetUserNodes(ids []int64) []UsernameNode {
|
|||||||
|
|
||||||
func GetUserId(name, discrim string) int64 {
|
func GetUserId(name, discrim string) int64 {
|
||||||
var node UsernameNode
|
var node UsernameNode
|
||||||
database.Database.Where(&UsernameNode{Name: utils.Base64Encode(name), Discriminator: discrim}).First(&node)
|
database.Database.Where(&UsernameNode{Name: name, Discriminator: discrim}).First(&node)
|
||||||
return node.Id
|
return node.Id
|
||||||
}
|
}
|
||||||
|
22
database/table/votes.go
Normal file
22
database/table/votes.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TicketsBot/GoPanel/database"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Votes struct {
|
||||||
|
Id int64 `gorm:"type:bigint;unique_index;primary_key"`
|
||||||
|
VoteTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Votes) TableName() string {
|
||||||
|
return "votes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasVoted(owner int64, ch chan bool) {
|
||||||
|
var node Votes
|
||||||
|
database.Database.Where(Votes{Id: owner}).First(&node)
|
||||||
|
|
||||||
|
ch <- time.Now().Sub(node.VoteTime) < 24 * time.Hour
|
||||||
|
}
|
@ -4,10 +4,15 @@
|
|||||||
@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: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')}
|
@font-face{font-family:Whitney;font-style:bold;font-weight:700;src:url('https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff') format('woff')}
|
||||||
|
|
||||||
|
html {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.discord-container {
|
.discord-container {
|
||||||
background-color: #2e3136;
|
background-color: #2e3136;
|
||||||
border-radius: 25px;
|
border-radius: 4px;
|
||||||
height: 100vh;
|
height: 80vh;
|
||||||
|
max-height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: 'Whitney', sans-serif !important;
|
font-family: 'Whitney', sans-serif !important;
|
||||||
@ -17,7 +22,7 @@
|
|||||||
background-color: #1e2124;
|
background-color: #1e2124;
|
||||||
height: 5vh;
|
height: 5vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 25px 25px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -29,3 +34,40 @@
|
|||||||
color: white;
|
color: white;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#message-container {
|
||||||
|
height: 70vh;
|
||||||
|
max-height: 70vh;
|
||||||
|
position: relative;
|
||||||
|
overflow: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
color: white !important;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
padding-top: 20px !important;
|
||||||
|
border-color: #2e3136 !important;
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
background-color: #2e3136 !important;
|
||||||
|
color: white !important;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #2e3136 !important;
|
||||||
|
background-color: #2e3136 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input:focus {
|
||||||
|
border-color: #2e3136 !important;
|
||||||
|
background-color: #2e3136 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
@ -62,9 +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://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>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Discord theme -->
|
{{#include_mock}}
|
||||||
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
|
<!-- Discord theme -->
|
||||||
|
<link href="/assets/css/discordmock.css" rel="stylesheet"/>
|
||||||
|
{{/include_mock}}
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
<td>{{ticketId}}</td>
|
<td>{{ticketId}}</td>
|
||||||
<td>{{username}}#{{discrim}}</td>
|
<td>{{username}}#{{discrim}}</td>
|
||||||
<td>{{#members}}{{username}}#{{discrim}}{{sep}}{{/members}}</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><a class="btn btn-primary btn-sm" role="button" href="/manage/{{guildId}}/tickets/view/{{uuid}}">View</a></td>
|
||||||
<td>Coming soon</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{{/tickets}}
|
{{/tickets}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -3,21 +3,30 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
|
||||||
<h4 class="card-title">Ticket List</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="discord-container">
|
<div class="discord-container">
|
||||||
<div class="channel-header">
|
<div class="channel-header">
|
||||||
<span class="channel-name">#ticket-{{ticketId}}</span>
|
<span class="channel-name">#ticket-{{ticketId}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-container">
|
<div id="message-container">
|
||||||
<div class="message">
|
{{#messages}}
|
||||||
|
<div class="message">
|
||||||
</div>
|
<b>{{username}}</b>
|
||||||
|
{{content}}
|
||||||
|
</div>
|
||||||
|
{{/messages}}
|
||||||
</div>
|
</div>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
|
<form action="javascript:sendMessage()">
|
||||||
|
{{#premium}}
|
||||||
|
<input type="text" class="form-control message-input" id="message" name="message"
|
||||||
|
placeholder="Message #ticket-{{ticketId}}">
|
||||||
|
{{/premium}}
|
||||||
|
{{^premium}}
|
||||||
|
<input type="text" class="form-control message-input" id="message" name="message"
|
||||||
|
placeholder="Premium users get live messages and can respond through webchat" disabled>
|
||||||
|
{{/premium}}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,3 +57,52 @@
|
|||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Scroll to bottom
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{#premium}}
|
||||||
|
<script>
|
||||||
|
ws = new WebSocket("ws://localhost:3001/webchat");
|
||||||
|
|
||||||
|
ws.onopen = (evt) => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"type": "auth",
|
||||||
|
"data": {
|
||||||
|
"guild": "{{guildId}}",
|
||||||
|
"ticket": "{{ticketId}}"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (evt) => {
|
||||||
|
let data = JSON.parse(evt.data);
|
||||||
|
|
||||||
|
let container = document.getElementById("message-container");
|
||||||
|
|
||||||
|
let element = document.createElement("div");
|
||||||
|
element.className = "message";
|
||||||
|
element.innerHTML = `
|
||||||
|
<b>${data.username}</b>
|
||||||
|
${data.content}
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(element);
|
||||||
|
|
||||||
|
// Scroll to bottom
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
let msg = document.getElementById("message").value;
|
||||||
|
document.getElementById("message").value = "";
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"type": "send",
|
||||||
|
"data": msg
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{/premium}}
|
||||||
|
18
utils/discord/endpoints/channel/CreateMessage.go
Normal file
18
utils/discord/endpoints/channel/CreateMessage.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package channel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils/discord"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateMessageBody struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateMessage(id int) discord.Endpoint {
|
||||||
|
return discord.Endpoint{
|
||||||
|
RequestType: discord.POST,
|
||||||
|
AuthorizationType: discord.BOT,
|
||||||
|
Endpoint: fmt.Sprintf("/channels/%d/messages", id),
|
||||||
|
}
|
||||||
|
}
|
104
utils/premiumutils.go
Normal file
104
utils/premiumutils.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
|
"github.com/TicketsBot/GoPanel/database/table"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils/discord/endpoints/guild"
|
||||||
|
"github.com/TicketsBot/GoPanel/utils/discord/objects"
|
||||||
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
|
"github.com/robfig/go-cache"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyResponse struct {
|
||||||
|
Premium bool
|
||||||
|
Tier int
|
||||||
|
}
|
||||||
|
|
||||||
|
var premiumCache = cache.New(10 * time.Minute, 10 * time.Minute)
|
||||||
|
|
||||||
|
func IsPremiumGuild(store sessions.Session, guildIdRaw string, ch chan bool) {
|
||||||
|
if premium, ok := premiumCache.Get(guildIdRaw); ok {
|
||||||
|
ch<-premium.(bool)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildId, err := strconv.ParseInt(guildIdRaw, 10, 64); if err != nil {
|
||||||
|
ch<-false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First lookup by premium key, then votes, then patreon
|
||||||
|
keyLookup := make(chan bool)
|
||||||
|
go table.IsPremium(guildId, keyLookup)
|
||||||
|
|
||||||
|
if <-keyLookup {
|
||||||
|
if err := premiumCache.Add(guildIdRaw, true, 10 * time.Minute); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ch<-true
|
||||||
|
} else {
|
||||||
|
// Get guild object
|
||||||
|
var g objects.Guild
|
||||||
|
endpoint := guild.GetGuild(int(guildId))
|
||||||
|
go endpoint.Request(store, nil, nil, &g)
|
||||||
|
|
||||||
|
// Lookup votes
|
||||||
|
ownerId, err := strconv.ParseInt(g.OwnerId, 10, 64); if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
ch <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVoted := make(chan bool)
|
||||||
|
table.HasVoted(ownerId, hasVoted)
|
||||||
|
if <-hasVoted {
|
||||||
|
ch <- true
|
||||||
|
|
||||||
|
if err := premiumCache.Add(guildIdRaw, true, 10 * time.Minute); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup Patreon
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/ispremium?key=%s&id=%s", config.Conf.Bot.PremiumLookupProxyUrl, config.Conf.Bot.PremiumLookupProxyKey, g.OwnerId)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
|
||||||
|
res, err := client.Do(req); if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
ch<-false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(res.Body); if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
ch<-false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxyResponse ProxyResponse
|
||||||
|
if err = json.Unmarshal(content, &proxyResponse); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
ch<-false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := premiumCache.Add(guildIdRaw, proxyResponse.Premium, 10 * time.Minute); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
ch <-proxyResponse.Premium
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user