Merge pull request #25 from btallman/feat/roles-and-permissions
feat: add role management, member tools, voice channels, and channel permissions
This commit is contained in:
commit
a2890ae1b8
|
|
@ -59,7 +59,8 @@ const client = new Client({
|
|||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildMembers
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,11 @@ export const ReplyToForumSchema = z.object({
|
|||
});
|
||||
|
||||
export const CreateTextChannelSchema = z.object({
|
||||
guildId: z.string({ description: "The ID of the server (guild) where the text channel will be created." }),
|
||||
channelName: z.string({ description: "The name for the new text channel." }),
|
||||
topic: z.string({ description: "The (optional) topic/description for the channel." }).optional(),
|
||||
reason: z.string({ description: "Optional reason for audit logs when creating the channel." }).optional()
|
||||
}, {
|
||||
description: "Create a new text channel in a specified server (guild)."
|
||||
guildId: z.string(),
|
||||
channelName: z.string(),
|
||||
topic: z.string().optional(),
|
||||
categoryId: z.string().optional(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
// Category schemas
|
||||
|
|
@ -204,6 +203,88 @@ export const ListServersSchema = z.object({}, {
|
|||
description: "List all servers (guilds) the bot is a member of."
|
||||
});
|
||||
|
||||
// Role schemas
|
||||
export const ListRolesSchema = z.object({
|
||||
guildId: z.string()
|
||||
});
|
||||
|
||||
export const CreateRoleSchema = z.object({
|
||||
guildId: z.string(),
|
||||
name: z.string(),
|
||||
color: z.string().optional(),
|
||||
hoist: z.boolean().optional(),
|
||||
mentionable: z.boolean().optional(),
|
||||
permissions: z.array(z.string()).optional(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const EditRoleSchema = z.object({
|
||||
guildId: z.string(),
|
||||
roleId: z.string(),
|
||||
name: z.string().optional(),
|
||||
color: z.string().optional(),
|
||||
hoist: z.boolean().optional(),
|
||||
mentionable: z.boolean().optional(),
|
||||
permissions: z.array(z.string()).optional(),
|
||||
position: z.number().optional(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const DeleteRoleSchema = z.object({
|
||||
guildId: z.string(),
|
||||
roleId: z.string(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const AssignRoleSchema = z.object({
|
||||
guildId: z.string(),
|
||||
userId: z.string(),
|
||||
roleId: z.string(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const RemoveRoleSchema = z.object({
|
||||
guildId: z.string(),
|
||||
userId: z.string(),
|
||||
roleId: z.string(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const ListMembersSchema = z.object({
|
||||
guildId: z.string(),
|
||||
limit: z.number().min(1).max(1000).optional().default(100),
|
||||
after: z.string().optional()
|
||||
});
|
||||
|
||||
export const GetMemberSchema = z.object({
|
||||
guildId: z.string(),
|
||||
userId: z.string()
|
||||
});
|
||||
|
||||
// Channel permission schemas
|
||||
export const SetChannelPermissionsSchema = z.object({
|
||||
channelId: z.string(),
|
||||
roleId: z.string(),
|
||||
allow: z.array(z.string()).optional(),
|
||||
deny: z.array(z.string()).optional(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const RemoveChannelPermissionsSchema = z.object({
|
||||
channelId: z.string(),
|
||||
roleId: z.string(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
// Voice channel schema
|
||||
export const CreateVoiceChannelSchema = z.object({
|
||||
guildId: z.string(),
|
||||
channelName: z.string(),
|
||||
categoryId: z.string().optional(),
|
||||
userLimit: z.number().min(0).max(99).optional(),
|
||||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const SearchMessagesSchema = z.object({
|
||||
guildId: z.string({ description: "The ID of the server (guild) to search in." }).min(1, "guildId is required"),
|
||||
// Optional filters
|
||||
|
|
|
|||
|
|
@ -34,6 +34,17 @@ import {
|
|||
createCategoryHandler,
|
||||
editCategoryHandler,
|
||||
deleteCategoryHandler,
|
||||
createVoiceChannelHandler,
|
||||
setChannelPermissionsHandler,
|
||||
removeChannelPermissionsHandler,
|
||||
listRolesHandler,
|
||||
createRoleHandler,
|
||||
editRoleHandler,
|
||||
deleteRoleHandler,
|
||||
assignRoleHandler,
|
||||
removeRoleHandler,
|
||||
listMembersHandler,
|
||||
getMemberHandler,
|
||||
listServersHandler,
|
||||
searchMessagesHandler
|
||||
} from './tools/tools.js';
|
||||
|
|
@ -214,6 +225,50 @@ export class DiscordMCPServer {
|
|||
toolResponse = await searchMessagesHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_list_roles":
|
||||
toolResponse = await listRolesHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_create_role":
|
||||
toolResponse = await createRoleHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_edit_role":
|
||||
toolResponse = await editRoleHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_delete_role":
|
||||
toolResponse = await deleteRoleHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_assign_role":
|
||||
toolResponse = await assignRoleHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_remove_role":
|
||||
toolResponse = await removeRoleHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_list_members":
|
||||
toolResponse = await listMembersHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_get_member":
|
||||
toolResponse = await getMemberHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_create_voice_channel":
|
||||
toolResponse = await createVoiceChannelHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_set_channel_permissions":
|
||||
toolResponse = await setChannelPermissionsHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_remove_channel_permissions":
|
||||
toolResponse = await removeChannelPermissionsHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
|
|
|||
161
src/toolList.ts
161
src/toolList.ts
|
|
@ -129,13 +129,14 @@ export const toolList = [
|
|||
},
|
||||
{
|
||||
name: "discord_create_text_channel",
|
||||
description: "Creates a new text channel in a Discord server with an optional topic",
|
||||
description: "Creates a new text channel in a Discord server with an optional topic and parent category",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
channelName: { type: "string" },
|
||||
topic: { type: "string" }
|
||||
topic: { type: "string" },
|
||||
categoryId: { type: "string", description: "Parent category ID to place the channel under" }
|
||||
},
|
||||
required: ["guildId", "channelName"]
|
||||
}
|
||||
|
|
@ -378,5 +379,161 @@ export const toolList = [
|
|||
},
|
||||
required: ["guildId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_list_roles",
|
||||
description: "Lists all roles in a Discord server with their properties",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" }
|
||||
},
|
||||
required: ["guildId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_create_role",
|
||||
description: "Creates a new role in a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
|
||||
hoist: { type: "boolean", description: "Whether the role should be displayed separately in the sidebar" },
|
||||
mentionable: { type: "boolean", description: "Whether the role can be mentioned by anyone" },
|
||||
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names (e.g. ['SendMessages', 'ViewChannel'])" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_edit_role",
|
||||
description: "Edits an existing role in a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
roleId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
|
||||
hoist: { type: "boolean" },
|
||||
mentionable: { type: "boolean" },
|
||||
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names" },
|
||||
position: { type: "number", description: "New position in the role hierarchy" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "roleId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_delete_role",
|
||||
description: "Deletes a role from a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
roleId: { type: "string" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "roleId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_assign_role",
|
||||
description: "Assigns a role to a member in a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
userId: { type: "string" },
|
||||
roleId: { type: "string" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "userId", "roleId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_remove_role",
|
||||
description: "Removes a role from a member in a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
userId: { type: "string" },
|
||||
roleId: { type: "string" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "userId", "roleId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_list_members",
|
||||
description: "Lists members in a Discord server with their roles",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
limit: { type: "number", description: "Maximum number of members to return (default 100, max 1000)", minimum: 1, maximum: 1000, default: 100 },
|
||||
after: { type: "string", description: "User ID to paginate after" }
|
||||
},
|
||||
required: ["guildId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_get_member",
|
||||
description: "Gets detailed information about a specific member in a Discord server",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
userId: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "userId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_create_voice_channel",
|
||||
description: "Creates a new voice channel in a Discord server with an optional parent category",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
channelName: { type: "string" },
|
||||
categoryId: { type: "string", description: "Parent category ID to place the channel under" },
|
||||
userLimit: { type: "number", description: "Maximum number of users allowed (0 for unlimited)", minimum: 0, maximum: 99 },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["guildId", "channelName"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_set_channel_permissions",
|
||||
description: "Sets permission overrides for a role or user on a channel or category",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
channelId: { type: "string", description: "Channel or category ID" },
|
||||
roleId: { type: "string", description: "Role or user ID to set permissions for" },
|
||||
allow: { type: "array", items: { type: "string" }, description: "Permission flags to allow (e.g. ['ViewChannel', 'SendMessages'])" },
|
||||
deny: { type: "array", items: { type: "string" }, description: "Permission flags to deny (e.g. ['ViewChannel', 'SendMessages'])" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["channelId", "roleId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_remove_channel_permissions",
|
||||
description: "Removes all permission overrides for a role or user on a channel or category",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
channelId: { type: "string", description: "Channel or category ID" },
|
||||
roleId: { type: "string", description: "Role or user ID to remove overrides for" },
|
||||
reason: { type: "string" }
|
||||
},
|
||||
required: ["channelId", "roleId"]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { ChannelType } from "discord.js";
|
||||
import { ChannelType, PermissionsBitField } from "discord.js";
|
||||
import { ToolContext, ToolResponse } from "./types.js";
|
||||
import {
|
||||
CreateTextChannelSchema,
|
||||
|
|
@ -7,7 +7,10 @@ import {
|
|||
ReadMessagesSchema,
|
||||
CreateCategorySchema,
|
||||
EditCategorySchema,
|
||||
DeleteCategorySchema
|
||||
DeleteCategorySchema,
|
||||
SetChannelPermissionsSchema,
|
||||
RemoveChannelPermissionsSchema,
|
||||
CreateVoiceChannelSchema
|
||||
} from "../schemas.js";
|
||||
import { handleDiscordError } from "../errorHandler.js";
|
||||
|
||||
|
|
@ -110,7 +113,7 @@ export async function createTextChannelHandler(
|
|||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, channelName, topic, reason } = CreateTextChannelSchema.parse(args);
|
||||
const { guildId, channelName, topic, categoryId, reason } = CreateTextChannelSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
|
|
@ -133,6 +136,7 @@ export async function createTextChannelHandler(
|
|||
type: ChannelType.GuildText
|
||||
};
|
||||
if (topic) channelOptions.topic = topic;
|
||||
if (categoryId) channelOptions.parent = categoryId;
|
||||
if (reason) channelOptions.reason = reason;
|
||||
const channel = await guild.channels.create(channelOptions);
|
||||
|
||||
|
|
@ -259,3 +263,136 @@ export async function readMessagesHandler(
|
|||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to resolve permission strings to bitfield
|
||||
function resolvePermissions(perms: string[]): bigint {
|
||||
let bits = BigInt(0);
|
||||
for (const perm of perms) {
|
||||
const flag = PermissionsBitField.Flags[perm as keyof typeof PermissionsBitField.Flags];
|
||||
if (flag !== undefined) {
|
||||
bits |= flag;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
// Voice channel creation handler
|
||||
export async function createVoiceChannelHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, channelName, categoryId, userLimit, reason } = CreateVoiceChannelSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
if (!guild) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const channelOptions: any = {
|
||||
name: channelName,
|
||||
type: ChannelType.GuildVoice
|
||||
};
|
||||
if (categoryId) channelOptions.parent = categoryId;
|
||||
if (typeof userLimit === "number") channelOptions.userLimit = userLimit;
|
||||
if (reason) channelOptions.reason = reason;
|
||||
const channel = await guild.channels.create(channelOptions);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully created voice channel "${channelName}" with ID: ${channel.id}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Set channel permission overrides
|
||||
export async function setChannelPermissionsHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { channelId, roleId, allow, deny, reason } = SetChannelPermissionsSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const channel = await context.client.channels.fetch(channelId);
|
||||
if (!channel) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
if (!('permissionOverwrites' in channel) || !channel.permissionOverwrites) {
|
||||
return {
|
||||
content: [{ type: "text", text: `This channel type does not support permission overrides` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const overwrite: any = { id: roleId };
|
||||
if (allow && allow.length > 0) overwrite.allow = resolvePermissions(allow);
|
||||
if (deny && deny.length > 0) overwrite.deny = resolvePermissions(deny);
|
||||
await channel.permissionOverwrites.edit(roleId, {
|
||||
...(allow && allow.length > 0 ? Object.fromEntries(allow.map(p => [p, true])) : {}),
|
||||
...(deny && deny.length > 0 ? Object.fromEntries(deny.map(p => [p, false])) : {})
|
||||
}, { reason: reason || "Permissions updated via API" });
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully updated permissions for role/user ${roleId} on channel ${channelId}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove channel permission overrides
|
||||
export async function removeChannelPermissionsHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { channelId, roleId, reason } = RemoveChannelPermissionsSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const channel = await context.client.channels.fetch(channelId);
|
||||
if (!channel) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
if (!('permissionOverwrites' in channel) || !channel.permissionOverwrites) {
|
||||
return {
|
||||
content: [{ type: "text", text: `This channel type does not support permission overrides` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
await channel.permissionOverwrites.delete(roleId, reason || "Permission override removed via API");
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully removed permission overrides for role/user ${roleId} on channel ${channelId}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,310 @@
|
|||
import { ChannelType, PermissionsBitField } from "discord.js";
|
||||
import { ToolContext, ToolResponse } from "./types.js";
|
||||
import {
|
||||
ListRolesSchema,
|
||||
CreateRoleSchema,
|
||||
EditRoleSchema,
|
||||
DeleteRoleSchema,
|
||||
AssignRoleSchema,
|
||||
RemoveRoleSchema,
|
||||
ListMembersSchema,
|
||||
GetMemberSchema
|
||||
} from "../schemas.js";
|
||||
import { handleDiscordError } from "../errorHandler.js";
|
||||
|
||||
// Helper to resolve permission strings to bitfield
|
||||
function resolvePermissions(perms: string[]): bigint {
|
||||
let bits = BigInt(0);
|
||||
for (const perm of perms) {
|
||||
const flag = PermissionsBitField.Flags[perm as keyof typeof PermissionsBitField.Flags];
|
||||
if (flag !== undefined) {
|
||||
bits |= flag;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
export async function listRolesHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId } = ListRolesSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const roles = await guild.roles.fetch();
|
||||
const formatted = roles
|
||||
.sort((a, b) => b.position - a.position)
|
||||
.map(role => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
color: role.hexColor,
|
||||
position: role.position,
|
||||
hoist: role.hoist,
|
||||
mentionable: role.mentionable,
|
||||
managed: role.managed,
|
||||
memberCount: role.members.size
|
||||
}));
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ guildId, roleCount: formatted.length, roles: formatted }, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createRoleHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, name, color, hoist, mentionable, permissions, reason } = CreateRoleSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const options: any = { name };
|
||||
if (color) options.color = color;
|
||||
if (typeof hoist === "boolean") options.hoist = hoist;
|
||||
if (typeof mentionable === "boolean") options.mentionable = mentionable;
|
||||
if (permissions) options.permissions = resolvePermissions(permissions);
|
||||
if (reason) options.reason = reason;
|
||||
const role = await guild.roles.create(options);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully created role "${name}" with ID: ${role.id}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function editRoleHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, roleId, name, color, hoist, mentionable, permissions, position, reason } = EditRoleSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const role = await guild.roles.fetch(roleId);
|
||||
if (!role) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find role with ID: ${roleId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const update: any = {};
|
||||
if (name) update.name = name;
|
||||
if (color) update.color = color;
|
||||
if (typeof hoist === "boolean") update.hoist = hoist;
|
||||
if (typeof mentionable === "boolean") update.mentionable = mentionable;
|
||||
if (permissions) update.permissions = resolvePermissions(permissions);
|
||||
if (typeof position === "number") update.position = position;
|
||||
if (reason) update.reason = reason;
|
||||
await role.edit(update);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully edited role "${role.name}" (ID: ${roleId})`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRoleHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, roleId, reason } = DeleteRoleSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const role = await guild.roles.fetch(roleId);
|
||||
if (!role) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find role with ID: ${roleId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const roleName = role.name;
|
||||
await role.delete(reason || "Role deleted via API");
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully deleted role "${roleName}" (ID: ${roleId})`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function assignRoleHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, userId, roleId, reason } = AssignRoleSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const member = await guild.members.fetch(userId);
|
||||
if (!member) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
await member.roles.add(roleId, reason || "Role assigned via API");
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully assigned role ${roleId} to member ${member.user.tag} (${userId})`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeRoleHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, userId, roleId, reason } = RemoveRoleSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const member = await guild.members.fetch(userId);
|
||||
if (!member) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
await member.roles.remove(roleId, reason || "Role removed via API");
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully removed role ${roleId} from member ${member.user.tag} (${userId})`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function listMembersHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, limit, after } = ListMembersSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const fetchOptions: { limit: number; after?: string } = { limit };
|
||||
if (after) fetchOptions.after = after;
|
||||
const members = await guild.members.list(fetchOptions);
|
||||
const formatted = members.map((member) => ({
|
||||
id: member.id,
|
||||
username: member.user.username,
|
||||
displayName: member.displayName,
|
||||
bot: member.user.bot,
|
||||
roles: member.roles.cache
|
||||
.filter((r) => r.name !== "@everyone")
|
||||
.map((r) => ({ id: r.id, name: r.name })),
|
||||
joinedAt: member.joinedAt
|
||||
}));
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({ guildId, memberCount: formatted.length, members: formatted }, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMemberHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, userId } = GetMemberSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
const member = await guild.members.fetch(userId);
|
||||
if (!member) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const formatted = {
|
||||
id: member.id,
|
||||
username: member.user.username,
|
||||
displayName: member.displayName,
|
||||
bot: member.user.bot,
|
||||
roles: member.roles.cache
|
||||
.filter(r => r.name !== "@everyone")
|
||||
.map(r => ({ id: r.id, name: r.name })),
|
||||
joinedAt: member.joinedAt,
|
||||
nickname: member.nickname,
|
||||
avatar: member.displayAvatarURL()
|
||||
};
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(formatted, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,10 @@ import {
|
|||
readMessagesHandler,
|
||||
createCategoryHandler,
|
||||
editCategoryHandler,
|
||||
deleteCategoryHandler
|
||||
deleteCategoryHandler,
|
||||
createVoiceChannelHandler,
|
||||
setChannelPermissionsHandler,
|
||||
removeChannelPermissionsHandler
|
||||
} from './channel.js';
|
||||
import {
|
||||
getServerInfoHandler,
|
||||
|
|
@ -38,6 +41,16 @@ import {
|
|||
editWebhookHandler,
|
||||
deleteWebhookHandler
|
||||
} from './webhooks.js';
|
||||
import {
|
||||
listRolesHandler,
|
||||
createRoleHandler,
|
||||
editRoleHandler,
|
||||
deleteRoleHandler,
|
||||
assignRoleHandler,
|
||||
removeRoleHandler,
|
||||
listMembersHandler,
|
||||
getMemberHandler
|
||||
} from './roles.js';
|
||||
|
||||
// Export tool handlers
|
||||
export {
|
||||
|
|
@ -67,6 +80,17 @@ export {
|
|||
createCategoryHandler,
|
||||
editCategoryHandler,
|
||||
deleteCategoryHandler,
|
||||
createVoiceChannelHandler,
|
||||
setChannelPermissionsHandler,
|
||||
removeChannelPermissionsHandler,
|
||||
listRolesHandler,
|
||||
createRoleHandler,
|
||||
editRoleHandler,
|
||||
deleteRoleHandler,
|
||||
assignRoleHandler,
|
||||
removeRoleHandler,
|
||||
listMembersHandler,
|
||||
getMemberHandler,
|
||||
listServersHandler,
|
||||
searchMessagesHandler
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue