Make use of local storage
This commit is contained in:
parent
1796ec9cb5
commit
16fadb8663
@ -1,151 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"context"
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
|
||||||
"github.com/TicketsBot/GoPanel/rpc/cache"
|
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
|
||||||
"github.com/TicketsBot/common/collections"
|
|
||||||
"github.com/TicketsBot/common/permission"
|
|
||||||
syncutils "github.com/TicketsBot/common/utils"
|
|
||||||
"github.com/TicketsBot/database"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/jackc/pgtype"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type wrappedGuild struct {
|
|
||||||
Id uint64 `json:"id,string"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
PermissionLevel permission.PermissionLevel `json:"permission_level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetGuilds(c *gin.Context) {
|
|
||||||
userId := c.Keys["userid"].(uint64)
|
|
||||||
|
|
||||||
// Get the guilds that the user is in, that the bot is also in
|
|
||||||
userGuilds, err := getGuildIntersection(userId)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wg := syncutils.NewChannelWaitGroup()
|
|
||||||
wg.Add(len(userGuilds))
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(c, time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
group, ctx := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
|
||||||
guilds := make([]wrappedGuild, 0, len(userGuilds))
|
|
||||||
for _, guild := range userGuilds {
|
|
||||||
guild := guild
|
|
||||||
|
|
||||||
group.Go(func() error {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
permLevel, err := utils.GetPermissionLevel(ctx, guild.GuildId, userId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
guilds = append(guilds, wrappedGuild{
|
|
||||||
Id: guild.GuildId,
|
|
||||||
Name: guild.Name,
|
|
||||||
Icon: guild.Icon,
|
|
||||||
PermissionLevel: permLevel,
|
|
||||||
})
|
|
||||||
mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := group.Wait(); err != nil {
|
|
||||||
c.JSON(500, utils.ErrorJson(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the guilds by name, but put the guilds with permission_level=0 last
|
|
||||||
slices.SortFunc(guilds, func(a, b wrappedGuild) int {
|
|
||||||
if a.PermissionLevel == 0 && b.PermissionLevel > 0 {
|
|
||||||
return 1
|
|
||||||
} else if a.PermissionLevel > 0 && b.PermissionLevel == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmp.Compare(a.Name, b.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
c.JSON(200, guilds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGuildIntersection(userId uint64) ([]database.UserGuild, error) {
|
|
||||||
// Get all the guilds that the user is in
|
|
||||||
userGuilds, err := dbclient.Client.UserGuilds.Get(context.Background(), userId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
guildIds := make([]uint64, len(userGuilds))
|
|
||||||
for i, guild := range userGuilds {
|
|
||||||
guildIds[i] = guild.GuildId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restrict the set of guilds to guilds that the bot is also in
|
|
||||||
botGuilds, err := getExistingGuilds(guildIds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
botGuildIds := collections.NewSet[uint64]()
|
|
||||||
for _, guildId := range botGuilds {
|
|
||||||
botGuildIds.Add(guildId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the intersection of the two sets
|
|
||||||
intersection := make([]database.UserGuild, 0, len(botGuilds))
|
|
||||||
for _, guild := range userGuilds {
|
|
||||||
if botGuildIds.Contains(guild.GuildId) {
|
|
||||||
intersection = append(intersection, guild)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return intersection, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExistingGuilds(userGuilds []uint64) ([]uint64, error) {
|
|
||||||
query := `SELECT "guild_id" from guilds WHERE "guild_id" = ANY($1);`
|
|
||||||
|
|
||||||
userGuildsArray := &pgtype.Int8Array{}
|
|
||||||
if err := userGuildsArray.Set(userGuilds); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := cache.Instance.Query(context.Background(), query, userGuildsArray)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var existingGuilds []uint64
|
|
||||||
for rows.Next() {
|
|
||||||
var guildId uint64
|
|
||||||
if err := rows.Scan(&guildId); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
existingGuilds = append(existingGuilds, guildId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingGuilds, nil
|
|
||||||
}
|
|
@ -1,30 +1,34 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TicketsBot/GoPanel/app/http/session"
|
"github.com/TicketsBot/GoPanel/app/http/session"
|
||||||
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
"github.com/TicketsBot/GoPanel/redis"
|
"github.com/TicketsBot/GoPanel/redis"
|
||||||
wrapper "github.com/TicketsBot/GoPanel/redis"
|
wrapper "github.com/TicketsBot/GoPanel/redis"
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
"github.com/TicketsBot/GoPanel/utils/discord"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rxdn/gdl/rest"
|
||||||
|
"github.com/rxdn/gdl/rest/request"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReloadGuildsHandler(ctx *gin.Context) {
|
func ReloadGuildsHandler(c *gin.Context) {
|
||||||
userId := ctx.Keys["userid"].(uint64)
|
userId := c.Keys["userid"].(uint64)
|
||||||
|
|
||||||
key := fmt.Sprintf("tickets:dashboard:guildreload:%d", userId)
|
key := fmt.Sprintf("tickets:dashboard:guildreload:%d", userId)
|
||||||
res, err := redis.Client.SetNX(wrapper.DefaultContext(), key, 1, time.Second*10).Result()
|
res, err := redis.Client.SetNX(wrapper.DefaultContext(), key, 1, time.Second*10).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res {
|
if !res {
|
||||||
ttl, err := redis.Client.TTL(wrapper.DefaultContext(), key).Result()
|
ttl, err := redis.Client.TTL(wrapper.DefaultContext(), key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
c.JSON(500, utils.ErrorJson(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,19 +37,19 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
|||||||
ttl = 0
|
ttl = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(429, utils.ErrorStr("You're doing this too quickly: try again in %d seconds", int(ttl.Seconds())))
|
c.JSON(429, utils.ErrorStr("You're doing this too quickly: try again in %d seconds", int(ttl.Seconds())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := session.Store.Get(userId)
|
store, err := session.Store.Get(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == session.ErrNoSession {
|
if err == session.ErrNoSession {
|
||||||
ctx.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"auth": true,
|
"auth": true,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -53,9 +57,9 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
// What does this do?
|
// What does this do?
|
||||||
if store.Expiry > time.Now().Unix() {
|
if store.Expiry > time.Now().Unix() {
|
||||||
res, err := discord.RefreshToken(store.RefreshToken)
|
res, err := rest.RefreshToken(c, nil, config.Conf.Oauth.Id, config.Conf.Oauth.Secret, store.RefreshToken)
|
||||||
if err != nil { // Tell client to re-authenticate
|
if err != nil { // Tell client to re-authenticate
|
||||||
ctx.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"reauthenticate_required": true,
|
"reauthenticate_required": true,
|
||||||
})
|
})
|
||||||
@ -67,20 +71,31 @@ func ReloadGuildsHandler(ctx *gin.Context) {
|
|||||||
store.Expiry = time.Now().Unix() + int64(res.ExpiresIn)
|
store.Expiry = time.Now().Unix() + int64(res.ExpiresIn)
|
||||||
|
|
||||||
if err := session.Store.Set(userId, store); err != nil {
|
if err := session.Store.Set(userId, store); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.LoadGuilds(store.AccessToken, userId); err != nil {
|
guilds, err := utils.LoadGuilds(c, store.AccessToken, userId)
|
||||||
// TODO: Log to sentry
|
if err != nil {
|
||||||
|
var oauthError request.OAuthError
|
||||||
|
if errors.As(err, &oauthError) {
|
||||||
|
if oauthError.ErrorCode == "invalid_grant" {
|
||||||
// Tell client to reauth, needs a 200 or client will display error
|
// Tell client to reauth, needs a 200 or client will display error
|
||||||
ctx.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"reauthenticate_required": true,
|
"reauthenticate_required": true,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ctx.JSON(200, utils.SuccessResponse)
|
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"guilds": guilds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,54 +2,53 @@ package root
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/TicketsBot/GoPanel/app"
|
||||||
"github.com/TicketsBot/GoPanel/app/http/session"
|
"github.com/TicketsBot/GoPanel/app/http/session"
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
"github.com/TicketsBot/GoPanel/config"
|
||||||
"github.com/TicketsBot/GoPanel/utils"
|
"github.com/TicketsBot/GoPanel/utils"
|
||||||
"github.com/TicketsBot/GoPanel/utils/discord"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/rxdn/gdl/rest"
|
"github.com/rxdn/gdl/rest"
|
||||||
|
"github.com/rxdn/gdl/rest/request"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
func CallbackHandler(c *gin.Context) {
|
||||||
TokenData struct {
|
code, ok := c.GetQuery("code")
|
||||||
ClientId string `qs:"client_id"`
|
|
||||||
ClientSecret string `qs:"client_secret"`
|
|
||||||
GrantType string `qs:"grant_type"`
|
|
||||||
Code string `qs:"code"`
|
|
||||||
RedirectUri string `qs:"redirect_uri"`
|
|
||||||
Scope string `qs:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenResponse struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
ExpiresIn int `json:"expires_in"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func CallbackHandler(ctx *gin.Context) {
|
|
||||||
code, ok := ctx.GetQuery("code")
|
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(400, utils.ErrorStr("Discord provided invalid Oauth2 code"))
|
c.JSON(400, utils.ErrorStr("Missing code query parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := discord.AccessToken(code)
|
res, err := rest.ExchangeCode(c, nil, config.Conf.Oauth.Id, config.Conf.Oauth.Secret, config.Conf.Oauth.RedirectUri, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
var oauthError request.OAuthError
|
||||||
|
if errors.As(err, &oauthError) {
|
||||||
|
if oauthError.ErrorCode == "invalid_grant" {
|
||||||
|
c.JSON(400, utils.ErrorStr("Invalid code: try logging in again"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, app.NewServerError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes := strings.Split(res.Scope, " ")
|
||||||
|
if !utils.Contains(scopes, "identify") {
|
||||||
|
c.JSON(400, utils.ErrorStr("Missing identify scope"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ID + name
|
// Get ID + name
|
||||||
currentUser, err := rest.GetCurrentUser(context.Background(), fmt.Sprintf("Bearer %s", res.AccessToken), nil)
|
currentUser, err := rest.GetCurrentUser(context.Background(), fmt.Sprintf("Bearer %s", res.AccessToken), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, app.NewServerError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,31 +61,47 @@ func CallbackHandler(ctx *gin.Context) {
|
|||||||
HasGuilds: false,
|
HasGuilds: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.LoadGuilds(res.AccessToken, currentUser.Id); err == nil {
|
var guilds []utils.GuildDto
|
||||||
store.HasGuilds = true
|
if utils.Contains(scopes, "guilds") {
|
||||||
} else {
|
guilds, err = utils.LoadGuilds(c, res.AccessToken, currentUser.Id)
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, app.NewServerError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.HasGuilds = true
|
||||||
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"userid": strconv.FormatUint(currentUser.Id, 10),
|
"userid": strconv.FormatUint(currentUser.Id, 10),
|
||||||
"timestamp": time.Now(),
|
"sub": strconv.FormatUint(currentUser.Id, 10),
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
})
|
})
|
||||||
|
|
||||||
str, err := token.SignedString([]byte(config.Conf.Server.Secret))
|
str, err := token.SignedString([]byte(config.Conf.Server.Secret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.Store.Set(currentUser.Id, store); err != nil {
|
if err := session.Store.Set(currentUser.Id, store); err != nil {
|
||||||
ctx.JSON(500, utils.ErrorJson(err))
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(200, gin.H{
|
resMap := gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"token": str,
|
"token": str,
|
||||||
})
|
"user_data": gin.H{
|
||||||
|
"id": strconv.FormatUint(currentUser.Id, 10),
|
||||||
|
"username": currentUser.Username,
|
||||||
|
"avatar": currentUser.Avatar,
|
||||||
|
"admin": utils.Contains(config.Conf.Admins, currentUser.Id),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if len(guilds) > 0 {
|
||||||
|
resMap["guilds"] = guilds
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resMap)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ func ErrorHandler(c *gin.Context) {
|
|||||||
message = "An error occurred processing your request"
|
message = "An error occurred processing your request"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Writer = cw.ResponseWriter
|
||||||
c.JSON(-1, ErrorResponse{
|
c.JSON(-1, ErrorResponse{
|
||||||
Error: message,
|
Error: message,
|
||||||
})
|
})
|
||||||
@ -44,6 +45,8 @@ func ErrorHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Writer.Status() >= 500 {
|
if c.Writer.Status() >= 500 {
|
||||||
|
c.Writer = cw.ResponseWriter
|
||||||
|
|
||||||
c.JSON(-1, ErrorResponse{
|
c.JSON(-1, ErrorResponse{
|
||||||
Error: "An internal server error occurred",
|
Error: "An internal server error occurred",
|
||||||
})
|
})
|
||||||
|
@ -196,7 +196,6 @@ func StartServer(logger *zap.Logger, sm *livechat.SocketManager) {
|
|||||||
|
|
||||||
userGroup := router.Group("/user", middleware.AuthenticateToken, middleware.UpdateLastSeen)
|
userGroup := router.Group("/user", middleware.AuthenticateToken, middleware.UpdateLastSeen)
|
||||||
{
|
{
|
||||||
userGroup.GET("/guilds", api.GetGuilds)
|
|
||||||
userGroup.POST("/guilds/reload", api.ReloadGuildsHandler)
|
userGroup.POST("/guilds/reload", api.ReloadGuildsHandler)
|
||||||
userGroup.GET("/permissionlevel", api.GetPermissionLevel)
|
userGroup.GET("/permissionlevel", api.GetPermissionLevel)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {API_URL, OAUTH} from "../js/constants";
|
import {OAUTH} from "../js/constants";
|
||||||
|
|
||||||
const _tokenKey = 'token';
|
const _tokenKey = 'token';
|
||||||
|
|
||||||
@ -31,17 +31,19 @@
|
|||||||
axios.defaults.headers.common['Authorization'] = getToken();
|
axios.defaults.headers.common['Authorization'] = getToken();
|
||||||
axios.defaults.headers.common['x-tickets'] = 'true'; // arbitrary header name and value
|
axios.defaults.headers.common['x-tickets'] = 'true'; // arbitrary header name and value
|
||||||
axios.defaults.validateStatus = (s) => true;
|
axios.defaults.validateStatus = (s) => true;
|
||||||
|
|
||||||
|
addRefreshInterceptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRefreshInterceptor() {
|
function addRefreshInterceptor() {
|
||||||
axios.interceptors.response.use(async (res) => { // we set validateStatus to false
|
axios.interceptors.response.use(async (res) => { // we set validateStatus to false
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
await _refreshToken();
|
redirectLogin();
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}, async (err) => {
|
}, async (err) => {
|
||||||
if (err.response.status === 401) {
|
if (err.response.status === 401) {
|
||||||
await _refreshToken();
|
redirectLogin();
|
||||||
}
|
}
|
||||||
return err.response;
|
return err.response;
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import {Navigate} from 'svelte-router-spa'
|
import {Navigate} from 'svelte-router-spa'
|
||||||
|
import {getAvatarUrl, getDefaultIcon} from "../js/icons";
|
||||||
|
|
||||||
export let name;
|
export let userData;
|
||||||
export let avatar;
|
|
||||||
|
let hasFailed = false;
|
||||||
|
function handleAvatarLoadError(e, userId) {
|
||||||
|
if (!hasFailed) {
|
||||||
|
hasFailed = true;
|
||||||
|
e.target.src = getDefaultIcon(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export let isWhitelabel = false;
|
|
||||||
export let isAdmin = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
@ -17,22 +23,13 @@
|
|||||||
<span class="sidebar-text">Servers</span>
|
<span class="sidebar-text">Servers</span>
|
||||||
</div>
|
</div>
|
||||||
</Navigate>
|
</Navigate>
|
||||||
{#if isWhitelabel}
|
|
||||||
<Navigate to="/whitelabel" styles="sidebar-link">
|
<Navigate to="/whitelabel" styles="sidebar-link">
|
||||||
<div class="sidebar-element">
|
<div class="sidebar-element">
|
||||||
<i class="fas fa-edit sidebar-icon"></i>
|
<i class="fas fa-edit sidebar-icon"></i>
|
||||||
<span class="sidebar-text">Whitelabel</span>
|
<span class="sidebar-text">Whitelabel</span>
|
||||||
</div>
|
</div>
|
||||||
</Navigate>
|
</Navigate>
|
||||||
{:else}
|
{#if userData.admin}
|
||||||
<a href="https://ticketsbot.net/premium" class="sidebar-link">
|
|
||||||
<div class="sidebar-element">
|
|
||||||
<i class="fas fa-edit sidebar-icon"></i>
|
|
||||||
<span class="sidebar-text">Whitelabel</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{#if isAdmin}
|
|
||||||
<Navigate to="/admin/bot-staff" styles="sidebar-link">
|
<Navigate to="/admin/bot-staff" styles="sidebar-link">
|
||||||
<div class="sidebar-element">
|
<div class="sidebar-element">
|
||||||
<i class="fa-solid fa-user-secret sidebar-icon"></i>
|
<i class="fa-solid fa-user-secret sidebar-icon"></i>
|
||||||
@ -51,10 +48,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sidebar-element user-element">
|
<div class="sidebar-element user-element">
|
||||||
<a class="sidebar-link">
|
<a class="sidebar-link">
|
||||||
<i id="avatar-sidebar" style="background: url('{avatar}') center center;"></i>
|
<img class="avatar" src={getAvatarUrl(userData.id, userData.avatar)}
|
||||||
{#if name !== undefined}
|
on:error={(e) => handleAvatarLoadError(e, userData.id)} alt="Avatar"/>
|
||||||
<span class="sidebar-text">{name}</span>
|
|
||||||
{/if}
|
<span class="sidebar-text">{userData.username}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,7 +130,7 @@
|
|||||||
margin: 0 !important
|
margin: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#avatar-sidebar {
|
.avatar {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -23,37 +23,24 @@
|
|||||||
import {loadingScreen} from "../js/stores"
|
import {loadingScreen} from "../js/stores"
|
||||||
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
|
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
|
||||||
import AdminSidebar from "../includes/AdminSidebar.svelte";
|
import AdminSidebar from "../includes/AdminSidebar.svelte";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
export let currentRoute;
|
export let currentRoute;
|
||||||
export let params = {};
|
export let params = {};
|
||||||
|
|
||||||
setDefaultHeaders()
|
setDefaultHeaders();
|
||||||
|
|
||||||
let name;
|
onMount(() => {
|
||||||
let avatar;
|
|
||||||
|
|
||||||
let isWhitelabel = false;
|
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
try {
|
||||||
async function loadData() {
|
const userData = JSON.parse(window.localStorage.getItem('user_data'));
|
||||||
const res = await axios.get(`${API_URL}/api/session`);
|
isAdmin = userData.admin;
|
||||||
if (res.status !== 200) {
|
} finally {
|
||||||
if (res.data.auth === true) {
|
|
||||||
redirectLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyError(res.data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin = res.data.admin;
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
navigateTo(`/`);
|
navigateTo(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
loadData();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Head/>
|
<Head/>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<Sidebar {name} {avatar} {isWhitelabel} {isAdmin} />
|
<Sidebar {userData} />
|
||||||
<div class="super-container">
|
<div class="super-container">
|
||||||
<LoadingScreen/>
|
<LoadingScreen/>
|
||||||
<NotifyModal/>
|
<NotifyModal/>
|
||||||
@ -22,36 +22,26 @@
|
|||||||
import {notifyError} from '../js/util'
|
import {notifyError} from '../js/util'
|
||||||
import {loadingScreen} from "../js/stores"
|
import {loadingScreen} from "../js/stores"
|
||||||
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
|
import {redirectLogin, setDefaultHeaders} from '../includes/Auth.svelte'
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
export let currentRoute;
|
export let currentRoute;
|
||||||
export let params = {};
|
export let params = {};
|
||||||
|
|
||||||
setDefaultHeaders()
|
setDefaultHeaders()
|
||||||
|
|
||||||
let name;
|
let userData = {
|
||||||
let avatar;
|
id: 0,
|
||||||
|
username: 'Unknown',
|
||||||
|
avatar: '',
|
||||||
|
admin: false
|
||||||
|
};
|
||||||
|
|
||||||
let isWhitelabel = false;
|
onMount(() => {
|
||||||
let isAdmin = false;
|
const retrieved = window.localStorage.getItem('user_data');
|
||||||
|
if (retrieved) {
|
||||||
async function loadData() {
|
userData = JSON.parse(retrieved);
|
||||||
const res = await axios.get(`${API_URL}/api/session`);
|
|
||||||
if (res.status !== 200) {
|
|
||||||
if (res.data.auth === true) {
|
|
||||||
redirectLogin();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
notifyError(res.data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = res.data.username;
|
|
||||||
avatar = res.data.avatar;
|
|
||||||
isWhitelabel = res.data.whitelabel;
|
|
||||||
isAdmin = res.data.admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -38,27 +38,7 @@
|
|||||||
|
|
||||||
setDefaultHeaders();
|
setDefaultHeaders();
|
||||||
|
|
||||||
export let guilds = [];
|
let guilds = window.localStorage.getItem('guilds') ? JSON.parse(window.localStorage.getItem('guilds')) : [];
|
||||||
|
|
||||||
async function loadData() {
|
|
||||||
const res = await axios.get(`${API_URL}/user/guilds`);
|
|
||||||
if (res.status !== 200) {
|
|
||||||
notifyError(res.data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
guilds = res.data;
|
|
||||||
|
|
||||||
permissionLevelCache.update(cache => {
|
|
||||||
for (const guild of guilds) {
|
|
||||||
cache[guild.id] = {
|
|
||||||
permission_level: guild.permission_level,
|
|
||||||
last_updated: new Date(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return cache;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshGuilds() {
|
async function refreshGuilds() {
|
||||||
await withLoadingScreen(async () => {
|
await withLoadingScreen(async () => {
|
||||||
@ -73,13 +53,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadData();
|
guilds = res.data.guilds;
|
||||||
|
window.localStorage.setItem('guilds', JSON.stringify(guilds));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
withLoadingScreen(async () => {
|
withLoadingScreen(() => {});
|
||||||
await loadData();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
setToken(res.data.token);
|
setToken(res.data.token);
|
||||||
|
window.localStorage.setItem('user_data', JSON.stringify(res.data.user_data));
|
||||||
|
if (res.data.guilds) {
|
||||||
|
window.localStorage.setItem('guilds', JSON.stringify(res.data.guilds));
|
||||||
|
}
|
||||||
|
|
||||||
let path = '/';
|
let path = '/';
|
||||||
|
|
||||||
@ -33,6 +37,11 @@
|
|||||||
if (path === '/callback') {
|
if (path === '/callback') {
|
||||||
path = '/';
|
path = '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new URL(path);
|
||||||
|
path = '/';
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Error parsing state: ${e}`)
|
console.log(`Error parsing state: ${e}`)
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{#each staff as user}
|
{#each staff as user}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{user.username}#{user.discriminator} ({user.id})</td>
|
<td>{user.username} ({user.id})</td>
|
||||||
<td>
|
<td>
|
||||||
<Button type="button" danger on:click={() => removeStaff(user.id)}>
|
<Button type="button" danger on:click={() => removeStaff(user.id)}>
|
||||||
Delete
|
Delete
|
||||||
|
@ -141,13 +141,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadGuilds() {
|
async function loadGuilds() {
|
||||||
const res = await axios.get(`${API_URL}/user/guilds`)
|
const fromLocalStorage = window.localStorage.getItem('guilds');
|
||||||
if (res.status !== 200) {
|
if (!fromLocalStorage) {
|
||||||
notifyError(`Failed to load guilds: ${res.data.error}`)
|
notifyError('Failed to load guilds from local storage.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
guilds = [...guilds, ...res.data];
|
guilds = [...guilds, ...JSON.parse(fromLocalStorage)];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitServers() {
|
async function submitServers() {
|
||||||
|
4
go.mod
4
go.mod
@ -8,7 +8,7 @@ require (
|
|||||||
github.com/BurntSushi/toml v1.2.1
|
github.com/BurntSushi/toml v1.2.1
|
||||||
github.com/TicketsBot/archiverclient v0.0.0-20241012221057-16a920bfb454
|
github.com/TicketsBot/archiverclient v0.0.0-20241012221057-16a920bfb454
|
||||||
github.com/TicketsBot/common v0.0.0-20241104184641-e39c64bdcf3e
|
github.com/TicketsBot/common v0.0.0-20241104184641-e39c64bdcf3e
|
||||||
github.com/TicketsBot/database v0.0.0-20241110235041-04a08e1a42a4
|
github.com/TicketsBot/database v0.0.0-20241113215509-c84281e50a7e
|
||||||
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138
|
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138
|
||||||
github.com/TicketsBot/worker v0.0.0-20241110222533-ba74e19de868
|
github.com/TicketsBot/worker v0.0.0-20241110222533-ba74e19de868
|
||||||
github.com/apex/log v1.1.2
|
github.com/apex/log v1.1.2
|
||||||
@ -29,7 +29,7 @@ require (
|
|||||||
github.com/penglongli/gin-metrics v0.1.10
|
github.com/penglongli/gin-metrics v0.1.10
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.14.0
|
||||||
github.com/rxdn/gdl v0.0.0-20241027214923-02dff700595b
|
github.com/rxdn/gdl v0.0.0-20241113224447-d578afa35bd3
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/weppos/publicsuffix-go v0.20.0
|
github.com/weppos/publicsuffix-go v0.20.0
|
||||||
|
4
go.sum
4
go.sum
@ -49,6 +49,8 @@ github.com/TicketsBot/common v0.0.0-20241104184641-e39c64bdcf3e h1:cYfBjPX/FhD/M
|
|||||||
github.com/TicketsBot/common v0.0.0-20241104184641-e39c64bdcf3e/go.mod h1:N7zwetwx8B3RK/ZajWwMroJSyv2ZJ+bIOZWv/z8DhaM=
|
github.com/TicketsBot/common v0.0.0-20241104184641-e39c64bdcf3e/go.mod h1:N7zwetwx8B3RK/ZajWwMroJSyv2ZJ+bIOZWv/z8DhaM=
|
||||||
github.com/TicketsBot/database v0.0.0-20241110235041-04a08e1a42a4 h1:9YMD+x2nBQN8SimrrveijYM1f7PSZ92J3h7UqgSeZfs=
|
github.com/TicketsBot/database v0.0.0-20241110235041-04a08e1a42a4 h1:9YMD+x2nBQN8SimrrveijYM1f7PSZ92J3h7UqgSeZfs=
|
||||||
github.com/TicketsBot/database v0.0.0-20241110235041-04a08e1a42a4/go.mod h1:mpVkDO8tnnWn1pMGEphVg6YSeGIhDwLAN43lBTkpGmU=
|
github.com/TicketsBot/database v0.0.0-20241110235041-04a08e1a42a4/go.mod h1:mpVkDO8tnnWn1pMGEphVg6YSeGIhDwLAN43lBTkpGmU=
|
||||||
|
github.com/TicketsBot/database v0.0.0-20241113215509-c84281e50a7e h1:8nvgWvmFAJfjuAUhaGdOO9y7puPAOHRjgGWn+/DI/HA=
|
||||||
|
github.com/TicketsBot/database v0.0.0-20241113215509-c84281e50a7e/go.mod h1:mpVkDO8tnnWn1pMGEphVg6YSeGIhDwLAN43lBTkpGmU=
|
||||||
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138 h1:wsR5ESeaQKo122qsmzPcblxlJdE0GIQbp2B/7/uX+TA=
|
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138 h1:wsR5ESeaQKo122qsmzPcblxlJdE0GIQbp2B/7/uX+TA=
|
||||||
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138/go.mod h1:4Rq0CgSCgXVW6uEyEUvWzxOmFp+L57rFfCjPDFPHFiw=
|
github.com/TicketsBot/logarchiver v0.0.0-20241012220745-5f3ba17a5138/go.mod h1:4Rq0CgSCgXVW6uEyEUvWzxOmFp+L57rFfCjPDFPHFiw=
|
||||||
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=
|
github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM=
|
||||||
@ -489,6 +491,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
|
|||||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
github.com/rxdn/gdl v0.0.0-20241027214923-02dff700595b h1:vSQ8iR4vrrDNchF24oxKMYbdO2D/2HKNqQkx8+v2ZMY=
|
github.com/rxdn/gdl v0.0.0-20241027214923-02dff700595b h1:vSQ8iR4vrrDNchF24oxKMYbdO2D/2HKNqQkx8+v2ZMY=
|
||||||
github.com/rxdn/gdl v0.0.0-20241027214923-02dff700595b/go.mod h1:hDxVWVHzvsO3Mt9d5KIjMLbm3K91Qgqw3LS0FIUxGVo=
|
github.com/rxdn/gdl v0.0.0-20241027214923-02dff700595b/go.mod h1:hDxVWVHzvsO3Mt9d5KIjMLbm3K91Qgqw3LS0FIUxGVo=
|
||||||
|
github.com/rxdn/gdl v0.0.0-20241113224447-d578afa35bd3 h1:ScHj6weZ5Eg4OqNH/8VT0VUxquywJ/Y8Ft/nanztbzI=
|
||||||
|
github.com/rxdn/gdl v0.0.0-20241113224447-d578afa35bd3/go.mod h1:hDxVWVHzvsO3Mt9d5KIjMLbm3K91Qgqw3LS0FIUxGVo=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/schollz/progressbar/v3 v3.8.2 h1:2kZJwZCpb+E/V79kGO7daeq+hUwUJW0A5QD1Wv455dA=
|
github.com/schollz/progressbar/v3 v3.8.2 h1:2kZJwZCpb+E/V79kGO7daeq+hUwUJW0A5QD1Wv455dA=
|
||||||
github.com/schollz/progressbar/v3 v3.8.2/go.mod h1:9KHLdyuXczIsyStQwzvW8xiELskmX7fQMaZdN23nAv8=
|
github.com/schollz/progressbar/v3 v3.8.2/go.mod h1:9KHLdyuXczIsyStQwzvW8xiELskmX7fQMaZdN23nAv8=
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
package discord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
|
||||||
"github.com/pasztorpisti/qs"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
TokenData struct {
|
|
||||||
ClientId string `qs:"client_id"`
|
|
||||||
ClientSecret string `qs:"client_secret"`
|
|
||||||
GrantType string `qs:"grant_type"`
|
|
||||||
Code string `qs:"code"`
|
|
||||||
RedirectUri string `qs:"redirect_uri"`
|
|
||||||
Scope string `qs:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshData struct {
|
|
||||||
ClientId string `qs:"client_id"`
|
|
||||||
ClientSecret string `qs:"client_secret"`
|
|
||||||
GrantType string `qs:"grant_type"`
|
|
||||||
RefreshToken string `qs:"refresh_token"`
|
|
||||||
RedirectUri string `qs:"redirect_uri"`
|
|
||||||
Scope string `qs:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenResponse struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
ExpiresIn int `json:"expires_in"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const TokenEndpoint = "https://discord.com/api/oauth2/token"
|
|
||||||
|
|
||||||
func AccessToken(code string) (TokenResponse, error) {
|
|
||||||
data := TokenData{
|
|
||||||
ClientId: strconv.FormatUint(config.Conf.Oauth.Id, 10),
|
|
||||||
ClientSecret: config.Conf.Oauth.Secret,
|
|
||||||
GrantType: "authorization_code",
|
|
||||||
Code: code,
|
|
||||||
RedirectUri: config.Conf.Oauth.RedirectUri,
|
|
||||||
Scope: "identify guilds",
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := tokenPost(data)
|
|
||||||
if err != nil {
|
|
||||||
return TokenResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var unmarshalled TokenResponse
|
|
||||||
if err = json.Unmarshal(res, &unmarshalled); err != nil {
|
|
||||||
return TokenResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalled, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RefreshToken(refreshToken string) (TokenResponse, error) {
|
|
||||||
data := RefreshData{
|
|
||||||
ClientId: strconv.FormatUint(config.Conf.Oauth.Id, 10),
|
|
||||||
ClientSecret: config.Conf.Oauth.Secret,
|
|
||||||
GrantType: "refresh_token",
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
RedirectUri: config.Conf.Oauth.RedirectUri,
|
|
||||||
Scope: "identify guilds",
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := tokenPost(data)
|
|
||||||
if err != nil {
|
|
||||||
return TokenResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var unmarshalled TokenResponse
|
|
||||||
if err = json.Unmarshal(res, &unmarshalled); err != nil {
|
|
||||||
return TokenResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalled, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenPost(body ...interface{}) ([]byte, error) {
|
|
||||||
str, err := qs.Marshal(body[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encoded := []byte(str)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", TokenEndpoint, bytes.NewBuffer([]byte(encoded)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", string(ApplicationFormUrlEncoded))
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
client.Timeout = 3 * time.Second
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
content, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
package discord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/TicketsBot/GoPanel/config"
|
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
|
||||||
"github.com/pasztorpisti/qs"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestType string
|
|
||||||
type ContentType string
|
|
||||||
type AuthorizationType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
GET RequestType = "GET"
|
|
||||||
POST RequestType = "POST"
|
|
||||||
PATCH RequestType = "PATCH"
|
|
||||||
|
|
||||||
BEARER AuthorizationType = "Bearer"
|
|
||||||
BOT AuthorizationType = "BOT"
|
|
||||||
NONE AuthorizationType = "NONE"
|
|
||||||
|
|
||||||
ApplicationJson ContentType = "application/json"
|
|
||||||
ApplicationFormUrlEncoded ContentType = "application/x-www-form-urlencoded"
|
|
||||||
|
|
||||||
BASE_URL = "https://discordapp.com/api/v8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Endpoint struct {
|
|
||||||
RequestType RequestType
|
|
||||||
AuthorizationType AuthorizationType
|
|
||||||
Endpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) Request(store sessions.Session, contentType *ContentType, body interface{}, response interface{}) (error, *http.Response) {
|
|
||||||
url := BASE_URL + e.Endpoint
|
|
||||||
// Create req
|
|
||||||
var req *http.Request
|
|
||||||
var err error
|
|
||||||
if body == nil || contentType == nil {
|
|
||||||
req, err = http.NewRequest(string(e.RequestType), url, nil)
|
|
||||||
} else {
|
|
||||||
// Encode body
|
|
||||||
var encoded []byte
|
|
||||||
if *contentType == ApplicationJson {
|
|
||||||
raw, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
encoded = raw
|
|
||||||
} else if *contentType == ApplicationFormUrlEncoded {
|
|
||||||
str, err := qs.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
encoded = []byte(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create req
|
|
||||||
req, err = http.NewRequest(string(e.RequestType), url, bytes.NewBuffer(encoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set content type and user agent
|
|
||||||
if contentType != nil {
|
|
||||||
req.Header.Set("Content-Type", string(*contentType))
|
|
||||||
}
|
|
||||||
req.Header.Set("User-Agent", "DiscordBot (https://github.com/TicketsBot/GoPanel, 1.0.0)")
|
|
||||||
|
|
||||||
// Auth
|
|
||||||
accessToken := store.Get("access_token").(string)
|
|
||||||
expiry := store.Get("expiry").(int64)
|
|
||||||
refreshToken := store.Get("refresh_token").(string)
|
|
||||||
|
|
||||||
// Check if needs refresh
|
|
||||||
if (time.Now().UnixNano() / int64(time.Second)) > expiry {
|
|
||||||
res, err := RefreshToken(refreshToken)
|
|
||||||
if err != nil {
|
|
||||||
store.Clear()
|
|
||||||
_ = store.Save()
|
|
||||||
return errors.New("Please login again!"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
store.Set("access_token", res.AccessToken)
|
|
||||||
store.Set("expiry", (time.Now().UnixNano()/int64(time.Second))+int64(res.ExpiresIn))
|
|
||||||
store.Set("refresh_token", res.RefreshToken)
|
|
||||||
|
|
||||||
accessToken = res.AccessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.AuthorizationType {
|
|
||||||
case BEARER:
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
|
||||||
case BOT:
|
|
||||||
req.Header.Set("Authorization", "Bot "+config.Conf.Bot.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
client.Timeout = 3 * time.Second
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(content, response), res
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import "github.com/TicketsBot/GoPanel/utils/discord"
|
|
||||||
|
|
||||||
var CurrentUser = discord.Endpoint{
|
|
||||||
RequestType: discord.GET,
|
|
||||||
AuthorizationType: discord.BEARER,
|
|
||||||
Endpoint: "/users/@me",
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import "github.com/TicketsBot/GoPanel/utils/discord"
|
|
||||||
|
|
||||||
var CurrentUserGuilds = discord.Endpoint{
|
|
||||||
RequestType: discord.GET,
|
|
||||||
AuthorizationType: discord.BEARER,
|
|
||||||
Endpoint: "/users/@me/guilds",
|
|
||||||
}
|
|
@ -1,29 +1,96 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/TicketsBot/GoPanel/app"
|
|
||||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||||
|
"github.com/TicketsBot/GoPanel/rpc/cache"
|
||||||
|
"github.com/TicketsBot/common/collections"
|
||||||
|
"github.com/TicketsBot/common/permission"
|
||||||
"github.com/TicketsBot/database"
|
"github.com/TicketsBot/database"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
"github.com/rxdn/gdl/objects/guild"
|
||||||
"github.com/rxdn/gdl/rest"
|
"github.com/rxdn/gdl/rest"
|
||||||
|
errgroup "golang.org/x/sync/errgroup"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadGuilds(accessToken string, userId uint64) error {
|
type GuildDto struct {
|
||||||
|
Id uint64 `json:"id,string"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
PermissionLevel permission.PermissionLevel `json:"permission_level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadGuilds(ctx context.Context, accessToken string, userId uint64) ([]GuildDto, error) {
|
||||||
authHeader := fmt.Sprintf("Bearer %s", accessToken)
|
authHeader := fmt.Sprintf("Bearer %s", accessToken)
|
||||||
|
|
||||||
data := rest.CurrentUserGuildsData{
|
data := rest.CurrentUserGuildsData{
|
||||||
Limit: 200,
|
Limit: 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), app.DefaultTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
guilds, err := rest.GetCurrentUserGuilds(ctx, authHeader, nil, data)
|
guilds, err := rest.GetCurrentUserGuilds(ctx, authHeader, nil, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := storeGuildsInDb(ctx, userId, guilds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userGuilds, err := getGuildIntersection(ctx, userId, guilds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
dtos := make([]GuildDto, 0, len(userGuilds))
|
||||||
|
for _, guild := range userGuilds {
|
||||||
|
guild := guild
|
||||||
|
|
||||||
|
group.Go(func() error {
|
||||||
|
permLevel, err := GetPermissionLevel(ctx, guild.Id, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
dtos = append(dtos, GuildDto{
|
||||||
|
Id: guild.Id,
|
||||||
|
Name: guild.Name,
|
||||||
|
Icon: guild.Icon,
|
||||||
|
PermissionLevel: permLevel,
|
||||||
|
})
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := group.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the guilds by name, but put the guilds with permission_level=0 last
|
||||||
|
slices.SortFunc(dtos, func(a, b GuildDto) int {
|
||||||
|
if a.PermissionLevel == 0 && b.PermissionLevel > 0 {
|
||||||
|
return 1
|
||||||
|
} else if a.PermissionLevel > 0 && b.PermissionLevel == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
return dtos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this function!
|
||||||
|
func storeGuildsInDb(ctx context.Context, userId uint64, guilds []guild.Guild) error {
|
||||||
var wrappedGuilds []database.UserGuild
|
var wrappedGuilds []database.UserGuild
|
||||||
|
|
||||||
// endpoint's partial guild doesn't includes ownerid
|
// endpoint's partial guild doesn't includes ownerid
|
||||||
@ -39,5 +106,61 @@ func LoadGuilds(accessToken string, userId uint64) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbclient.Client.UserGuilds.Set(context.Background(), userId, wrappedGuilds)
|
return dbclient.Client.UserGuilds.Set(ctx, userId, wrappedGuilds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGuildIntersection(ctx context.Context, userId uint64, userGuilds []guild.Guild) ([]guild.Guild, error) {
|
||||||
|
guildIds := make([]uint64, len(userGuilds))
|
||||||
|
for i, guild := range userGuilds {
|
||||||
|
guildIds[i] = guild.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict the set of guilds to guilds that the bot is also in
|
||||||
|
botGuilds, err := getExistingGuilds(ctx, guildIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
botGuildIds := collections.NewSet[uint64]()
|
||||||
|
for _, guildId := range botGuilds {
|
||||||
|
botGuildIds.Add(guildId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the intersection of the two sets
|
||||||
|
intersection := make([]guild.Guild, 0, len(botGuilds))
|
||||||
|
for _, guild := range userGuilds {
|
||||||
|
if botGuildIds.Contains(guild.Id) {
|
||||||
|
intersection = append(intersection, guild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExistingGuilds(ctx context.Context, userGuilds []uint64) ([]uint64, error) {
|
||||||
|
query := `SELECT "guild_id" from guilds WHERE "guild_id" = ANY($1);`
|
||||||
|
|
||||||
|
userGuildsArray := &pgtype.Int8Array{}
|
||||||
|
if err := userGuildsArray.Set(userGuilds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := cache.Instance.Query(ctx, query, userGuildsArray)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var existingGuilds []uint64
|
||||||
|
for rows.Next() {
|
||||||
|
var guildId uint64
|
||||||
|
if err := rows.Scan(&guildId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existingGuilds = append(existingGuilds, guildId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingGuilds, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user