Add Discord integration support across components

- Introduced a new 'discord' entry in the ConfigurationWizard for Discord integration setup.
- Updated NodeLibrary to include Discord as a node type with associated color coding.
- Enhanced BaseNode with Discord icon representation.
- Configured nodeTypeConfigs for Discord with detailed descriptions and common use cases.
- Added connection suggestions for Discord nodes to facilitate integration patterns.
- Implemented DiscordService and discordProcessor for handling various Discord operations, including sending messages, managing channels, and user interactions.
- Created DiscordWizard component for configuring Discord operations with dynamic form fields based on selected operation type.
- Added DiscordNode component for visualizing Discord operation details, including status and configuration.
This commit is contained in:
Nikhil-Doye
2025-10-23 16:31:39 -04:00
parent 24f687191f
commit 72918c1ad7
17 changed files with 921 additions and 2 deletions
+13
View File
@@ -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<NodeType, WizardStep[]> = {
dataInput: [
@@ -146,6 +147,17 @@ const WIZARD_STEPS: Record<NodeType, WizardStep[]> = {
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<ConfigurationWizardProps> = ({
@@ -208,6 +220,7 @@ export const ConfigurationWizard: React.FC<ConfigurationWizardProps> = ({
structuredOutput: "Structured Output",
database: "Database Operations",
slack: "Slack Integration",
discord: "Discord Integration",
};
return labels[type] || "New Node";
};
+43
View File
@@ -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<NodeConfigurationProps> = ({
+1
View File
@@ -54,6 +54,7 @@ export const NodeLibrary: React.FC = () => {
dataOutput: "gray",
database: "cyan",
slack: "purple",
discord: "indigo",
};
const nodeTypesByCategory = getNodeTypesByCategory();
+8 -1
View File
@@ -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<WorkflowEditorProps> = ({ 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);
+8
View File
@@ -14,6 +14,7 @@ import {
AlertCircle,
Database,
MessageSquare,
Bot,
} from "lucide-react";
import { clsx } from "clsx";
@@ -27,6 +28,7 @@ const nodeIcons: Record<NodeType, React.ComponentType<any>> = {
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 = {
+179
View File
@@ -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, React.ComponentType<any>> = {
[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, string> = {
[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<DiscordNodeProps> = ({ 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 (
<div
className={`bg-white border-2 rounded-lg shadow-sm transition-all duration-200 ${
selected
? "border-indigo-500 shadow-lg"
: "border-indigo-200 hover:border-indigo-300"
}`}
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-100">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg bg-indigo-100 flex items-center justify-center">
<IconComponent className="w-4 h-4 text-indigo-600" />
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900">
{data.label || "Discord Integration"}
</h3>
<p className="text-xs text-gray-500">{operationLabel}</p>
</div>
</div>
{/* Status Indicator */}
<div className="flex items-center space-x-2">
{data.status === "running" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-blue-100">
<Clock className="w-3 h-3 text-blue-500 animate-spin" />
</div>
)}
{data.status === "success" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-green-100">
<CheckCircle className="w-3 h-3 text-green-500" />
</div>
)}
{data.status === "error" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-red-100">
<XCircle className="w-3 h-3 text-red-500" />
</div>
)}
</div>
</div>
{/* Content */}
<div className="px-4 pb-4">
<div className="space-y-2">
{/* Operation Details */}
<div className="text-xs text-gray-600">
{preview && (
<div className="space-y-1">
{Object.entries(preview).map(([key, value]) => (
<div key={key} className="flex justify-between">
<span className="text-gray-500 capitalize">{key}:</span>
<span className="text-gray-700 truncate ml-2">
{String(value)}
</span>
</div>
))}
</div>
)}
</div>
{/* Status Display */}
{data.status && (
<div className={`text-xs font-medium ${getStatusColor()}`}>
{data.status === "success" && "✓ Completed"}
{data.status === "error" && "✗ Failed"}
{data.status === "running" && "⏳ Running"}
{data.status === "idle" && "⏸ Ready"}
</div>
)}
</div>
</div>
</div>
);
};
+2
View File
@@ -10,3 +10,5 @@ export { DataOutputNode } from "./DataOutputNode";
export { UnifiedDatabaseNode } from "./UnifiedDatabaseNode";
// Slack node
export { SlackNode } from "./SlackNode";
// Discord node
export { DiscordNode } from "./DiscordNode";
+214
View File
@@ -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<WizardStepProps> = ({
data,
onChange,
onNext,
onPrevious,
}) => {
const [selectedOperation, setSelectedOperation] = useState<DiscordOperation>(
data.operation || DiscordOperation.MESSAGE
);
const handleOperationSelect = (operation: DiscordOperation) => {
setSelectedOperation(operation);
onChange({
...data,
operation,
});
};
const handleNext = () => {
if (selectedOperation) {
onNext();
}
};
return (
<div className="space-y-6">
<div className="text-center">
<div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-purple-100 rounded-full">
<Bot className="w-8 h-8 text-purple-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900">
Discord Integration
</h2>
<p className="text-gray-600 mt-2">
Choose what type of Discord operation you want to perform
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{DISCORD_OPERATIONS.map((operation) => {
const IconComponent = operation.icon;
const isSelected = selectedOperation === operation.id;
return (
<Card
key={operation.id}
className={`cursor-pointer transition-all duration-200 ${
isSelected
? "ring-2 ring-purple-500 bg-purple-50"
: "hover:shadow-md hover:border-purple-200"
}`}
onClick={() => handleOperationSelect(operation.id)}
>
<CardHeader className="pb-3">
<div className="flex items-center space-x-3">
<div className={`p-2 rounded-lg ${operation.color}`}>
<IconComponent className="w-5 h-5" />
</div>
<div className="flex-1">
<CardTitle className="text-lg font-semibold">
{operation.name}
</CardTitle>
<p className="text-sm text-gray-600 mt-1">
{operation.description}
</p>
</div>
{isSelected && (
<CheckCircle className="w-5 h-5 text-purple-600" />
)}
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-2">
{operation.features.map((feature, index) => (
<Badge
key={index}
variant="secondary"
className="text-xs mr-1 mb-1"
>
{feature}
</Badge>
))}
</div>
</CardContent>
</Card>
);
})}
</div>
<div className="flex justify-between pt-6">
<Button
variant="outline"
onClick={onPrevious}
className="flex items-center space-x-2"
>
<Settings className="w-4 h-4" />
<span>Back</span>
</Button>
<Button
onClick={handleNext}
disabled={!selectedOperation}
className="flex items-center space-x-2 bg-purple-600 hover:bg-purple-700"
>
<span>Next: Configure {selectedOperation}</span>
<CheckCircle className="w-4 h-4" />
</Button>
</div>
</div>
);
};
+22
View File
@@ -258,6 +258,26 @@ export const fieldConfigs: Record<string, FieldConfig> = {
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] || [];
+20
View File
@@ -151,6 +151,26 @@ export const nodeTypeConfigs: Record<NodeType, NodeTypeConfig> = {
"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 => {
+287
View File
@@ -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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
// 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<any> {
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<any> {
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,
};
}
}
+2
View File
@@ -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":
@@ -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"],
@@ -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;
+49
View File
@@ -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;
}
+2 -1
View File
@@ -20,7 +20,8 @@ export type NodeType =
// Unified database connector
| "database"
// Slack integration
| "slack";
| "slack"
| "discord";
export type NodeStatus =
| "idle"
+12
View File
@@ -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",
},
};
/**