feat: Facebook Page integration
Four tools: facebook_get_page, facebook_get_posts, facebook_create_post
(text + optional link), facebook_create_photo_post (image URL + caption).
Uses Graph API v19.0 with Page access token. Credentials stored per-customer
in Redis under creds:{id}:facebook with pageId alongside the access token.
Env-var fallback: FACEBOOK_{ACCOUNT}_ACCESS_TOKEN + FACEBOOK_{ACCOUNT}_PAGE_ID.
Wired into Platform type, validPlatforms, /api/connections, manifest OpenAPI
spec, and manifest tool registry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
160
src/clients/facebook.ts
Normal file
160
src/clients/facebook.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import type { Customer } from '../billing/middleware.js';
|
||||||
|
import type { OAuthCredentials } from '../multitenancy/credential-store.js';
|
||||||
|
import { createToolAudit } from '../multitenancy/audit-log.js';
|
||||||
|
|
||||||
|
const FACEBOOK_API_BASE = 'https://graph.facebook.com/v19.0';
|
||||||
|
|
||||||
|
interface FacebookCredentials extends OAuthCredentials {
|
||||||
|
pageId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnvToken(account: string): string {
|
||||||
|
return process.env[`FACEBOOK_${account.toUpperCase()}_ACCESS_TOKEN`] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnvPageId(account: string): string {
|
||||||
|
return process.env[`FACEBOOK_${account.toUpperCase()}_PAGE_ID`] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveCreds(
|
||||||
|
args: { account?: string },
|
||||||
|
customer?: Customer
|
||||||
|
): Promise<{ accessToken: string; pageId: string }> {
|
||||||
|
if (customer) {
|
||||||
|
const creds = await customer.getCredential<FacebookCredentials>('facebook');
|
||||||
|
if (!creds) throw new Error('Facebook not connected for this account');
|
||||||
|
return { accessToken: creds.accessToken, pageId: creds.pageId };
|
||||||
|
}
|
||||||
|
const account = args.account ?? 'default';
|
||||||
|
const accessToken = getEnvToken(account);
|
||||||
|
const pageId = getEnvPageId(account);
|
||||||
|
if (!accessToken || !pageId) {
|
||||||
|
throw new Error('Missing Facebook credentials. Set FACEBOOK_{ACCOUNT}_ACCESS_TOKEN and FACEBOOK_{ACCOUNT}_PAGE_ID');
|
||||||
|
}
|
||||||
|
return { accessToken, pageId };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fbRequest(
|
||||||
|
endpoint: string,
|
||||||
|
accessToken: string,
|
||||||
|
method: 'GET' | 'POST' = 'GET',
|
||||||
|
body?: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
const url = new URL(`${FACEBOOK_API_BASE}${endpoint}`);
|
||||||
|
if (method === 'GET') url.searchParams.set('access_token', accessToken);
|
||||||
|
|
||||||
|
const res = await fetch(url.toString(), {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: method === 'POST' ? JSON.stringify({ ...body, access_token: accessToken }) : undefined,
|
||||||
|
signal: AbortSignal.timeout(15000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const error = await res.text();
|
||||||
|
throw new Error(`Facebook API error (${res.status}): ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPage(
|
||||||
|
args: { account?: string },
|
||||||
|
customer?: Customer
|
||||||
|
): Promise<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
about?: string;
|
||||||
|
fan_count: number;
|
||||||
|
followers_count: number;
|
||||||
|
link?: string;
|
||||||
|
}> {
|
||||||
|
const { accessToken, pageId } = await resolveCreds(args, customer);
|
||||||
|
const data = await fbRequest(
|
||||||
|
`/${pageId}?fields=id,name,category,about,fan_count,followers_count,link`,
|
||||||
|
accessToken
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: data.id ?? '',
|
||||||
|
name: data.name ?? '',
|
||||||
|
category: data.category ?? '',
|
||||||
|
about: data.about,
|
||||||
|
fan_count: data.fan_count ?? 0,
|
||||||
|
followers_count: data.followers_count ?? 0,
|
||||||
|
link: data.link,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPosts(
|
||||||
|
args: { limit?: number; account?: string },
|
||||||
|
customer?: Customer
|
||||||
|
): Promise<Array<{
|
||||||
|
id: string;
|
||||||
|
message?: string;
|
||||||
|
story?: string;
|
||||||
|
created_time: string;
|
||||||
|
permalink_url?: string;
|
||||||
|
}>> {
|
||||||
|
const { accessToken, pageId } = await resolveCreds(args, customer);
|
||||||
|
const limit = args.limit ?? 10;
|
||||||
|
const data = await fbRequest(
|
||||||
|
`/${pageId}/feed?fields=id,message,story,created_time,permalink_url&limit=${limit}`,
|
||||||
|
accessToken
|
||||||
|
);
|
||||||
|
return (data.data ?? []).map((p: Record<string, unknown>) => ({
|
||||||
|
id: String(p.id ?? ''),
|
||||||
|
message: p.message as string | undefined,
|
||||||
|
story: p.story as string | undefined,
|
||||||
|
created_time: String(p.created_time ?? ''),
|
||||||
|
permalink_url: p.permalink_url as string | undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPost(
|
||||||
|
args: { message: string; link?: string; account?: string },
|
||||||
|
customer?: Customer
|
||||||
|
): Promise<{ success: boolean; post_id: string }> {
|
||||||
|
const audit = customer ? createToolAudit(customer.id, 'facebook:createPost') : null;
|
||||||
|
const auditArgs = { message: args.message.slice(0, 80) };
|
||||||
|
const { accessToken, pageId } = await resolveCreds(args, customer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body: Record<string, unknown> = { message: args.message };
|
||||||
|
if (args.link) body.link = args.link;
|
||||||
|
|
||||||
|
const data = await fbRequest(`/${pageId}/feed`, accessToken, 'POST', body);
|
||||||
|
const result = { success: true, post_id: String(data.id ?? '') };
|
||||||
|
if (audit) await audit.success(auditArgs);
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
if (audit) await audit.error(auditArgs, String(err));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createPhotoPost(
|
||||||
|
args: { image_url: string; caption?: string; account?: string },
|
||||||
|
customer?: Customer
|
||||||
|
): Promise<{ success: boolean; post_id: string; photo_id: string }> {
|
||||||
|
const audit = customer ? createToolAudit(customer.id, 'facebook:createPhotoPost') : null;
|
||||||
|
const auditArgs = { image_url: args.image_url };
|
||||||
|
const { accessToken, pageId } = await resolveCreds(args, customer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fbRequest(`/${pageId}/photos`, accessToken, 'POST', {
|
||||||
|
url: args.image_url,
|
||||||
|
caption: args.caption ?? '',
|
||||||
|
});
|
||||||
|
const result = {
|
||||||
|
success: true,
|
||||||
|
post_id: String(data.post_id ?? data.id ?? ''),
|
||||||
|
photo_id: String(data.id ?? ''),
|
||||||
|
};
|
||||||
|
if (audit) await audit.success(auditArgs);
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
if (audit) await audit.error(auditArgs, String(err));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -663,7 +663,7 @@ app.post('/api/connect/:platform', meterMiddleware, async (req, res) => {
|
|||||||
const platform = req.params.platform as Platform;
|
const platform = req.params.platform as Platform;
|
||||||
const { accessToken, refreshToken, expiresAt, scope } = req.body as Record<string, string>;
|
const { accessToken, refreshToken, expiresAt, scope } = req.body as Record<string, string>;
|
||||||
|
|
||||||
const validPlatforms: Platform[] = ['linkedin', 'telegram', 'discord', 'instagram', 'twitter', 'tiktok', 'snapchat'];
|
const validPlatforms: Platform[] = ['linkedin', 'telegram', 'discord', 'instagram', 'twitter', 'tiktok', 'snapchat', 'facebook'];
|
||||||
if (!validPlatforms.includes(platform)) {
|
if (!validPlatforms.includes(platform)) {
|
||||||
res.status(400).json({ error: 'unknown_platform' });
|
res.status(400).json({ error: 'unknown_platform' });
|
||||||
return;
|
return;
|
||||||
@@ -687,7 +687,7 @@ app.post('/api/connect/:platform', meterMiddleware, async (req, res) => {
|
|||||||
// Get connection status for a customer
|
// Get connection status for a customer
|
||||||
app.get('/api/connections', meterMiddleware, async (req, res) => {
|
app.get('/api/connections', meterMiddleware, async (req, res) => {
|
||||||
const customer = (req as unknown as { customer: Customer }).customer;
|
const customer = (req as unknown as { customer: Customer }).customer;
|
||||||
const platforms: Platform[] = ['email', 'whatsapp', 'linkedin', 'telegram', 'discord', 'instagram', 'twitter', 'tiktok', 'snapchat', 'obsidian'];
|
const platforms: Platform[] = ['email', 'whatsapp', 'linkedin', 'telegram', 'discord', 'instagram', 'twitter', 'tiktok', 'snapchat', 'facebook', 'obsidian'];
|
||||||
|
|
||||||
const status: Record<string, boolean> = {};
|
const status: Record<string, boolean> = {};
|
||||||
for (const platform of platforms) {
|
for (const platform of platforms) {
|
||||||
|
|||||||
174
src/manifest.ts
174
src/manifest.ts
@@ -473,6 +473,75 @@ export function getOpenApiSpec(serverUrl: string) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Facebook ───────────────────────────────────────────────
|
||||||
|
'/api/facebook/page': {
|
||||||
|
get: {
|
||||||
|
operationId: 'facebook_get_page',
|
||||||
|
summary: 'Get Facebook Page profile',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'account', in: 'query', schema: { type: 'string' } },
|
||||||
|
],
|
||||||
|
responses: { '200': { description: 'Page info' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/facebook/posts': {
|
||||||
|
get: {
|
||||||
|
operationId: 'facebook_get_posts',
|
||||||
|
summary: 'Get Facebook Page posts',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'limit', in: 'query', schema: { type: 'integer' } },
|
||||||
|
{ name: 'account', in: 'query', schema: { type: 'string' } },
|
||||||
|
],
|
||||||
|
responses: { '200': { description: 'Post list' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/facebook/post': {
|
||||||
|
post: {
|
||||||
|
operationId: 'facebook_create_post',
|
||||||
|
summary: 'Publish text post to Facebook Page',
|
||||||
|
requestBody: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['message'],
|
||||||
|
properties: {
|
||||||
|
message: { type: 'string' },
|
||||||
|
link: { type: 'string' },
|
||||||
|
account: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: { '200': { description: 'Post created' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/facebook/photo': {
|
||||||
|
post: {
|
||||||
|
operationId: 'facebook_create_photo_post',
|
||||||
|
summary: 'Publish photo post to Facebook Page',
|
||||||
|
requestBody: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['image_url'],
|
||||||
|
properties: {
|
||||||
|
image_url: { type: 'string' },
|
||||||
|
caption: { type: 'string' },
|
||||||
|
account: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: { '200': { description: 'Photo posted' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// ── Twitter/X ───────────────────────────────────────────────
|
// ── Twitter/X ───────────────────────────────────────────────
|
||||||
'/api/twitter/search': {
|
'/api/twitter/search': {
|
||||||
get: {
|
get: {
|
||||||
@@ -1234,6 +1303,107 @@ export function getManifest(serverUrl: string, authEnabled: boolean) {
|
|||||||
examples: [{ image_url: 'https://example.com/photo.jpg', caption: 'New post!', account: 'default' }],
|
examples: [{ image_url: 'https://example.com/photo.jpg', caption: 'New post!', account: 'default' }],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Facebook tools ─────────────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
name: 'facebook_get_page',
|
||||||
|
category: 'facebook',
|
||||||
|
description: 'Get a Facebook Page profile including name, category, fan count, and follower count',
|
||||||
|
when_to_use: 'User asks about their Facebook Page stats, followers, or account details.',
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
category: { type: 'string' },
|
||||||
|
about: { type: 'string' },
|
||||||
|
fan_count: { type: 'number' },
|
||||||
|
followers_count: { type: 'number' },
|
||||||
|
link: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
examples: [{ account: 'default' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_get_posts',
|
||||||
|
category: 'facebook',
|
||||||
|
description: 'Get recent posts from a Facebook Page feed',
|
||||||
|
when_to_use: 'User wants to see recent Facebook Page posts or content history.',
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
limit: { type: 'number', description: 'Max posts (default: 10)' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
message: { type: 'string' },
|
||||||
|
story: { type: 'string' },
|
||||||
|
created_time: { type: 'string' },
|
||||||
|
permalink_url: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
examples: [{ limit: 5, account: 'default' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_create_post',
|
||||||
|
category: 'facebook',
|
||||||
|
description: 'Publish a text post (optionally with a link) to a Facebook Page',
|
||||||
|
when_to_use: 'User wants to post a status update or share a link on their Facebook Page.',
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['message'],
|
||||||
|
properties: {
|
||||||
|
message: { type: 'string', description: 'Post text content' },
|
||||||
|
link: { type: 'string', description: 'Optional URL to attach' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: { type: 'boolean' },
|
||||||
|
post_id: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
examples: [{ message: 'Something new is coming next week.', account: 'default' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_create_photo_post',
|
||||||
|
category: 'facebook',
|
||||||
|
description: 'Publish a photo post to a Facebook Page using a public image URL',
|
||||||
|
when_to_use: 'User wants to post an image or photo to their Facebook Page.',
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['image_url'],
|
||||||
|
properties: {
|
||||||
|
image_url: { type: 'string', description: 'Publicly accessible URL of the image' },
|
||||||
|
caption: { type: 'string', description: 'Post caption text' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: { type: 'boolean' },
|
||||||
|
post_id: { type: 'string' },
|
||||||
|
photo_id: { type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
examples: [{ image_url: 'https://example.com/image.jpg', caption: 'New post!', account: 'default' }],
|
||||||
|
},
|
||||||
|
|
||||||
// ── Twitter/X tools ────────────────────────────────────────────────────
|
// ── Twitter/X tools ────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
name: 'twitter_search_tweets',
|
name: 'twitter_search_tweets',
|
||||||
@@ -1526,6 +1696,10 @@ export function getManifest(serverUrl: string, authEnabled: boolean) {
|
|||||||
description: 'Twitter/X search and profile lookup (read-only on free tier)',
|
description: 'Twitter/X search and profile lookup (read-only on free tier)',
|
||||||
icon: '🐦',
|
icon: '🐦',
|
||||||
},
|
},
|
||||||
|
facebook: {
|
||||||
|
description: 'Facebook Page posting and management via Graph API',
|
||||||
|
icon: '📘',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function decrypt(ciphertext: string): string {
|
|||||||
return decipher.update(encrypted) + decipher.final('utf8');
|
return decipher.update(encrypted) + decipher.final('utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Platform = 'email' | 'whatsapp' | 'linkedin' | 'telegram' | 'discord' | 'instagram' | 'twitter' | 'tiktok' | 'snapchat' | 'obsidian';
|
export type Platform = 'email' | 'whatsapp' | 'linkedin' | 'telegram' | 'discord' | 'instagram' | 'twitter' | 'tiktok' | 'snapchat' | 'facebook' | 'obsidian';
|
||||||
|
|
||||||
export interface EmailCredentials {
|
export interface EmailCredentials {
|
||||||
host: string;
|
host: string;
|
||||||
|
|||||||
84
src/tools.ts
84
src/tools.ts
@@ -11,6 +11,7 @@ import { getProfile as getInstagramProfile, getMedia as getInstagramMedia, creat
|
|||||||
import { searchTweets, getUserProfile, getUserTweets, createTweet } from './clients/twitter.js';
|
import { searchTweets, getUserProfile, getUserTweets, createTweet } from './clients/twitter.js';
|
||||||
import { getUserProfile as getTikTokProfile, getUserVideos, createVideo, getVideoStatus } from './clients/tiktok.js';
|
import { getUserProfile as getTikTokProfile, getUserVideos, createVideo, getVideoStatus } from './clients/tiktok.js';
|
||||||
import { getMe as getSnapchatMe, createSnap, getAdAccounts } from './clients/snapchat.js';
|
import { getMe as getSnapchatMe, createSnap, getAdAccounts } from './clients/snapchat.js';
|
||||||
|
import { getPage, getPosts, createPost as createFacebookPost, createPhotoPost } from './clients/facebook.js';
|
||||||
|
|
||||||
const ACCOUNT_PARAM = {
|
const ACCOUNT_PARAM = {
|
||||||
account: {
|
account: {
|
||||||
@@ -610,6 +611,59 @@ export const tools: Tool[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Facebook tools ───────────────────────────────────────────
|
||||||
|
{
|
||||||
|
name: 'facebook_get_page',
|
||||||
|
description:
|
||||||
|
'Get a Facebook Page profile including name, category, fan count, and follower count.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_get_posts',
|
||||||
|
description:
|
||||||
|
'Get recent posts from a Facebook Page feed.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
limit: { type: 'number', description: 'Max posts to return (default: 10)' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_create_post',
|
||||||
|
description:
|
||||||
|
'Publish a text post (optionally with a link) to a Facebook Page.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['message'],
|
||||||
|
properties: {
|
||||||
|
message: { type: 'string', description: 'Post text content' },
|
||||||
|
link: { type: 'string', description: 'Optional URL to attach to the post' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook_create_photo_post',
|
||||||
|
description:
|
||||||
|
'Publish a photo to a Facebook Page using a publicly accessible image URL. Creates a photo post with optional caption.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['image_url'],
|
||||||
|
properties: {
|
||||||
|
image_url: { type: 'string', description: 'Publicly accessible URL of the image to post' },
|
||||||
|
caption: { type: 'string', description: 'Post caption text' },
|
||||||
|
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function acct(args: Record<string, unknown>): Account {
|
function acct(args: Record<string, unknown>): Account {
|
||||||
@@ -924,6 +978,36 @@ export async function handleToolCall(
|
|||||||
}, customer);
|
}, customer);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// ── Facebook ────────────────────────────────────────────────
|
||||||
|
case 'facebook_get_page':
|
||||||
|
result = await getPage({
|
||||||
|
account: args.account as string | undefined,
|
||||||
|
}, customer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'facebook_get_posts':
|
||||||
|
result = await getPosts({
|
||||||
|
limit: (args.limit as number) ?? 10,
|
||||||
|
account: args.account as string | undefined,
|
||||||
|
}, customer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'facebook_create_post':
|
||||||
|
result = await createFacebookPost({
|
||||||
|
message: args.message as string,
|
||||||
|
link: args.link as string | undefined,
|
||||||
|
account: args.account as string | undefined,
|
||||||
|
}, customer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'facebook_create_photo_post':
|
||||||
|
result = await createPhotoPost({
|
||||||
|
image_url: args.image_url as string,
|
||||||
|
caption: args.caption as string | undefined,
|
||||||
|
account: args.account as string | undefined,
|
||||||
|
}, customer);
|
||||||
|
break;
|
||||||
|
|
||||||
// Legacy Yahoo-prefixed names — keep working for any cached Claude sessions
|
// Legacy Yahoo-prefixed names — keep working for any cached Claude sessions
|
||||||
case 'yahoo_get_profile':
|
case 'yahoo_get_profile':
|
||||||
result = await getProfile('yahoo');
|
result = await getProfile('yahoo');
|
||||||
|
|||||||
Reference in New Issue
Block a user