From 21e1a854ac2a753eb0524c73ae2a63f9f5299c34 Mon Sep 17 00:00:00 2001 From: Dot-Rar Date: Sun, 20 Oct 2019 14:11:54 +0100 Subject: [PATCH] Web chat --- app/http/endpoints/manage/sendmessage.go | 76 ++++++++ app/http/endpoints/manage/ticketview.go | 31 ++- app/http/endpoints/manage/webchatws.go | 176 ++++++++++++++++++ app/http/server.go | 7 +- cache/redis.go | 25 +++ cache/uriparser.go | 21 +++ cache/webchat.go | 41 ++++ cmd/panel/main.go | 5 + config.toml.example | 3 + config/config.go | 3 + database/table/premiumguilds.go | 72 +++++++ database/table/tickets.go | 10 +- database/table/username.go | 7 +- database/table/votes.go | 22 +++ public/static/css/discordmock.css | 48 ++++- public/templates/layouts/manage.mustache | 7 +- public/templates/views/ticketlist.mustache | 3 +- public/templates/views/ticketview.mustache | 74 +++++++- .../endpoints/channel/CreateMessage.go | 18 ++ utils/premiumutils.go | 104 +++++++++++ 20 files changed, 728 insertions(+), 25 deletions(-) create mode 100644 app/http/endpoints/manage/sendmessage.go create mode 100644 app/http/endpoints/manage/webchatws.go create mode 100644 cache/redis.go create mode 100644 cache/uriparser.go create mode 100644 cache/webchat.go create mode 100644 database/table/premiumguilds.go create mode 100644 database/table/votes.go create mode 100644 utils/discord/endpoints/channel/CreateMessage.go create mode 100644 utils/premiumutils.go diff --git a/app/http/endpoints/manage/sendmessage.go b/app/http/endpoints/manage/sendmessage.go new file mode 100644 index 0000000..3867842 --- /dev/null +++ b/app/http/endpoints/manage/sendmessage.go @@ -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()) +} diff --git a/app/http/endpoints/manage/ticketview.go b/app/http/endpoints/manage/ticketview.go index 7e73daa..ec75d5c 100644 --- a/app/http/endpoints/manage/ticketview.go +++ b/app/http/endpoints/manage/ticketview.go @@ -10,9 +10,13 @@ import ( "github.com/TicketsBot/GoPanel/utils/discord/objects" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" + "regexp" "strconv" + "strings" ) +var MentionRegex, _ = regexp.Compile("<@(\\d+)>") + func TicketViewHandler(ctx *gin.Context) { store := sessions.Default(ctx) if store == nil { @@ -53,7 +57,9 @@ func TicketViewHandler(ctx *gin.Context) { // Get ticket UUID from URL and verify it exists uuid := ctx.Param("uuid") - ticket := table.GetTicket(uuid) + ticketChan := make(chan table.Ticket) + go table.GetTicket(uuid, ticketChan) + ticket := <-ticketChan exists := ticket != table.Ticket{} // If invalid ticket UUID, take user to ticket list @@ -77,12 +83,31 @@ func TicketViewHandler(ctx *gin.Context) { // Format messages, exclude unneeded data var messagesFormatted []map[string]interface{} for _, message := range utils.Reverse(messages) { + content := message.Content + + // Format mentions properly + match := MentionRegex.FindAllStringSubmatch(content, -1) + for _, mention := range match { + if len(mention) >= 2 { + mentionedId, err := strconv.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{}{ "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{}{ "name": store.Get("name").(string), "guildId": guildIdStr, @@ -93,6 +118,8 @@ func TicketViewHandler(ctx *gin.Context) { "error": errorMessage, "messages": messagesFormatted, "ticketId": ticket.TicketId, + "include_mock": true, + "premium": <-premium, })) } } diff --git a/app/http/endpoints/manage/webchatws.go b/app/http/endpoints/manage/webchatws.go new file mode 100644 index 0000000..e460908 --- /dev/null +++ b/app/http/endpoints/manage/webchatws.go @@ -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) + } + } + } + } +} diff --git a/app/http/server.go b/app/http/server.go index 922b5a7..dbbdee8 100644 --- a/app/http/server.go +++ b/app/http/server.go @@ -66,7 +66,12 @@ func StartServer() { router.GET("/manage/:id/tickets", manage.TicketListHandler) // /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 { panic(err) diff --git a/cache/redis.go b/cache/redis.go new file mode 100644 index 0000000..49e91bc --- /dev/null +++ b/cache/redis.go @@ -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, + } +} diff --git a/cache/uriparser.go b/cache/uriparser.go new file mode 100644 index 0000000..844b7c9 --- /dev/null +++ b/cache/uriparser.go @@ -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, + } +} diff --git a/cache/webchat.go b/cache/webchat.go new file mode 100644 index 0000000..3e423c1 --- /dev/null +++ b/cache/webchat.go @@ -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() + } +} diff --git a/cmd/panel/main.go b/cmd/panel/main.go index 7dba0d3..b658713 100644 --- a/cmd/panel/main.go +++ b/cmd/panel/main.go @@ -2,6 +2,7 @@ package main import ( "github.com/TicketsBot/GoPanel/app/http" + "github.com/TicketsBot/GoPanel/cache" "github.com/TicketsBot/GoPanel/config" "github.com/TicketsBot/GoPanel/database" "math/rand" @@ -13,5 +14,9 @@ func main() { config.LoadConfig() database.ConnectToDatabase() + + cache.Client = cache.NewRedisClient() + go cache.Client.ListenForMessages() + http.StartServer() } diff --git a/config.toml.example b/config.toml.example index e1a4012..5e2c376 100644 --- a/config.toml.example +++ b/config.toml.example @@ -23,8 +23,11 @@ threads=5 [bot] token="" +premium-lookup-proxy-url="http://localhost:3000" +premium-lookup-proxy-key="" [redis] host="127.0.0.1" port=6379 password="" +threads=5 diff --git a/config/config.go b/config/config.go index 49b625c..a68261e 100644 --- a/config/config.go +++ b/config/config.go @@ -48,12 +48,15 @@ type ( Bot struct { Token string + PremiumLookupProxyUrl string `toml:"premium-lookup-proxy-url"` + PremiumLookupProxyKey string `toml:"premium-lookup-proxy-key"` } Redis struct { Host string Port int Password string + Threads int } ) diff --git a/database/table/premiumguilds.go b/database/table/premiumguilds.go new file mode 100644 index 0000000..0e40977 --- /dev/null +++ b/database/table/premiumguilds.go @@ -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, ",") +} diff --git a/database/table/tickets.go b/database/table/tickets.go index be147e9..15f3ec1 100644 --- a/database/table/tickets.go +++ b/database/table/tickets.go @@ -29,8 +29,14 @@ func GetOpenTickets(guild int64) []Ticket { return tickets } -func GetTicket(uuid string) Ticket { +func GetTicket(uuid string, ch chan Ticket) { var ticket 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 } diff --git a/database/table/username.go b/database/table/username.go index 2273dbe..782279f 100644 --- a/database/table/username.go +++ b/database/table/username.go @@ -2,7 +2,6 @@ package table import ( "github.com/TicketsBot/GoPanel/database" - "github.com/TicketsBot/GoPanel/utils" ) type UsernameNode struct { @@ -16,10 +15,10 @@ func (UsernameNode) TableName() string { return "usernames" } -func GetUsername(id int64) string { +func GetUsername(id int64, ch chan string) { node := UsernameNode{Name: "Unknown"} database.Database.Where(&UsernameNode{Id: id}).First(&node) - return utils.Base64Decode(node.Name) + ch <- node.Name } func GetUserNodes(ids []int64) []UsernameNode { @@ -30,6 +29,6 @@ func GetUserNodes(ids []int64) []UsernameNode { func GetUserId(name, discrim string) int64 { 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 } diff --git a/database/table/votes.go b/database/table/votes.go new file mode 100644 index 0000000..e0a8f71 --- /dev/null +++ b/database/table/votes.go @@ -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 +} diff --git a/public/static/css/discordmock.css b/public/static/css/discordmock.css index 61db96c..c098293 100644 --- a/public/static/css/discordmock.css +++ b/public/static/css/discordmock.css @@ -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:Whitney;font-style:bold;font-weight:700;src:url('https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff') format('woff')} +html { + overflow-y: hidden; +} + .discord-container { background-color: #2e3136; - border-radius: 25px; - height: 100vh; + border-radius: 4px; + height: 80vh; + max-height: 100vh; margin: 0; padding: 0; font-family: 'Whitney', sans-serif !important; @@ -17,7 +22,7 @@ background-color: #1e2124; height: 5vh; width: 100%; - border-radius: 25px 25px 0 0; + border-radius: 4px 4px 0 0; position: relative; text-align: center; @@ -29,3 +34,40 @@ color: white; 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; +} diff --git a/public/templates/layouts/manage.mustache b/public/templates/layouts/manage.mustache index 41218f0..7e27856 100644 --- a/public/templates/layouts/manage.mustache +++ b/public/templates/layouts/manage.mustache @@ -62,9 +62,10 @@ - - - + {{#include_mock}} + + + {{/include_mock}} diff --git a/public/templates/views/ticketlist.mustache b/public/templates/views/ticketlist.mustache index d417016..79ec0b1 100644 --- a/public/templates/views/ticketlist.mustache +++ b/public/templates/views/ticketlist.mustache @@ -23,8 +23,7 @@ {{ticketId}} {{username}}#{{discrim}} {{#members}}{{username}}#{{discrim}}{{sep}}{{/members}} - - Coming soon + View {{/tickets}} diff --git a/public/templates/views/ticketview.mustache b/public/templates/views/ticketview.mustache index 05dce28..43ed464 100644 --- a/public/templates/views/ticketview.mustache +++ b/public/templates/views/ticketview.mustache @@ -3,21 +3,30 @@
-
-

Ticket List

-
#ticket-{{ticketId}}
-
-
- -
+
+ {{#messages}} +
+ {{username}} + {{content}} +
+ {{/messages}}
- +
+ {{#premium}} + + {{/premium}} + {{^premium}} + + {{/premium}} +
@@ -48,3 +57,52 @@
+ + + +{{#premium}} + +{{/premium}} diff --git a/utils/discord/endpoints/channel/CreateMessage.go b/utils/discord/endpoints/channel/CreateMessage.go new file mode 100644 index 0000000..37c1346 --- /dev/null +++ b/utils/discord/endpoints/channel/CreateMessage.go @@ -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), + } +} diff --git a/utils/premiumutils.go b/utils/premiumutils.go new file mode 100644 index 0000000..277da44 --- /dev/null +++ b/utils/premiumutils.go @@ -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 + } +} \ No newline at end of file