Add global command channel rules and enhance command handling
- Introduced global command channel whitelist and blacklist in the GuildSettings model for improved command access control. - Implemented command gate logic in the command routing to handle channel and role restrictions, providing user feedback for denied commands. - Enhanced the CommandsManager component to support global rules and channel/role selection, improving the user interface for managing command permissions. - Updated localization files to include new keys for global command rules and related messages, ensuring clarity in user interactions.
This commit is contained in:
@@ -90,6 +90,22 @@ export const SETTINGS_SEARCH_FIELDS: SearchIndexEntry[] = [
|
||||
hash: 'field-access-modules',
|
||||
keywords: ['access']
|
||||
},
|
||||
{
|
||||
id: 'setting-commands-global-allow',
|
||||
kind: 'setting',
|
||||
labelKey: 'modulePages.commands.globalAllowedChannels',
|
||||
href: 'commands',
|
||||
hash: 'field-commands-global-allow',
|
||||
keywords: ['whitelist', 'channel', 'command']
|
||||
},
|
||||
{
|
||||
id: 'setting-commands-global-deny',
|
||||
kind: 'setting',
|
||||
labelKey: 'modulePages.commands.globalDeniedChannels',
|
||||
href: 'commands',
|
||||
hash: 'field-commands-global-deny',
|
||||
keywords: ['blacklist', 'channel', 'command']
|
||||
},
|
||||
// Moderation
|
||||
{
|
||||
id: 'setting-mod-enabled',
|
||||
|
||||
88
apps/webui/src/lib/discord-guild-resources.ts
Normal file
88
apps/webui/src/lib/discord-guild-resources.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { DiscordChannelOption, DiscordRoleOption } from '@nexumi/shared';
|
||||
import { env } from './env';
|
||||
import { redis } from './redis';
|
||||
|
||||
const DISCORD_API_BASE = 'https://discord.com/api/v10';
|
||||
const CACHE_TTL_SECONDS = 60;
|
||||
|
||||
/** Discord channel types usable for slash commands. */
|
||||
const COMMAND_CHANNEL_TYPES = new Set([
|
||||
0, // GUILD_TEXT
|
||||
5, // GUILD_ANNOUNCEMENT
|
||||
10, // ANNOUNCEMENT_THREAD
|
||||
11, // PUBLIC_THREAD
|
||||
12, // PRIVATE_THREAD
|
||||
15, // GUILD_FORUM
|
||||
16 // GUILD_MEDIA
|
||||
]);
|
||||
|
||||
async function discordBotFetch<T>(path: string): Promise<T> {
|
||||
const response = await fetch(`${DISCORD_API_BASE}${path}`, {
|
||||
headers: { Authorization: `Bot ${env.BOT_TOKEN}` },
|
||||
cache: 'no-store'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Discord API request to ${path} failed with status ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
interface DiscordChannelRest {
|
||||
id: string;
|
||||
name: string;
|
||||
type: number;
|
||||
parent_id?: string | null;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
interface DiscordRoleRest {
|
||||
id: string;
|
||||
name: string;
|
||||
color: number;
|
||||
position: number;
|
||||
managed?: boolean;
|
||||
}
|
||||
|
||||
export async function listGuildChannelsForDashboard(guildId: string): Promise<DiscordChannelOption[]> {
|
||||
const cacheKey = `dashboard:guild:${guildId}:channels`;
|
||||
const cached = await redis.get(cacheKey);
|
||||
if (cached) {
|
||||
return JSON.parse(cached) as DiscordChannelOption[];
|
||||
}
|
||||
|
||||
const channels = await discordBotFetch<DiscordChannelRest[]>(`/guilds/${guildId}/channels`);
|
||||
const options = channels
|
||||
.filter((channel) => COMMAND_CHANNEL_TYPES.has(channel.type))
|
||||
.map((channel) => ({
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
type: channel.type,
|
||||
parentId: channel.parent_id ?? null
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
await redis.set(cacheKey, JSON.stringify(options), 'EX', CACHE_TTL_SECONDS);
|
||||
return options;
|
||||
}
|
||||
|
||||
export async function listGuildRolesForDashboard(guildId: string): Promise<DiscordRoleOption[]> {
|
||||
const cacheKey = `dashboard:guild:${guildId}:roles`;
|
||||
const cached = await redis.get(cacheKey);
|
||||
if (cached) {
|
||||
return JSON.parse(cached) as DiscordRoleOption[];
|
||||
}
|
||||
|
||||
const roles = await discordBotFetch<DiscordRoleRest[]>(`/guilds/${guildId}/roles`);
|
||||
const options = roles
|
||||
.filter((role) => role.name !== '@everyone')
|
||||
.map((role) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
color: role.color,
|
||||
position: role.position
|
||||
}))
|
||||
.sort((a, b) => b.position - a.position);
|
||||
|
||||
await redis.set(cacheKey, JSON.stringify(options), 'EX', CACHE_TTL_SECONDS);
|
||||
return options;
|
||||
}
|
||||
50
apps/webui/src/lib/module-configs/command-global-rules.ts
Normal file
50
apps/webui/src/lib/module-configs/command-global-rules.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { CommandGlobalRules, CommandGlobalRulesPatch } from '@nexumi/shared';
|
||||
import { prisma } from '../prisma';
|
||||
|
||||
async function ensureGuildSettings(guildId: string) {
|
||||
await prisma.guild.upsert({
|
||||
where: { id: guildId },
|
||||
update: {},
|
||||
create: { id: guildId }
|
||||
});
|
||||
|
||||
return prisma.guildSettings.upsert({
|
||||
where: { guildId },
|
||||
update: {},
|
||||
create: { guildId }
|
||||
});
|
||||
}
|
||||
|
||||
function toRules(settings: {
|
||||
commandAllowedChannelIds: string[];
|
||||
commandDeniedChannelIds: string[];
|
||||
}): CommandGlobalRules {
|
||||
return {
|
||||
allowedChannelIds: settings.commandAllowedChannelIds,
|
||||
deniedChannelIds: settings.commandDeniedChannelIds
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCommandGlobalRules(guildId: string): Promise<CommandGlobalRules> {
|
||||
const settings = await ensureGuildSettings(guildId);
|
||||
return toRules(settings);
|
||||
}
|
||||
|
||||
export async function updateCommandGlobalRules(
|
||||
guildId: string,
|
||||
patch: CommandGlobalRulesPatch
|
||||
): Promise<CommandGlobalRules> {
|
||||
await ensureGuildSettings(guildId);
|
||||
const updated = await prisma.guildSettings.update({
|
||||
where: { guildId },
|
||||
data: {
|
||||
...(patch.allowedChannelIds !== undefined
|
||||
? { commandAllowedChannelIds: patch.allowedChannelIds }
|
||||
: {}),
|
||||
...(patch.deniedChannelIds !== undefined
|
||||
? { commandDeniedChannelIds: patch.deniedChannelIds }
|
||||
: {})
|
||||
}
|
||||
});
|
||||
return toRules(updated);
|
||||
}
|
||||
Reference in New Issue
Block a user