diff --git a/src/components/ConfigurationWizard.tsx b/src/components/ConfigurationWizard.tsx index 2d8819f..b21261e 100644 --- a/src/components/ConfigurationWizard.tsx +++ b/src/components/ConfigurationWizard.tsx @@ -49,6 +49,7 @@ import { DataOutputWizard } from "./wizards/DataOutputWizard"; import { EmbeddingWizard } from "./wizards/EmbeddingWizard"; import { SimilaritySearchWizard } from "./wizards/SimilaritySearchWizard"; import { StructuredOutputWizard } from "./wizards/StructuredOutputWizard"; +import { DiscordWizard } from "./wizards/DiscordWizard"; const WIZARD_STEPS: Record = { dataInput: [ @@ -146,6 +147,17 @@ const WIZARD_STEPS: Record = { helpText: "Set up your Slack operation parameters", }, ], + // Discord integration + discord: [ + { + id: "discord-config", + title: "Discord Integration", + description: "Configure your Discord integration", + component: DiscordWizard, + validation: (data: any) => !!data.operation && !!data.botToken, + helpText: "Set up your Discord operation parameters", + }, + ], }; export const ConfigurationWizard: React.FC = ({ @@ -208,6 +220,7 @@ export const ConfigurationWizard: React.FC = ({ structuredOutput: "Structured Output", database: "Database Operations", slack: "Slack Integration", + discord: "Discord Integration", }; return labels[type] || "New Node"; }; diff --git a/src/components/NodeConfiguration.tsx b/src/components/NodeConfiguration.tsx index bc34ce6..1f049b3 100644 --- a/src/components/NodeConfiguration.tsx +++ b/src/components/NodeConfiguration.tsx @@ -292,6 +292,49 @@ const nodeTypeConfigs: Record< }, ], }, + discord: { + title: "Discord Integration Configuration", + fields: [ + { + key: "botToken", + label: "Bot Token", + type: "text", + placeholder: "your-bot-token", + }, + { + key: "operation", + label: "Operation Type", + type: "select", + options: [ + "message", + "channel", + "user", + "role", + "reaction", + "voice", + "webhook", + ], + }, + { + key: "channelId", + label: "Channel ID (for message/channel operations)", + type: "text", + placeholder: "123456789012345678", + }, + { + key: "message", + label: "Message Text", + type: "textarea", + placeholder: "Hello from the workflow!", + }, + { + key: "userId", + label: "User ID (for user operations)", + type: "text", + placeholder: "123456789012345678", + }, + ], + }, }; export const NodeConfiguration: React.FC = ({ diff --git a/src/components/NodeLibrary.tsx b/src/components/NodeLibrary.tsx index d022ace..df86e29 100644 --- a/src/components/NodeLibrary.tsx +++ b/src/components/NodeLibrary.tsx @@ -54,6 +54,7 @@ export const NodeLibrary: React.FC = () => { dataOutput: "gray", database: "cyan", slack: "purple", + discord: "indigo", }; const nodeTypesByCategory = getNodeTypesByCategory(); diff --git a/src/components/WorkflowEditor.tsx b/src/components/WorkflowEditor.tsx index f0898a9..5452b9e 100644 --- a/src/components/WorkflowEditor.tsx +++ b/src/components/WorkflowEditor.tsx @@ -34,6 +34,8 @@ import { UnifiedDatabaseNode, // Slack node SlackNode, + // Discord node + DiscordNode, } from "./nodes"; import { NodeData } from "../types"; import { Grid, Trash2, Sparkles, X, Lightbulb, Play } from "lucide-react"; @@ -50,6 +52,8 @@ const nodeTypes: NodeTypes = { database: UnifiedDatabaseNode, // Slack node slack: SlackNode, + // Discord node + discord: DiscordNode, }; interface WorkflowEditorProps { @@ -69,7 +73,10 @@ export const WorkflowEditor: React.FC = ({ onClose }) => { togglePanel, updateNodePosition, } = useWorkflowStore(); - const nodes = useMemo(() => currentWorkflow?.nodes || [], [currentWorkflow?.nodes]); + const nodes = useMemo( + () => currentWorkflow?.nodes || [], + [currentWorkflow?.nodes] + ); const edges = currentWorkflow?.edges || []; const [showConfig, setShowConfig] = useState(false); const [showCopilot, setShowCopilot] = useState(false); diff --git a/src/components/nodes/BaseNode.tsx b/src/components/nodes/BaseNode.tsx index 7b740d5..ef68aea 100644 --- a/src/components/nodes/BaseNode.tsx +++ b/src/components/nodes/BaseNode.tsx @@ -14,6 +14,7 @@ import { AlertCircle, Database, MessageSquare, + Bot, } from "lucide-react"; import { clsx } from "clsx"; @@ -27,6 +28,7 @@ const nodeIcons: Record> = { dataOutput: ArrowDownToLine, database: Database, slack: MessageSquare, + discord: Bot, }; const nodeColors: Record< @@ -87,6 +89,12 @@ const nodeColors: Record< icon: "text-purple-600", accent: "bg-purple-500", }, + discord: { + bg: "bg-indigo-50", + border: "border-indigo-200", + icon: "text-indigo-600", + accent: "bg-indigo-500", + }, }; const statusConfig = { diff --git a/src/components/nodes/DiscordNode.tsx b/src/components/nodes/DiscordNode.tsx new file mode 100644 index 0000000..8f84461 --- /dev/null +++ b/src/components/nodes/DiscordNode.tsx @@ -0,0 +1,179 @@ +import React from "react"; +import { NodeData } from "../../types"; +import { DiscordOperation } from "../../types/discord"; +import { NodeProps } from "reactflow"; +import { + MessageSquare, + Hash, + Users, + Shield, + Heart, + Mic, + Webhook, + Bot, + CheckCircle, + XCircle, + Clock, +} from "lucide-react"; + +interface DiscordNodeProps extends NodeProps { + data: NodeData; +} + +const operationIcons: Record> = { + [DiscordOperation.MESSAGE]: MessageSquare, + [DiscordOperation.CHANNEL]: Hash, + [DiscordOperation.USER]: Users, + [DiscordOperation.ROLE]: Shield, + [DiscordOperation.REACTION]: Heart, + [DiscordOperation.VOICE]: Mic, + [DiscordOperation.WEBHOOK]: Webhook, +}; + +const operationLabels: Record = { + [DiscordOperation.MESSAGE]: "Send Message", + [DiscordOperation.CHANNEL]: "Manage Channel", + [DiscordOperation.USER]: "Manage User", + [DiscordOperation.ROLE]: "Manage Role", + [DiscordOperation.REACTION]: "Add Reaction", + [DiscordOperation.VOICE]: "Voice Control", + [DiscordOperation.WEBHOOK]: "Webhook", +}; + +export const DiscordNode: React.FC = ({ data, selected }) => { + const operation = data.config?.operation as DiscordOperation; + const IconComponent = operation ? operationIcons[operation] : Bot; + const operationLabel = operation + ? operationLabels[operation] + : "Discord Operation"; + + const getStatusColor = () => { + if (data.status === "success") return "text-green-600"; + if (data.status === "error") return "text-red-600"; + if (data.status === "running") return "text-blue-600"; + return "text-gray-600"; + }; + + const getPreviewData = () => { + if (!data.config) return null; + + switch (operation) { + case DiscordOperation.MESSAGE: + return { + channel: data.config.channelId + ? `#${data.config.channelId}` + : "No channel", + message: data.config.message || "No message", + }; + case DiscordOperation.CHANNEL: + return { + name: data.config.channelName || "No name", + type: data.config.channelType || "text", + }; + case DiscordOperation.USER: + return { + user: data.config.userId || "No user", + nickname: data.config.nickname || "No nickname", + }; + case DiscordOperation.ROLE: + return { + role: data.config.roleName || "No role", + color: data.config.roleColor || "#000000", + }; + case DiscordOperation.REACTION: + return { + emoji: data.config.emoji || "👍", + message: data.config.messageId || "No message", + }; + case DiscordOperation.VOICE: + return { + channel: data.config.voiceChannelId || "No channel", + action: data.config.action || "join", + }; + case DiscordOperation.WEBHOOK: + return { + name: data.config.webhookName || "No name", + url: data.config.webhookUrl ? "Configured" : "No URL", + }; + default: + return null; + } + }; + + const preview = getPreviewData(); + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

+ {data.label || "Discord Integration"} +

+

{operationLabel}

+
+
+ + {/* Status Indicator */} +
+ {data.status === "running" && ( +
+ +
+ )} + {data.status === "success" && ( +
+ +
+ )} + {data.status === "error" && ( +
+ +
+ )} +
+
+ + {/* Content */} +
+
+ {/* Operation Details */} +
+ {preview && ( +
+ {Object.entries(preview).map(([key, value]) => ( +
+ {key}: + + {String(value)} + +
+ ))} +
+ )} +
+ + {/* Status Display */} + {data.status && ( +
+ {data.status === "success" && "✓ Completed"} + {data.status === "error" && "✗ Failed"} + {data.status === "running" && "⏳ Running"} + {data.status === "idle" && "⏸ Ready"} +
+ )} +
+
+
+ ); +}; diff --git a/src/components/nodes/index.ts b/src/components/nodes/index.ts index 3b91a3c..11f5586 100644 --- a/src/components/nodes/index.ts +++ b/src/components/nodes/index.ts @@ -10,3 +10,5 @@ export { DataOutputNode } from "./DataOutputNode"; export { UnifiedDatabaseNode } from "./UnifiedDatabaseNode"; // Slack node export { SlackNode } from "./SlackNode"; +// Discord node +export { DiscordNode } from "./DiscordNode"; diff --git a/src/components/wizards/DiscordWizard.tsx b/src/components/wizards/DiscordWizard.tsx new file mode 100644 index 0000000..6aaabce --- /dev/null +++ b/src/components/wizards/DiscordWizard.tsx @@ -0,0 +1,214 @@ +import React, { useState } from "react"; +import { WizardStepProps } from "../ConfigurationWizard"; +import { Button } from "../ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; +import { Badge } from "../ui/badge"; +import { + MessageSquare, + Hash, + Users, + Shield, + Heart, + Mic, + Webhook, + Bot, + CheckCircle, + Settings, +} from "lucide-react"; +import { DiscordOperation } from "../../types/discord"; + +const DISCORD_OPERATIONS = [ + { + id: DiscordOperation.MESSAGE, + name: "Send Message", + description: "Send messages to Discord channels or users", + icon: MessageSquare, + color: "bg-blue-100 text-blue-800 border-blue-200", + features: ["Text messages", "Embeds", "File attachments", "Mentions"], + }, + { + id: DiscordOperation.CHANNEL, + name: "Manage Channel", + description: "Create, modify, or manage Discord channels", + icon: Hash, + color: "bg-green-100 text-green-800 border-green-200", + features: [ + "Create channels", + "Modify settings", + "Manage permissions", + "Archive channels", + ], + }, + { + id: DiscordOperation.USER, + name: "Manage User", + description: "Manage Discord users and their properties", + icon: Users, + color: "bg-purple-100 text-purple-800 border-purple-200", + features: [ + "Change nicknames", + "Get user info", + "Manage roles", + "Kick/ban users", + ], + }, + { + id: DiscordOperation.ROLE, + name: "Manage Role", + description: "Create and manage Discord roles", + icon: Shield, + color: "bg-orange-100 text-orange-800 border-orange-200", + features: [ + "Create roles", + "Modify permissions", + "Assign colors", + "Manage hierarchy", + ], + }, + { + id: DiscordOperation.REACTION, + name: "Add Reaction", + description: "Add or remove reactions to messages", + icon: Heart, + color: "bg-pink-100 text-pink-800 border-pink-200", + features: [ + "Emoji reactions", + "Custom emojis", + "Bulk reactions", + "Remove reactions", + ], + }, + { + id: DiscordOperation.VOICE, + name: "Voice Control", + description: "Manage voice channels and connections", + icon: Mic, + color: "bg-indigo-100 text-indigo-800 border-indigo-200", + features: ["Join voice", "Leave voice", "Mute/unmute", "Voice settings"], + }, + { + id: DiscordOperation.WEBHOOK, + name: "Webhook", + description: "Send messages via webhooks", + icon: Webhook, + color: "bg-teal-100 text-teal-800 border-teal-200", + features: [ + "Custom webhooks", + "Avatar settings", + "Username override", + "Rich embeds", + ], + }, +]; + +export const DiscordWizard: React.FC = ({ + data, + onChange, + onNext, + onPrevious, +}) => { + const [selectedOperation, setSelectedOperation] = useState( + data.operation || DiscordOperation.MESSAGE + ); + + const handleOperationSelect = (operation: DiscordOperation) => { + setSelectedOperation(operation); + onChange({ + ...data, + operation, + }); + }; + + const handleNext = () => { + if (selectedOperation) { + onNext(); + } + }; + + return ( +
+
+
+ +
+

