mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +00:00
feat(api): add get-picker-data; refactor fully away from old gRPC datatypes
This commit is contained in:
parent
823760dc2f
commit
0b384bfe5c
22 changed files with 359 additions and 151 deletions
|
@ -108,4 +108,4 @@
|
|||
"typescript": "^4.1.3",
|
||||
"webpack": "4.33.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const server = http.createServer((req, res) => {
|
|||
}
|
||||
res.statusCode = response.status;
|
||||
loggedStatus = String(response.status);
|
||||
Object.entries(response.headers).forEach(([k, v]) => res.setHeader(k, v));
|
||||
response.headers.forEach((value, key) => res.setHeader(key, value));
|
||||
res.end(response.body);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
78
src/backend-worker/handlers/get-picker-data.ts
Normal file
78
src/backend-worker/handlers/get-picker-data.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
DiscordUser,
|
||||
GuildSlug,
|
||||
PresentableGuild,
|
||||
SessionData,
|
||||
} from 'roleypoly/common/types';
|
||||
import { respond, withSession } from '../utils/api-tools';
|
||||
import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild';
|
||||
|
||||
const fail = respond(
|
||||
{
|
||||
error: 'guild not found',
|
||||
},
|
||||
{
|
||||
status: 404,
|
||||
}
|
||||
);
|
||||
|
||||
export const GetPickerData = withSession(
|
||||
(session?: SessionData) => async (request: Request): Promise<Response> => {
|
||||
const url = new URL(request.url);
|
||||
const [, , guildID] = url.pathname.split('/');
|
||||
|
||||
if (!guildID) {
|
||||
return respond(
|
||||
{
|
||||
error: 'missing guild id',
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const { id: userID } = session?.user as DiscordUser;
|
||||
const guilds = session?.guilds as GuildSlug[];
|
||||
|
||||
// Save a Discord API request by checking if this user is a member by session first
|
||||
const checkGuild = guilds.find((guild) => guild.id === guildID);
|
||||
if (!checkGuild) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const guild = await getGuild(guildID, {
|
||||
skipCachePull: url.searchParams.has('__no_cache'),
|
||||
});
|
||||
if (!guild) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const memberRolesP = getGuildMemberRoles({
|
||||
serverID: guildID,
|
||||
userID,
|
||||
});
|
||||
|
||||
const guildDataP = getGuildData(guildID);
|
||||
|
||||
const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]);
|
||||
if (!memberRoles) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const presentableGuild: PresentableGuild = {
|
||||
id: guildID,
|
||||
guild: checkGuild,
|
||||
roles: guild.roles,
|
||||
member: {
|
||||
roles: memberRoles,
|
||||
},
|
||||
data: guildData,
|
||||
};
|
||||
|
||||
return respond(presentableGuild);
|
||||
},
|
||||
{
|
||||
mustAuthenticate: true,
|
||||
}
|
||||
);
|
|
@ -120,11 +120,15 @@ const getGuilds = async (accessToken: string) => {
|
|||
'Bearer'
|
||||
);
|
||||
|
||||
if (!guilds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const guildSlugs = guilds.map<GuildSlug>((guild) => ({
|
||||
id: guild.id,
|
||||
name: guild.name,
|
||||
icon: guild.icon,
|
||||
permissionLevel: parsePermissions(guild.permissions, guild.owner),
|
||||
permissionLevel: parsePermissions(BigInt(guild.permissions), guild.owner),
|
||||
}));
|
||||
|
||||
return guildSlugs;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { BotJoin } from './handlers/bot-join';
|
||||
import { GetPickerData } from './handlers/get-picker-data';
|
||||
import { GetSession } from './handlers/get-session';
|
||||
import { GetSlug } from './handlers/get-slug';
|
||||
import { LoginBounce } from './handlers/login-bounce';
|
||||
|
@ -16,6 +17,7 @@ router.add('GET', 'login-bounce', LoginBounce);
|
|||
router.add('GET', 'login-callback', LoginCallback);
|
||||
router.add('GET', 'get-session', GetSession);
|
||||
router.add('GET', 'get-slug', GetSlug);
|
||||
router.add('GET', 'get-picker-data', GetPickerData);
|
||||
router.add('GET', 'x-headers', (request) => {
|
||||
const headers: { [x: string]: string } = {};
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export const resolveFailures = (
|
|||
};
|
||||
|
||||
export const parsePermissions = (
|
||||
permissions: number,
|
||||
permissions: bigint,
|
||||
owner: boolean = false
|
||||
): UserGuildPermissions => {
|
||||
if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) {
|
||||
|
@ -56,6 +56,9 @@ export const getSessionID = (request: Request): { type: string; id: string } | n
|
|||
return { type, id };
|
||||
};
|
||||
|
||||
const userAgent =
|
||||
'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)';
|
||||
|
||||
export const discordFetch = async <T>(
|
||||
url: string,
|
||||
auth: string,
|
||||
|
@ -64,8 +67,7 @@ export const discordFetch = async <T>(
|
|||
const response = await fetch('https://discord.com/api/v8' + url, {
|
||||
headers: {
|
||||
authorization: `${authType} ${auth}`,
|
||||
'user-agent':
|
||||
'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)',
|
||||
'user-agent': userAgent,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -81,12 +83,17 @@ export const cacheLayer = <Identity, Data>(
|
|||
keyFactory: (identity: Identity) => string,
|
||||
missHandler: (identity: Identity) => Promise<Data | null>,
|
||||
ttlSeconds?: number
|
||||
) => async (identity: Identity): Promise<Data | null> => {
|
||||
) => async (
|
||||
identity: Identity,
|
||||
options: { skipCachePull?: boolean } = {}
|
||||
): Promise<Data | null> => {
|
||||
const key = keyFactory(identity);
|
||||
|
||||
const value = await kv.get<Data>(key);
|
||||
if (value) {
|
||||
return value;
|
||||
if (!options.skipCachePull) {
|
||||
const value = await kv.get<Data>(key);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackValue = await missHandler(identity);
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import { Guild, Member, Role, RoleSafety } from 'roleypoly/common/types';
|
||||
import {
|
||||
Features,
|
||||
Guild,
|
||||
GuildData as GuildDataT,
|
||||
OwnRoleInfo,
|
||||
Role,
|
||||
RoleSafety,
|
||||
} from 'roleypoly/common/types';
|
||||
import { evaluatePermission, permissions } from 'roleypoly/common/utils/hasPermission';
|
||||
import { cacheLayer, discordFetch } from './api-tools';
|
||||
import { botToken } from './config';
|
||||
import { Guilds } from './kv';
|
||||
import { botClientID, botToken } from './config';
|
||||
import { GuildData, Guilds } from './kv';
|
||||
|
||||
type APIGuild = {
|
||||
// Only relevant stuff
|
||||
|
@ -30,24 +38,112 @@ export const getGuild = cacheLayer(
|
|||
return null;
|
||||
}
|
||||
|
||||
const botMemberRoles =
|
||||
(await getGuildMemberRoles({
|
||||
serverID: id,
|
||||
userID: botClientID,
|
||||
})) || [];
|
||||
|
||||
const highestRolePosition = botMemberRoles.reduce<number>((highest, roleID) => {
|
||||
const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID);
|
||||
if (!role) {
|
||||
return highest;
|
||||
}
|
||||
|
||||
// If highest is a bigger number, it stays the highest.
|
||||
if (highest > role.position) {
|
||||
return highest;
|
||||
}
|
||||
|
||||
return role.position;
|
||||
}, guildRaw.roles.length - 1);
|
||||
|
||||
const roles = guildRaw.roles.map<Role>((role) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
color: role.color,
|
||||
managed: role.managed,
|
||||
position: role.position,
|
||||
permissions: role.permissions,
|
||||
safety: calculateRoleSafety(role, highestRolePosition),
|
||||
}));
|
||||
|
||||
// Filters the raw guild data into data we actually want
|
||||
const guild: Guild = {
|
||||
const guild: Guild & OwnRoleInfo = {
|
||||
id: guildRaw.id,
|
||||
name: guildRaw.name,
|
||||
icon: guildRaw.icon,
|
||||
roles: guildRaw.roles.map<Role>((role) => ({
|
||||
...role,
|
||||
safety: RoleSafety.SAFE, // TODO: calculate safety
|
||||
})),
|
||||
roles,
|
||||
highestRolePosition,
|
||||
};
|
||||
|
||||
return guild;
|
||||
}
|
||||
},
|
||||
60 * 60 * 2 // 2 hour TTL
|
||||
);
|
||||
|
||||
export const getGuildMember = async (
|
||||
serverID: string,
|
||||
userID: string
|
||||
): Promise<Member> => {
|
||||
return {} as any;
|
||||
type GuildMemberIdentity = {
|
||||
serverID: string;
|
||||
userID: string;
|
||||
};
|
||||
|
||||
type APIMember = {
|
||||
// Only relevant stuff, again.
|
||||
roles: string[];
|
||||
};
|
||||
|
||||
export const getGuildMemberRoles = cacheLayer<GuildMemberIdentity, Role['id'][]>(
|
||||
Guilds,
|
||||
({ serverID, userID }) => `guilds/${serverID}/members/${userID}`,
|
||||
async ({ serverID, userID }) => {
|
||||
const discordMember = await discordFetch<APIMember>(
|
||||
`/guilds/${serverID}/members/${userID}`,
|
||||
botToken,
|
||||
'Bot'
|
||||
);
|
||||
|
||||
if (!discordMember) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return discordMember.roles;
|
||||
},
|
||||
60 * 5 // 5 minute TTL
|
||||
);
|
||||
|
||||
export const getGuildData = async (id: string): Promise<GuildDataT> => {
|
||||
const guildData = await GuildData.get<GuildDataT>(id);
|
||||
|
||||
if (!guildData) {
|
||||
return {
|
||||
id,
|
||||
message: '',
|
||||
categories: [],
|
||||
features: Features.None,
|
||||
};
|
||||
}
|
||||
|
||||
return guildData;
|
||||
};
|
||||
|
||||
const calculateRoleSafety = (role: Role | APIRole, highestBotRolePosition: number) => {
|
||||
let safety = RoleSafety.Safe;
|
||||
|
||||
if (role.managed) {
|
||||
safety |= RoleSafety.ManagedRole;
|
||||
}
|
||||
|
||||
if (role.position > highestBotRolePosition) {
|
||||
safety |= RoleSafety.HigherThanBot;
|
||||
}
|
||||
|
||||
const permBigInt = BigInt(role.permissions);
|
||||
if (
|
||||
evaluatePermission(permBigInt, permissions.ADMINISTRATOR) ||
|
||||
evaluatePermission(permBigInt, permissions.MANAGE_ROLES)
|
||||
) {
|
||||
safety |= RoleSafety.DangerousPermissions;
|
||||
}
|
||||
|
||||
return safety;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
export enum CategoryType {
|
||||
SINGLE = 0,
|
||||
MULTI,
|
||||
Single = 0,
|
||||
Multi,
|
||||
}
|
||||
|
||||
export type Category = {
|
||||
id: string;
|
||||
name: string;
|
||||
rolesList: string[];
|
||||
roles: string[];
|
||||
hidden: boolean;
|
||||
type: CategoryType;
|
||||
position: number;
|
||||
|
|
|
@ -9,34 +9,34 @@ export type Guild = {
|
|||
roles: Role[];
|
||||
};
|
||||
|
||||
export type GuildRoles = {
|
||||
id: string;
|
||||
rolesList: Role[];
|
||||
};
|
||||
export enum Features {
|
||||
None,
|
||||
Preview = None,
|
||||
}
|
||||
|
||||
export type GuildData = {
|
||||
id: string;
|
||||
message: string;
|
||||
categoriesList: Category[];
|
||||
entitlementsList: string[];
|
||||
categories: Category[];
|
||||
features: Features;
|
||||
};
|
||||
|
||||
export type PresentableGuild = {
|
||||
id: string;
|
||||
guild: Guild;
|
||||
guild: GuildSlug;
|
||||
member: Member;
|
||||
data: GuildData;
|
||||
roles: GuildRoles;
|
||||
roles: Role[];
|
||||
};
|
||||
|
||||
export type GuildEnumeration = {
|
||||
guildsList: PresentableGuild[];
|
||||
guilds: PresentableGuild[];
|
||||
};
|
||||
|
||||
export enum UserGuildPermissions {
|
||||
User,
|
||||
Manager,
|
||||
Admin,
|
||||
Manager = 1 << 1,
|
||||
Admin = 1 << 2,
|
||||
}
|
||||
|
||||
export type GuildSlug = {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
export enum RoleSafety {
|
||||
SAFE = 0,
|
||||
HIGHERTHANBOT,
|
||||
DANGEROUSPERMISSIONS,
|
||||
Safe = 0,
|
||||
HigherThanBot = 1 << 1,
|
||||
DangerousPermissions = 1 << 2,
|
||||
ManagedRole = 1 << 3,
|
||||
}
|
||||
|
||||
export type Role = {
|
||||
|
@ -14,3 +15,7 @@ export type Role = {
|
|||
/** Permissions is should be used as a BigInt, NOT a number. */
|
||||
permissions: string;
|
||||
};
|
||||
|
||||
export type OwnRoleInfo = {
|
||||
highestRolePosition: number;
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@ export type DiscordUser = {
|
|||
};
|
||||
|
||||
export type Member = {
|
||||
guildid: string;
|
||||
rolesList: string[];
|
||||
nick: string;
|
||||
user: DiscordUser;
|
||||
guildid?: string;
|
||||
roles: string[];
|
||||
nick?: string;
|
||||
user?: DiscordUser;
|
||||
};
|
||||
|
||||
export type RoleypolyUser = {
|
||||
|
|
|
@ -2,10 +2,10 @@ import {
|
|||
Category,
|
||||
CategoryType,
|
||||
DiscordUser,
|
||||
Features,
|
||||
Guild,
|
||||
GuildData,
|
||||
GuildEnumeration,
|
||||
GuildRoles,
|
||||
GuildSlug,
|
||||
Member,
|
||||
Role,
|
||||
|
@ -21,7 +21,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0xffc0cb,
|
||||
position: 1,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'bbb',
|
||||
|
@ -30,7 +30,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0xc0ebff,
|
||||
position: 2,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'ccc',
|
||||
|
@ -39,7 +39,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0xc0ffd5,
|
||||
position: 3,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'ddd',
|
||||
|
@ -48,7 +48,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0xff0000,
|
||||
position: 4,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'eee',
|
||||
|
@ -57,7 +57,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0x000000,
|
||||
position: 5,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'fff',
|
||||
|
@ -66,7 +66,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0x1,
|
||||
position: 6,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'unsafe1',
|
||||
|
@ -75,7 +75,7 @@ export const roleCategory: Role[] = [
|
|||
color: 0xff0088,
|
||||
position: 7,
|
||||
managed: false,
|
||||
safety: RoleSafety.HIGHERTHANBOT,
|
||||
safety: RoleSafety.HigherThanBot,
|
||||
},
|
||||
{
|
||||
id: 'unsafe2',
|
||||
|
@ -84,16 +84,16 @@ export const roleCategory: Role[] = [
|
|||
color: 0x00ff88,
|
||||
position: 8,
|
||||
managed: false,
|
||||
safety: RoleSafety.DANGEROUSPERMISSIONS,
|
||||
safety: RoleSafety.DangerousPermissions,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCategory: Category = {
|
||||
id: 'aaa',
|
||||
name: 'Mock',
|
||||
rolesList: roleCategory.map((x) => x.id),
|
||||
roles: roleCategory.map((x) => x.id),
|
||||
hidden: false,
|
||||
type: CategoryType.MULTI,
|
||||
type: CategoryType.Multi,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
|
@ -105,7 +105,7 @@ export const roleCategory2: Role[] = [
|
|||
color: 0xff0000,
|
||||
position: 9,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
{
|
||||
id: 'eee2',
|
||||
|
@ -114,24 +114,19 @@ export const roleCategory2: Role[] = [
|
|||
color: 0x00ff00,
|
||||
position: 10,
|
||||
managed: false,
|
||||
safety: RoleSafety.SAFE,
|
||||
safety: RoleSafety.Safe,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCategorySingle: Category = {
|
||||
id: 'bbb',
|
||||
name: 'Mock Single 岡野',
|
||||
rolesList: roleCategory2.map((x) => x.id),
|
||||
roles: roleCategory2.map((x) => x.id),
|
||||
hidden: false,
|
||||
type: CategoryType.SINGLE,
|
||||
type: CategoryType.Single,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
export const guildRoles: GuildRoles = {
|
||||
id: 'aaa',
|
||||
rolesList: [...roleCategory, ...roleCategory2],
|
||||
};
|
||||
|
||||
export const roleWikiData = {
|
||||
aaa: 'Typically used by feminine-identifying people',
|
||||
bbb: 'Typically used by masculine-identifying people',
|
||||
|
@ -145,33 +140,38 @@ export const guild: Guild = {
|
|||
roles: [],
|
||||
};
|
||||
|
||||
export const guildMap: { [x: string]: Guild } = {
|
||||
'emoji megaporium': guild,
|
||||
export const guildMap: { [x: string]: GuildSlug } = {
|
||||
'emoji megaporium': {
|
||||
name: guild.name,
|
||||
id: guild.id,
|
||||
permissionLevel: 0,
|
||||
icon: guild.icon,
|
||||
},
|
||||
Roleypoly: {
|
||||
name: 'Roleypoly',
|
||||
id: '203493697696956418',
|
||||
permissionLevel: 0,
|
||||
icon: 'ff08d36f5aee1ff48f8377b65d031ab0',
|
||||
roles: [],
|
||||
},
|
||||
'chamber of secrets': {
|
||||
name: 'chamber of secrets',
|
||||
id: 'aaa',
|
||||
permissionLevel: 0,
|
||||
icon: '',
|
||||
roles: [],
|
||||
},
|
||||
Eclipse: {
|
||||
name: 'Eclipse',
|
||||
id: '408821059161423873',
|
||||
permissionLevel: 0,
|
||||
icon: '49dfdd8b2456e2977e80a8b577b19c0d',
|
||||
roles: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const guildData: GuildData = {
|
||||
id: 'aaa',
|
||||
message: 'henlo worl!!',
|
||||
categoriesList: [mockCategory, mockCategorySingle],
|
||||
entitlementsList: [],
|
||||
categories: [mockCategory, mockCategorySingle],
|
||||
features: Features.None,
|
||||
};
|
||||
|
||||
export const user: DiscordUser = {
|
||||
|
@ -184,7 +184,7 @@ export const user: DiscordUser = {
|
|||
|
||||
export const member: Member = {
|
||||
guildid: 'aaa',
|
||||
rolesList: ['aaa', 'eee', 'unsafe2', 'ddd2'],
|
||||
roles: ['aaa', 'eee', 'unsafe2', 'ddd2'],
|
||||
nick: 'okano cat',
|
||||
user: user,
|
||||
};
|
||||
|
@ -194,42 +194,42 @@ export const rpUser: RoleypolyUser = {
|
|||
};
|
||||
|
||||
export const guildEnum: GuildEnumeration = {
|
||||
guildsList: [
|
||||
guilds: [
|
||||
{
|
||||
id: 'aaa',
|
||||
guild: guildMap['emoji megaporium'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
},
|
||||
{
|
||||
id: 'bbb',
|
||||
guild: guildMap['Roleypoly'],
|
||||
member: {
|
||||
...member,
|
||||
rolesList: ['unsafe2'],
|
||||
roles: ['unsafe2'],
|
||||
},
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
},
|
||||
{
|
||||
id: 'ccc',
|
||||
guild: guildMap['chamber of secrets'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
},
|
||||
{
|
||||
id: 'ddd',
|
||||
guild: guildMap['Eclipse'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mastheadSlugs: GuildSlug[] = guildEnum.guildsList.map<GuildSlug>(
|
||||
export const mastheadSlugs: GuildSlug[] = guildEnum.guilds.map<GuildSlug>(
|
||||
(guild, idx) => ({
|
||||
id: guild.guild.id,
|
||||
name: guild.guild.name,
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
import { Role } from 'roleypoly/common/types';
|
||||
import { guildRoles } from 'roleypoly/common/types/storyData';
|
||||
import { hasPermission, hasPermissionOrAdmin, permissions } from './hasPermission';
|
||||
import { roleCategory } from 'roleypoly/common/types/storyData';
|
||||
import { hasPermission, hasPermissionOrAdmin } from './hasPermission';
|
||||
|
||||
export const permissions = {
|
||||
KICK_MEMBERS: BigInt(0x2),
|
||||
BAN_MEMBERS: BigInt(0x4),
|
||||
ADMINISTRATOR: BigInt(0x8),
|
||||
SPEAK: BigInt(0x200000),
|
||||
CHANGE_NICKNAME: BigInt(0x4000000),
|
||||
MANAGE_ROLES: BigInt(0x10000000),
|
||||
};
|
||||
|
||||
const roles: Role[] = [
|
||||
{
|
||||
...guildRoles.rolesList[0],
|
||||
...roleCategory[0],
|
||||
permissions: String(permissions.ADMINISTRATOR),
|
||||
},
|
||||
{
|
||||
...guildRoles.rolesList[0],
|
||||
...roleCategory[0],
|
||||
permissions: String(
|
||||
permissions.SPEAK | permissions.BAN_MEMBERS | permissions.CHANGE_NICKNAME
|
||||
),
|
||||
},
|
||||
{
|
||||
...guildRoles.rolesList[0],
|
||||
...roleCategory[0],
|
||||
permissions: String(permissions.BAN_MEMBERS),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -19,35 +19,37 @@ export const hasPermissionOrAdmin = (roles: Role[], permission: bigint): boolean
|
|||
hasPermission(roles, permission | permissions.ADMINISTRATOR);
|
||||
|
||||
export const permissions = {
|
||||
CREATE_INSTANT_INVITE: BigInt(0x1),
|
||||
KICK_MEMBERS: BigInt(0x2),
|
||||
BAN_MEMBERS: BigInt(0x4),
|
||||
// IMPORTANT: Only uncomment what's actually used. All are left for convenience.
|
||||
|
||||
// CREATE_INSTANT_INVITE: BigInt(0x1),
|
||||
// KICK_MEMBERS: BigInt(0x2),
|
||||
// BAN_MEMBERS: BigInt(0x4),
|
||||
ADMINISTRATOR: BigInt(0x8),
|
||||
MANAGE_CHANNELS: BigInt(0x10),
|
||||
MANAGE_GUILD: BigInt(0x20),
|
||||
ADD_REACTIONS: BigInt(0x40),
|
||||
VIEW_AUDIT_LOG: BigInt(0x80),
|
||||
VIEW_CHANNEL: BigInt(0x400),
|
||||
SEND_MESSAGES: BigInt(0x800),
|
||||
SEND_TTS_MESSAGES: BigInt(0x1000),
|
||||
MANAGE_MESSAGES: BigInt(0x2000),
|
||||
EMBED_LINKS: BigInt(0x4000),
|
||||
ATTACH_FILES: BigInt(0x8000),
|
||||
READ_MESSAGE_HISTORY: BigInt(0x10000),
|
||||
MENTION_EVERYONE: BigInt(0x20000),
|
||||
USE_EXTERNAL_EMOJIS: BigInt(0x40000),
|
||||
VIEW_GUILD_INSIGHTS: BigInt(0x80000),
|
||||
CONNECT: BigInt(0x100000),
|
||||
SPEAK: BigInt(0x200000),
|
||||
MUTE_MEMBERS: BigInt(0x400000),
|
||||
DEAFEN_MEMBERS: BigInt(0x800000),
|
||||
MOVE_MEMBERS: BigInt(0x1000000),
|
||||
USE_VAD: BigInt(0x2000000),
|
||||
PRIORITY_SPEAKER: BigInt(0x100),
|
||||
STREAM: BigInt(0x200),
|
||||
CHANGE_NICKNAME: BigInt(0x4000000),
|
||||
MANAGE_NICKNAMES: BigInt(0x8000000),
|
||||
// MANAGE_CHANNELS: BigInt(0x10),
|
||||
// MANAGE_GUILD: BigInt(0x20),
|
||||
// ADD_REACTIONS: BigInt(0x40),
|
||||
// VIEW_AUDIT_LOG: BigInt(0x80),
|
||||
// VIEW_CHANNEL: BigInt(0x400),
|
||||
// SEND_MESSAGES: BigInt(0x800),
|
||||
// SEND_TTS_MESSAGES: BigInt(0x1000),
|
||||
// MANAGE_MESSAGES: BigInt(0x2000),
|
||||
// EMBED_LINKS: BigInt(0x4000),
|
||||
// ATTACH_FILES: BigInt(0x8000),
|
||||
// READ_MESSAGE_HISTORY: BigInt(0x10000),
|
||||
// MENTION_EVERYONE: BigInt(0x20000),
|
||||
// USE_EXTERNAL_EMOJIS: BigInt(0x40000),
|
||||
// VIEW_GUILD_INSIGHTS: BigInt(0x80000),
|
||||
// CONNECT: BigInt(0x100000),
|
||||
// SPEAK: BigInt(0x200000),
|
||||
// MUTE_MEMBERS: BigInt(0x400000),
|
||||
// DEAFEN_MEMBERS: BigInt(0x800000),
|
||||
// MOVE_MEMBERS: BigInt(0x1000000),
|
||||
// USE_VAD: BigInt(0x2000000),
|
||||
// PRIORITY_SPEAKER: BigInt(0x100),
|
||||
// STREAM: BigInt(0x200),
|
||||
// CHANGE_NICKNAME: BigInt(0x4000000),
|
||||
// MANAGE_NICKNAMES: BigInt(0x8000000),
|
||||
MANAGE_ROLES: BigInt(0x10000000),
|
||||
MANAGE_WEBHOOKS: BigInt(0x20000000),
|
||||
MANAGE_EMOJIS: BigInt(0x40000000),
|
||||
// MANAGE_WEBHOOKS: BigInt(0x20000000),
|
||||
// MANAGE_EMOJIS: BigInt(0x40000000),
|
||||
};
|
||||
|
|
|
@ -65,9 +65,9 @@ export const Role = (props: Props) => {
|
|||
|
||||
const disabledReason = (role: RPCRole) => {
|
||||
switch (role.safety) {
|
||||
case RoleSafety.HIGHERTHANBOT:
|
||||
case RoleSafety.HigherThanBot:
|
||||
return `This role is above Roleypoly's own role.`;
|
||||
case RoleSafety.DANGEROUSPERMISSIONS:
|
||||
case RoleSafety.DangerousPermissions:
|
||||
const rolePermissions = BigInt(role.permissions);
|
||||
let permissionHits: string[] = [];
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const typeEnumToSwitch = (typeData: CategoryType) => {
|
||||
if (typeData === CategoryType.SINGLE) {
|
||||
if (typeData === CategoryType.Single) {
|
||||
return 'Single';
|
||||
} else {
|
||||
return 'Multiple';
|
||||
|
@ -28,9 +28,9 @@ const typeEnumToSwitch = (typeData: CategoryType) => {
|
|||
|
||||
const switchToTypeEnum = (typeData: 'Single' | 'Multiple') => {
|
||||
if (typeData === 'Single') {
|
||||
return CategoryType.SINGLE;
|
||||
return CategoryType.Single;
|
||||
} else {
|
||||
return CategoryType.MULTI;
|
||||
return CategoryType.Multi;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -53,14 +53,14 @@ export const EditorCategory = (props: Props) => {
|
|||
updateSearchTerm('');
|
||||
props.onChange({
|
||||
...props.category,
|
||||
rolesList: [...props.category.rolesList, role.id],
|
||||
roles: [...props.category.roles, role.id],
|
||||
});
|
||||
};
|
||||
|
||||
const handleRoleDeselect = (role: RoleType) => () => {
|
||||
props.onChange({
|
||||
...props.category,
|
||||
rolesList: props.category.rolesList.filter((x) => x !== role.id),
|
||||
roles: props.category.roles.filter((x) => x !== role.id),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -122,7 +122,7 @@ export const EditorCategory = (props: Props) => {
|
|||
onChange={(x) => updateSearchTerm(x.target.value)}
|
||||
/>
|
||||
<RoleContainer>
|
||||
{props.category.rolesList.map((id) => {
|
||||
{props.category.roles.map((id) => {
|
||||
const role = props.guildRoles.find((x) => x.id === id);
|
||||
if (!role) {
|
||||
return <></>;
|
||||
|
|
|
@ -56,7 +56,7 @@ export const PickerCategory = (props: CategoryProps) => (
|
|||
role={role}
|
||||
selected={props.selectedRoles.includes(role.id)}
|
||||
onClick={props.onChange(role)}
|
||||
disabled={role.safety !== RoleSafety.SAFE}
|
||||
disabled={role.safety !== RoleSafety.Safe}
|
||||
tooltipId={props.category.id}
|
||||
/>
|
||||
</Container>
|
||||
|
|
|
@ -17,12 +17,12 @@ export const EditorShell = (props: Props) => (
|
|||
|
||||
const RolesTab = (props: Props) => (
|
||||
<div>
|
||||
{props.guild.data.categoriesList.map((category, idx) => (
|
||||
{props.guild.data.categories.map((category, idx) => (
|
||||
<CategoryContainer key={idx}>
|
||||
<EditorCategory
|
||||
category={category}
|
||||
uncategorizedRoles={[]}
|
||||
guildRoles={props.guild.roles.rolesList}
|
||||
guildRoles={props.guild.roles}
|
||||
onChange={(x) => console.log(x)}
|
||||
/>
|
||||
</CategoryContainer>
|
||||
|
|
|
@ -8,9 +8,10 @@ import * as React from 'react';
|
|||
import {
|
||||
guild,
|
||||
guildData,
|
||||
guildRoles,
|
||||
member,
|
||||
mockCategorySingle,
|
||||
roleCategory,
|
||||
roleCategory2,
|
||||
} from 'roleypoly/common/types/storyData';
|
||||
import { Role } from 'roleypoly/design-system/atoms/role';
|
||||
import { PickerCategory } from 'roleypoly/design-system/molecules/picker-category';
|
||||
|
@ -19,9 +20,9 @@ import { RolePicker, RolePickerProps } from './RolePicker';
|
|||
|
||||
it('unselects the rest of a category in single mode', () => {
|
||||
const props: RolePickerProps = {
|
||||
guildData: { ...guildData, categoriesList: [mockCategorySingle] },
|
||||
member: { ...member, rolesList: [] },
|
||||
roles: guildRoles,
|
||||
guildData: { ...guildData, categories: [mockCategorySingle] },
|
||||
member: { ...member, roles: [] },
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
guild: guild,
|
||||
onSubmit: jest.fn(),
|
||||
editable: false,
|
||||
|
@ -34,10 +35,10 @@ it('unselects the rest of a category in single mode', () => {
|
|||
roles.first().props().onClick?.(true);
|
||||
|
||||
view.find(ResetSubmit).props().onSubmit();
|
||||
expect(props.onSubmit).toBeCalledWith([mockCategorySingle.rolesList[0]]);
|
||||
expect(props.onSubmit).toBeCalledWith([mockCategorySingle.roles[0]]);
|
||||
|
||||
roles.last().props().onClick?.(true);
|
||||
roles.at(1).props().onClick?.(true);
|
||||
|
||||
view.find(ResetSubmit).props().onSubmit();
|
||||
expect(props.onSubmit).toBeCalledWith([mockCategorySingle.rolesList[1]]);
|
||||
expect(props.onSubmit).toBeCalledWith([mockCategorySingle.roles[1]]);
|
||||
});
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { guild, guildData, guildRoles, member } from 'roleypoly/common/types/storyData';
|
||||
import {
|
||||
guild,
|
||||
guildData,
|
||||
member,
|
||||
roleCategory,
|
||||
roleCategory2,
|
||||
} from 'roleypoly/common/types/storyData';
|
||||
import { RolePicker, RolePickerProps } from './RolePicker';
|
||||
|
||||
const props: Partial<RolePickerProps> = {
|
||||
guildData: guildData,
|
||||
member: member,
|
||||
guild: guild,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
editable: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
CategoryType,
|
||||
Guild,
|
||||
GuildData,
|
||||
GuildRoles,
|
||||
Member,
|
||||
Role,
|
||||
} from 'roleypoly/common/types';
|
||||
|
@ -27,7 +26,7 @@ export type RolePickerProps = {
|
|||
guild: Guild;
|
||||
guildData: GuildData;
|
||||
member: Member;
|
||||
roles: GuildRoles;
|
||||
roles: Role[];
|
||||
onSubmit: (selectedRoles: string[]) => void;
|
||||
editable: boolean;
|
||||
};
|
||||
|
@ -43,15 +42,15 @@ const arrayMatches = (a: any[], b: any[]) => {
|
|||
|
||||
export const RolePicker = (props: RolePickerProps) => {
|
||||
const [selectedRoles, updateSelectedRoles] = React.useState<string[]>(
|
||||
props.member.rolesList
|
||||
props.member.roles
|
||||
);
|
||||
|
||||
const handleChange = (category: Category) => (role: Role) => (newState: boolean) => {
|
||||
if (category.type === CategoryType.SINGLE) {
|
||||
if (category.type === CategoryType.Single) {
|
||||
updateSelectedRoles(
|
||||
newState === true
|
||||
? [
|
||||
...selectedRoles.filter((x) => !category.rolesList.includes(x)),
|
||||
...selectedRoles.filter((x) => !category.roles.includes(x)),
|
||||
role.id,
|
||||
]
|
||||
: selectedRoles.filter((x) => x !== role.id)
|
||||
|
@ -79,31 +78,29 @@ export const RolePicker = (props: RolePickerProps) => {
|
|||
</>
|
||||
)}
|
||||
|
||||
{props.guildData.categoriesList.length !== 0 ? (
|
||||
{props.guildData.categories.length !== 0 ? (
|
||||
<>
|
||||
<div>
|
||||
{props.guildData.categoriesList.map((category, idx) => (
|
||||
{props.guildData.categories.map((category, idx) => (
|
||||
<CategoryContainer key={idx}>
|
||||
<PickerCategory
|
||||
key={idx}
|
||||
category={category}
|
||||
title={category.name}
|
||||
selectedRoles={selectedRoles.filter((roleId) =>
|
||||
category.rolesList.includes(roleId)
|
||||
category.roles.includes(roleId)
|
||||
)}
|
||||
roles={
|
||||
category.rolesList
|
||||
category.roles
|
||||
.map((role) =>
|
||||
props.roles.rolesList.find(
|
||||
(r) => r.id === role
|
||||
)
|
||||
props.roles.find((r) => r.id === role)
|
||||
)
|
||||
.filter((r) => r !== undefined) as Role[]
|
||||
}
|
||||
onChange={handleChange(category)}
|
||||
wikiMode={false}
|
||||
type={
|
||||
category.type === CategoryType.SINGLE
|
||||
category.type === CategoryType.Single
|
||||
? 'single'
|
||||
: 'multi'
|
||||
}
|
||||
|
@ -112,12 +109,12 @@ export const RolePicker = (props: RolePickerProps) => {
|
|||
))}
|
||||
</div>
|
||||
<FaderOpacity
|
||||
isVisible={!arrayMatches(selectedRoles, props.member.rolesList)}
|
||||
isVisible={!arrayMatches(selectedRoles, props.member.roles)}
|
||||
>
|
||||
<ResetSubmit
|
||||
onSubmit={() => props.onSubmit(selectedRoles)}
|
||||
onReset={() => {
|
||||
updateSelectedRoles(props.member.rolesList);
|
||||
updateSelectedRoles(props.member.roles);
|
||||
}}
|
||||
/>
|
||||
</FaderOpacity>
|
||||
|
|
|
@ -3,9 +3,10 @@ import {
|
|||
guild,
|
||||
guildData,
|
||||
guildEnum,
|
||||
guildRoles,
|
||||
mastheadSlugs,
|
||||
member,
|
||||
roleCategory,
|
||||
roleCategory2,
|
||||
user,
|
||||
} from 'roleypoly/common/types/storyData';
|
||||
import { RolePickerTemplate, RolePickerTemplateProps } from './RolePicker';
|
||||
|
@ -19,7 +20,7 @@ const props: RolePickerTemplateProps = {
|
|||
member: member,
|
||||
guild: guild,
|
||||
guilds: mastheadSlugs,
|
||||
roles: guildRoles,
|
||||
roles: [...roleCategory, ...roleCategory2],
|
||||
editable: false,
|
||||
user: user,
|
||||
guildEnumeration: guildEnum,
|
||||
|
|
Loading…
Add table
Reference in a new issue