🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@cf-agents/discord

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cf-agents/discord - npm Package Compare versions

Comparing version
0.0.1
to
0.1.0
+7
CHANGELOG.md
# @cf-agents/discord
## 0.1.0
### Minor Changes
- 0f96a03: Added support for 'Single Channel Mode', allowing a default channel/chat ID to be configured at tool initialization.
/**
* Verify a Discord webhook signature using Ed25519.
*/
export async function verifyDiscordSignature(
payload: string,
signature: string,
timestamp: string,
publicKey: string
): Promise<boolean> {
try {
const encoder = new TextEncoder();
const data = encoder.encode(timestamp + payload);
const signatureBytes = new Uint8Array(
signature.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))
);
const publicKeyBytes = new Uint8Array(
publicKey.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))
);
const key = await crypto.subtle.importKey(
"raw",
publicKeyBytes,
{ name: "Ed25519", namedCurve: "Ed25519" },
false,
["verify"]
);
return await crypto.subtle.verify(
"Ed25519",
key,
signatureBytes,
data
);
} catch (e) {
console.error("Discord signature verification failed:", e);
return false;
}
}
export type DiscordInteractionResponse = {
message: string;
userId: string;
channelId: string;
reply: (text: string) => Promise<void>;
};
/**
* Standard handler for Discord interactions (slash commands or outgoing webhooks)
*/
export async function handleDiscordWebhook(
request: Request,
env: { DISCORD_PUBLIC_KEY: string; DISCORD_TOKEN: string }
): Promise<DiscordInteractionResponse | Response | null> {
if (request.method !== "POST") return null;
const signature = request.headers.get("X-Signature-Ed25519");
const timestamp = request.headers.get("X-Signature-Timestamp");
const body = await request.text();
if (!signature || !timestamp || !env.DISCORD_PUBLIC_KEY) {
return new Response("Missing signature headers", { status: 401 });
}
const isValid = await verifyDiscordSignature(
body,
signature,
timestamp,
env.DISCORD_PUBLIC_KEY
);
if (!isValid) {
return new Response("Invalid request signature", { status: 401 });
}
const interaction = JSON.parse(body);
// Handle PING from Discord
if (interaction.type === 1) {
return new Response(JSON.stringify({ type: 1 }), {
headers: { "Content-Type": "application/json" },
});
}
// Handle Application Command (Slash Command) or Message Component
if (interaction.type === 2 || interaction.type === 3) {
const userId = interaction.member?.user?.id || interaction.user?.id;
const channelId = interaction.channel_id;
const message = interaction.data?.options?.[0]?.value || interaction.data?.custom_id || "";
return {
message,
userId,
channelId,
reply: async (text: string) => {
const url = `https://discord.com/api/v10/interactions/${interaction.id}/${interaction.token}/callback`;
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: 4, // CHANNEL_MESSAGE_WITH_SOURCE
data: { content: text },
}),
});
},
};
}
return null;
}
+1
-1
{
"name": "@cf-agents/discord",
"version": "0.0.1",
"version": "0.1.0",
"main": "src/index.ts",

@@ -5,0 +5,0 @@ "types": "src/index.ts",

@@ -20,6 +20,4 @@ # @cf-agents/discord

## Usage
Initialize the tools with your Discord Bot Token and an optional default channel.
Initialize the tools with your Discord Bot Token.
```typescript

@@ -31,4 +29,24 @@ import { createDiscordTools } from "@cf-agents/discord";

token: "YOUR_BOT_TOKEN"
})
}),
config: {
channelId: "OPTIONAL_DEFAULT_CHANNEL_ID"
}
});
```## Two-Way Messaging (Webhooks)
Handle incoming Discord interactions (slash commands) to trigger your agent.
```typescript
import { handleDiscordWebhook } from "@cf-agents/discord";
// In your fetch handler
const result = await handleDiscordWebhook(request, {
DISCORD_PUBLIC_KEY: env.DISCORD_PUBLIC_KEY,
DISCORD_TOKEN: env.DISCORD_TOKEN
});
if (result && "message" in result) {
// Process with agent and reply
await result.reply("Processing your request...");
}
```
export * from "./types";
export * from "./tools";
export * from "./webhooks";

@@ -7,2 +7,5 @@ import { tool } from "ai";

getToken: () => Promise<DiscordToken>;
config?: {
channelId?: string;
};
}

@@ -27,8 +30,13 @@

parameters: z.object({
channelId: z.string().describe("The ID of the channel to send to"),
channelId: z.string().optional().describe("The ID of the channel to send to. Optional if a channel is pre-configured."),
content: z.string().describe("The message content"),
}),
execute: async ({ channelId, content }) => {
const targetChannelId = channelId ?? context.config?.channelId;
if (!targetChannelId) {
throw new Error("No channelId provided and no default channel configured.");
}
const response = await getAuthenticatedFetch(
`${DISCORD_API_BASE}/channels/${channelId}/messages`,
`${DISCORD_API_BASE}/channels/${targetChannelId}/messages`,
{

@@ -52,9 +60,14 @@ method: "POST",

parameters: z.object({
channelId: z.string().describe("The ID of the channel to read from"),
channelId: z.string().optional().describe("The ID of the channel to read from. Optional if a channel is pre-configured."),
limit: z.number().optional().default(10).describe("Number of messages to retrieve (max 100)"),
}),
execute: async ({ channelId, limit }) => {
const targetChannelId = channelId ?? context.config?.channelId;
if (!targetChannelId) {
throw new Error("No channelId provided and no default channel configured.");
}
const params = new URLSearchParams({ limit: String(limit) });
const response = await getAuthenticatedFetch(
`${DISCORD_API_BASE}/channels/${channelId}/messages?${params}`
`${DISCORD_API_BASE}/channels/${targetChannelId}/messages?${params}`
);

@@ -61,0 +74,0 @@