+ Discord Integration +

+

+ Choose what type of Discord operation you want to perform +

+
+ +
+ {DISCORD_OPERATIONS.map((operation) => { + const IconComponent = operation.icon; + const isSelected = selectedOperation === operation.id; + + return ( + handleOperationSelect(operation.id)} + > + +
+
+ +
+
+ + {operation.name} + +

+ {operation.description} +

+
+ {isSelected && ( + + )} +
+
+ +
+ {operation.features.map((feature, index) => ( + + {feature} + + ))} +
+
+
+ ); + })} +
+ +
+ + + +
+
+ ); +}; diff --git a/src/config/fieldConfigs.ts b/src/config/fieldConfigs.ts index 1d36bc5..97b9e21 100644 --- a/src/config/fieldConfigs.ts +++ b/src/config/fieldConfigs.ts @@ -258,6 +258,26 @@ export const fieldConfigs: Record = { required: true, }, }, + // Discord operation field + "discord.operation": { + technicalName: "operation", + userFriendlyName: "Discord operation", + description: "Choose what type of Discord operation to perform", + helpText: + "Select the type of Discord operation you want to perform. Each operation has different configuration options.", + examples: [ + "Message - Send messages to channels or users", + "Channel - Create, modify, or manage channels", + "User - Manage users and their properties", + "Role - Create and manage roles", + "Reaction - Add/remove reactions to messages", + "Voice - Manage voice channels and connections", + "Webhook - Send messages via webhooks", + ], + validation: { + required: true, + }, + }, // Output Fields format: { @@ -404,6 +424,8 @@ export const getFieldsForNodeType = (nodeType: string): string[] => { structuredOutput: ["schema", "model", "label"], dataOutput: ["format", "filename", "label"], database: ["connectionId", "operation", "label"], + slack: ["botToken", "operation", "label"], + discord: ["botToken", "operation", "label"], }; return fieldMappings[nodeType] || []; diff --git a/src/config/nodeTypeConfigs.ts b/src/config/nodeTypeConfigs.ts index aea9ba9..a877c2a 100644 --- a/src/config/nodeTypeConfigs.ts +++ b/src/config/nodeTypeConfigs.ts @@ -151,6 +151,26 @@ export const nodeTypeConfigs: Record = { "React to messages with emojis", ], }, + discord: { + technicalName: "discord", + userFriendlyName: "Discord Integration", + description: + "Send messages, manage channels, roles, and interact with Discord servers", + category: "Communication", + icon: "🎮", + helpText: + "Integrate with Discord to send messages, create channels, manage roles, add reactions, and interact with your community. Perfect for gaming communities and developer teams.", + commonUseCases: [ + "Send automated messages to channels", + "Create and manage server channels", + "Assign roles to users automatically", + "Add reactions to messages", + "Manage voice channels", + "Send webhook notifications", + "Moderate server content", + "Welcome new members", + ], + }, }; export const getNodeTypeConfig = (nodeType: NodeType): NodeTypeConfig => { diff --git a/src/services/discord/DiscordService.ts b/src/services/discord/DiscordService.ts new file mode 100644 index 0000000..e380597 --- /dev/null +++ b/src/services/discord/DiscordService.ts @@ -0,0 +1,287 @@ +import { DiscordOperation, DiscordConfig } from "../../types/discord"; + +export class DiscordService { + private baseUrl = "https://discord.com/api/v10"; + + async executeOperation( + operation: DiscordOperation, + config: DiscordConfig + ): Promise { + try { + switch (operation) { + case DiscordOperation.MESSAGE: + return await this.sendMessage(config); + case DiscordOperation.CHANNEL: + return await this.manageChannel(config); + case DiscordOperation.USER: + return await this.manageUser(config); + case DiscordOperation.ROLE: + return await this.manageRole(config); + case DiscordOperation.REACTION: + return await this.manageReaction(config); + case DiscordOperation.VOICE: + return await this.manageVoice(config); + case DiscordOperation.WEBHOOK: + return await this.manageWebhook(config); + default: + throw new Error(`Unsupported Discord operation: ${operation}`); + } + } catch (error) { + throw new Error( + `Discord operation failed: ${ + error instanceof Error ? error.message : "Unknown error" + }` + ); + } + } + + private async sendMessage(config: DiscordConfig): Promise { + const payload: any = { + content: config.message, + }; + + if (config.embed) { + payload.embeds = [config.embed]; + } + + return await this.makeRequest( + `/channels/${config.channelId}/messages`, + { + method: "POST", + body: JSON.stringify(payload), + }, + config.botToken + ); + } + + private async manageChannel(config: DiscordConfig): Promise { + const payload: any = { + name: config.channelName, + type: + config.channelType === "text" + ? 0 + : config.channelType === "voice" + ? 2 + : 4, + }; + + if (config.channelTopic) { + payload.topic = config.channelTopic; + } + + return await this.makeRequest( + `/guilds/${config.channelId}/channels`, + { + method: "POST", + body: JSON.stringify(payload), + }, + config.botToken + ); + } + + private async manageUser(config: DiscordConfig): Promise { + if (config.nickname) { + return await this.makeRequest( + `/guilds/${config.channelId}/members/${config.userId}`, + { + method: "PATCH", + body: JSON.stringify({ nick: config.nickname }), + }, + config.botToken + ); + } + + // Get user info + return await this.makeRequest( + `/users/${config.userId}`, + { + method: "GET", + }, + config.botToken + ); + } + + private async manageRole(config: DiscordConfig): Promise { + const payload: any = { + name: config.roleName, + }; + + if (config.roleColor) { + payload.color = parseInt(config.roleColor.replace("#", ""), 16); + } + + if (config.rolePermissions) { + payload.permissions = config.rolePermissions; + } + + if (config.roleId) { + return await this.makeRequest( + `/guilds/${config.channelId}/roles/${config.roleId}`, + { + method: "PATCH", + body: JSON.stringify(payload), + }, + config.botToken + ); + } else { + return await this.makeRequest( + `/guilds/${config.channelId}/roles`, + { + method: "POST", + body: JSON.stringify(payload), + }, + config.botToken + ); + } + } + + private async manageReaction(config: DiscordConfig): Promise { + const emoji = encodeURIComponent(config.emoji || "👍"); + + return await this.makeRequest( + `/channels/${config.channelId}/messages/${config.messageId}/reactions/${emoji}`, + { + method: "PUT", + }, + config.botToken + ); + } + + private async manageVoice(config: DiscordConfig): Promise { + // Voice operations are complex and typically require voice connection + // This is a placeholder for basic voice channel management + return await this.makeRequest( + `/channels/${config.voiceChannelId}`, + { + method: "GET", + }, + config.botToken + ); + } + + private async manageWebhook(config: DiscordConfig): Promise { + if (config.webhookUrl) { + // Use webhook URL directly + const payload = { + content: config.message, + username: config.webhookName, + avatar_url: config.webhookAvatar, + }; + + const response = await fetch(config.webhookUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`Webhook request failed: ${response.statusText}`); + } + + return await response.json(); + } + + // Create webhook + const payload = { + name: config.webhookName, + avatar: config.webhookAvatar, + }; + + return await this.makeRequest( + `/channels/${config.channelId}/webhooks`, + { + method: "POST", + body: JSON.stringify(payload), + }, + config.botToken + ); + } + + private async makeRequest( + endpoint: string, + options: RequestInit, + botToken: string + ): Promise { + const url = `${this.baseUrl}${endpoint}`; + + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bot ${botToken}`, + "Content-Type": "application/json", + "User-Agent": "DiscordBot (https://github.com/your-bot, 1.0.0)", + ...options.headers, + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `Discord API error: ${response.status} ${response.statusText} - ${ + errorData.message || "Unknown error" + }` + ); + } + + return await response.json(); + } + + validateConfig(config: DiscordConfig): { + isValid: boolean; + errors: string[]; + } { + const errors: string[] = []; + + if (!config.botToken) { + errors.push("Bot token is required"); + } + + if (!config.operation) { + errors.push("Operation is required"); + } + + switch (config.operation) { + case DiscordOperation.MESSAGE: + if (!config.channelId) + errors.push("Channel ID is required for message operations"); + if (!config.message) errors.push("Message content is required"); + break; + case DiscordOperation.CHANNEL: + if (!config.channelName) + errors.push("Channel name is required for channel operations"); + break; + case DiscordOperation.USER: + if (!config.userId) + errors.push("User ID is required for user operations"); + break; + case DiscordOperation.ROLE: + if (!config.roleName) + errors.push("Role name is required for role operations"); + break; + case DiscordOperation.REACTION: + if (!config.messageId) + errors.push("Message ID is required for reaction operations"); + if (!config.emoji) + errors.push("Emoji is required for reaction operations"); + break; + case DiscordOperation.VOICE: + if (!config.voiceChannelId) + errors.push("Voice channel ID is required for voice operations"); + break; + case DiscordOperation.WEBHOOK: + if (!config.webhookUrl && !config.channelId) { + errors.push( + "Either webhook URL or channel ID is required for webhook operations" + ); + } + break; + } + + return { + isValid: errors.length === 0, + errors, + }; + } +} diff --git a/src/services/executionEngine.ts b/src/services/executionEngine.ts index 5e6e389..9c1b4e8 100644 --- a/src/services/executionEngine.ts +++ b/src/services/executionEngine.ts @@ -834,6 +834,8 @@ export class ExecutionEngine { return (await import("./processors/unifiedDatabaseProcessor")).default; case "slack": return (await import("./processors/slackProcessor")).default; + case "discord": + return (await import("./processors/discordProcessor")).default; case "structuredOutput": return (await import("./processors/structuredOutputProcessor")).default; case "embeddingGenerator": diff --git a/src/services/processors/ProcessorRegistry.ts b/src/services/processors/ProcessorRegistry.ts index b425d3f..218f581 100644 --- a/src/services/processors/ProcessorRegistry.ts +++ b/src/services/processors/ProcessorRegistry.ts @@ -9,6 +9,7 @@ export class ProcessorRegistry { "llmTask", "database", "slack", + "discord", "structuredOutput", "embeddingGenerator", "similaritySearch", @@ -150,6 +151,10 @@ export class ProcessorRegistry { pattern: /slack|message|notification|chat/i, types: ["slack"], }, + { + pattern: /discord|gaming|community|server/i, + types: ["discord"], + }, { pattern: /embed|vector/i, types: ["embeddingGenerator", "similaritySearch"], diff --git a/src/services/processors/discordProcessor.ts b/src/services/processors/discordProcessor.ts new file mode 100644 index 0000000..7caaad2 --- /dev/null +++ b/src/services/processors/discordProcessor.ts @@ -0,0 +1,54 @@ +import { NodeData } from "../../types"; +import { DiscordService } from "../discord/DiscordService"; +import { DiscordOperation } from "../../types/discord"; + +async function discordProcessor(nodeData: NodeData): Promise<{ + success: boolean; + data?: any; + error?: string; +}> { + try { + const { config } = nodeData; + + if (!config) { + return { + success: false, + error: "Discord configuration is missing", + }; + } + + const discordService = new DiscordService(); + + // Validate configuration + const validation = discordService.validateConfig(config as any); + if (!validation.isValid) { + return { + success: false, + error: `Configuration validation failed: ${validation.errors.join( + ", " + )}`, + }; + } + + // Execute Discord operation + const result = await discordService.executeOperation( + config.operation as DiscordOperation, + config as any + ); + + return { + success: true, + data: result, + }; + } catch (error) { + return { + success: false, + error: + error instanceof Error + ? error.message + : "Unknown Discord operation error", + }; + } +} + +export default discordProcessor; diff --git a/src/types/discord.ts b/src/types/discord.ts new file mode 100644 index 0000000..a4619dc --- /dev/null +++ b/src/types/discord.ts @@ -0,0 +1,49 @@ +export enum DiscordOperation { + MESSAGE = "message", + CHANNEL = "channel", + USER = "user", + ROLE = "role", + REACTION = "reaction", + VOICE = "voice", + WEBHOOK = "webhook", +} + +export interface DiscordConfig { + botToken: string; + operation: DiscordOperation; + label: string; + + // Message operations + channelId?: string; + message?: string; + embed?: any; + + // Channel operations + channelName?: string; + channelType?: "text" | "voice" | "category"; + channelTopic?: string; + + // User operations + userId?: string; + username?: string; + nickname?: string; + + // Role operations + roleId?: string; + roleName?: string; + roleColor?: string; + rolePermissions?: string; + + // Reaction operations + messageId?: string; + emoji?: string; + + // Voice operations + voiceChannelId?: string; + action?: "join" | "leave" | "mute" | "unmute"; + + // Webhook operations + webhookUrl?: string; + webhookName?: string; + webhookAvatar?: string; +} diff --git a/src/types/index.ts b/src/types/index.ts index 63d01d9..2be6f0f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -20,7 +20,8 @@ export type NodeType = // Unified database connector | "database" // Slack integration - | "slack"; + | "slack" + | "discord"; export type NodeStatus = | "idle" diff --git a/src/utils/connectionSuggestions.ts b/src/utils/connectionSuggestions.ts index a61290b..28a1c3d 100644 --- a/src/utils/connectionSuggestions.ts +++ b/src/utils/connectionSuggestions.ts @@ -128,6 +128,18 @@ const NODE_COMPATIBILITY: Record< description: "Slack nodes typically send notifications and updates to team channels", }, + discord: { + canConnectTo: ["dataOutput"], + shouldConnectTo: ["dataOutput"], + commonPatterns: [ + "Process → Send message", + "Generate report → Post to channel", + "Data analysis → Share results", + "Game events → Notify community", + ], + description: + "Discord nodes typically send messages and updates to community channels", + }, }; /**