Show servers with no permission

This commit is contained in:
rxdn 2024-06-21 21:00:50 +01:00
parent a933f2e124
commit b67adc8a4e
3 changed files with 126 additions and 100 deletions

View File

@ -1,19 +1,21 @@
package api package api
import ( import (
"cmp"
"context" "context"
"errors"
dbclient "github.com/TicketsBot/GoPanel/database" dbclient "github.com/TicketsBot/GoPanel/database"
"github.com/TicketsBot/GoPanel/rpc/cache" "github.com/TicketsBot/GoPanel/rpc/cache"
"github.com/TicketsBot/GoPanel/utils" "github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/common/collections"
"github.com/TicketsBot/common/permission" "github.com/TicketsBot/common/permission"
syncutils "github.com/TicketsBot/common/utils" syncutils "github.com/TicketsBot/common/utils"
"github.com/TicketsBot/database" "github.com/TicketsBot/database"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jackc/pgtype" "github.com/jackc/pgtype"
"github.com/rxdn/gdl/rest/request"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"sort" "slices"
"sync"
"time"
) )
type wrappedGuild struct { type wrappedGuild struct {
@ -23,101 +25,101 @@ type wrappedGuild struct {
PermissionLevel permission.PermissionLevel `json:"permission_level"` PermissionLevel permission.PermissionLevel `json:"permission_level"`
} }
func GetGuilds(ctx *gin.Context) { func GetGuilds(c *gin.Context) {
userId := ctx.Keys["userid"].(uint64) userId := c.Keys["userid"].(uint64)
// Get all guilds the user is in // Get the guilds that the user is in, that the bot is also in
guilds, err := dbclient.Client.UserGuilds.Get(userId) userGuilds, err := getGuildIntersection(userId)
if err != nil { if err != nil {
ctx.JSON(500, utils.ErrorJson(err)) c.JSON(500, utils.ErrorJson(err))
return
}
// Get the subset of guilds that the user is in that the bot is also in
guildIds := make([]uint64, len(guilds))
guildMap := make(map[uint64]database.UserGuild) // Make a map of all guilds for O(1) access
for i, guild := range guilds {
guildIds[i] = guild.GuildId
guildMap[guild.GuildId] = guild
}
botGuilds, err := getExistingGuilds(guildIds)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return return
} }
wg := syncutils.NewChannelWaitGroup() wg := syncutils.NewChannelWaitGroup()
wg.Add(len(botGuilds)) wg.Add(len(userGuilds))
group, _ := errgroup.WithContext(context.Background()) ctx, cancel := context.WithTimeout(c, time.Second*10)
ch := make(chan wrappedGuild) defer cancel()
for _, guildId := range botGuilds {
guildId := guildId group, ctx := errgroup.WithContext(ctx)
g := guildMap[guildId]
var mu sync.Mutex
guilds := make([]wrappedGuild, 0, len(userGuilds))
for _, guild := range userGuilds {
guild := guild
group.Go(func() error { group.Go(func() error {
defer wg.Done() defer wg.Done()
// Determine the user's permission level in this guild permLevel, err := utils.GetPermissionLevel(ctx, guild.GuildId, userId)
var permLevel permission.PermissionLevel if err != nil {
if g.Owner { return err
permLevel = permission.Admin
} else {
tmp, err := utils.GetPermissionLevel(context.Background(), g.GuildId, userId)
if err != nil {
// If a Discord error occurs, just skip the server
var restError request.RestError
if errors.As(err, &restError) {
return nil
} else {
return err
}
}
permLevel = tmp
} }
if permLevel >= permission.Support { mu.Lock()
wrapped := wrappedGuild{ guilds = append(guilds, wrappedGuild{
Id: g.GuildId, Id: guild.GuildId,
Name: g.Name, Name: guild.Name,
Icon: g.Icon, Icon: guild.Icon,
PermissionLevel: permLevel, PermissionLevel: permLevel,
} })
mu.Unlock()
ch <- wrapped
}
return nil return nil
}) })
} }
adminGuilds := make([]wrappedGuild, 0)
group.Go(func() error {
loop:
for {
select {
case <-wg.Wait():
break loop
case guild := <-ch:
adminGuilds = append(adminGuilds, guild)
}
}
return nil
})
if err := group.Wait(); err != nil { if err := group.Wait(); err != nil {
ctx.JSON(500, utils.ErrorJson(err)) c.JSON(500, utils.ErrorJson(err))
return return
} }
// sort // Sort the guilds by name, but put the guilds with permission_level=0 last
sort.Slice(adminGuilds, func(i, j int) bool { slices.SortFunc(guilds, func(a, b wrappedGuild) int {
return adminGuilds[i].Name < adminGuilds[j].Name 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)
}) })
ctx.JSON(200, adminGuilds) 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(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) { func getExistingGuilds(userGuilds []uint64) ([]uint64, error) {

View File

@ -1,23 +1,30 @@
<div class="guild-badge" on:click={goto(guild.id)}> <div class="guild-badge" on:click={goto(guild.id)} class:disabled={guild.permission_level === 0}>
<div class="guild-icon-bg"> <div class="guild-icon-bg">
{#if guild.icon === undefined || guild.icon === ""} {#if guild.icon === undefined || guild.icon === ""}
<i class="fas fa-question guild-icon-fa"></i> <i class="fas fa-question guild-icon-fa" class:disabled={guild.permission_level === 0}></i>
{:else} {:else}
<img class="guild-icon" src="{getIconUrl()}" alt="Guild Icon"/> <img class="guild-icon" src="{getIconUrl()}" alt="Guild Icon"
{/if} class:disabled={guild.permission_level === 0}/>
</div> {/if}
</div>
<div> <div class="text-wrapper" class:disabled={guild.permission_level === 0}>
<span class="guild-name"> <span class="guild-name">
{guild.name} {guild.name}
</span>
<span class="no-permission" class:disabled={guild.permission_level > 0}>
No permission
<Tooltip tip="You do not have permission to manage this server." top color="#121212">
<a href="https://docs.ticketsbot.net/miscellaneous/dashboard-no-permission" target="_blank">
<i class="fas fa-circle-question form-label tooltip-icon"></i>
</a>
</Tooltip>
</span> </span>
</div> </div>
</div> </div>
<script> <script>
import axios from 'axios'; import Tooltip from "svelte-tooltip";
import {API_URL} from "../js/constants";
import {notifyError} from "../js/util";
export let guild; export let guild;
@ -40,19 +47,11 @@
async function goto(guildId) { async function goto(guildId) {
if (guild.permission_level === 2) { if (guild.permission_level === 2) {
window.location.href = `/manage/${guildId}/settings`; window.location.href = `/manage/${guildId}/settings`;
} else { } else if (guild.permission_level === 1) {
window.location.href = `/manage/${guildId}/transcripts`; window.location.href = `/manage/${guildId}/transcripts`;
} } else {
}
async function getPermissionLevel(guildId) {
const res = await axios.get(`${API_URL}/user/permissionlevel?guild=${guildId}`);
if (res.status !== 200 || !res.data.success) {
notifyError(res.data.error);
return; return;
} }
return res.data.permission_level;
} }
</script> </script>
@ -70,6 +69,10 @@
cursor: pointer; cursor: pointer;
} }
.guild-badge.disabled {
cursor: default;
}
@media (max-width: 950px) { @media (max-width: 950px) {
:global(.guild-badge) { :global(.guild-badge) {
width: 100%; width: 100%;
@ -102,6 +105,27 @@
:global(.guild-name) { :global(.guild-name) {
color: white !important; color: white !important;
}
.text-wrapper.disabled > .guild-name {
opacity: 45%;
}
.guild-icon-bg > *.disabled {
opacity: 25%;
}
.text-wrapper {
display: flex;
flex-direction: column;
padding-left: 10px; padding-left: 10px;
} }
.text-wrapper > .no-permission {
opacity: 75%;
}
.text-wrapper > .no-permission.disabled {
visibility: hidden;
}
</style> </style>

View File

@ -3,7 +3,7 @@
<i class="fas fa-plus fa-2x guild-icon-fa"></i> <i class="fas fa-plus fa-2x guild-icon-fa"></i>
</div> </div>
<div> <div style="padding-left: 10px">
<span class="guild-name">Invite to your server</span> <span class="guild-name">Invite to your server</span>
</div> </div>
</div> </div>