algorembrant's picture
Upload 44 files
668d995 verified
/**
* Messenger-specific MCP Tools
* Provides tools for interacting with Facebook Messenger
*/
import { z } from 'zod';
import { browserManager } from '../browser.js';
import { config } from '../config.js';
// Tool schemas
export const sendMessageSchema = z.object({
conversationName: z.string().describe('Name of the conversation/contact to send message to'),
message: z.string().describe('The message text to send'),
});
export const readMessagesSchema = z.object({
count: z.number().optional().default(20).describe('Number of messages to read (default: 20)'),
});
export const searchConversationSchema = z.object({
query: z.string().describe('Search query for finding a conversation'),
});
export const sendFileSchema = z.object({
conversationName: z.string().describe('Name of the conversation/contact'),
filePath: z.string().describe('Absolute path to the file to send'),
});
// Messenger selectors (may need updates as Messenger changes)
const SELECTORS = {
searchInput: '[aria-label="Search Messenger"]',
conversationList: '[role="navigation"] [role="row"]',
messageInput: '[aria-label="Message"]',
sendButton: '[aria-label="Press enter to send"]',
messageList: '[role="main"] [role="row"]',
fileInput: 'input[type="file"]',
attachButton: '[aria-label="Attach a file"]',
};
/**
* Navigate to Messenger and ensure we're logged in
*/
export async function ensureMessengerOpen(): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
const currentUrl = page.url();
const configuredUrl = config.messenger.url.replace(/\/$/, '');
const currentUrlClean = currentUrl.replace(/\/$/, '');
if (currentUrlClean !== configuredUrl) {
await page.goto(config.messenger.url);
await page.waitForLoadState('networkidle');
}
// Check if we're on the login page
const isLoginPage = await page.$('input[name="email"]');
if (isLoginPage) {
return {
success: false,
error: 'Please log in to Messenger manually in the browser window, then try again.'
};
}
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* List recent conversations
*/
export async function listConversations(): Promise<{ conversations: string[]; error?: string }> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { conversations: [], error: messengerStatus.error };
}
const page = await browserManager.getPage();
// Get conversation names from the sidebar
const conversations = await page.$$eval(
SELECTORS.conversationList,
(rows) => rows.slice(0, 20).map((row) => {
const nameEl = row.querySelector('[dir="auto"]');
return nameEl?.textContent?.trim() || '';
}).filter(Boolean)
);
return { conversations };
} catch (error) {
return { conversations: [], error: String(error) };
}
}
/**
* Search for a conversation
*/
export async function searchConversation(query: string): Promise<{ results: string[]; error?: string }> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { results: [], error: messengerStatus.error };
}
const page = await browserManager.getPage();
// Click search and type query
const searchInput = await page.$(SELECTORS.searchInput);
if (!searchInput) {
return { results: [], error: 'Could not find search input' };
}
await searchInput.click();
await searchInput.fill(query);
await page.waitForTimeout(1000); // Wait for search results
// Get search results
const results = await page.$$eval(
'[role="listbox"] [role="option"]',
(options) => options.map((opt) => opt.textContent?.trim() || '').filter(Boolean)
);
// Clear search
await searchInput.fill('');
await page.keyboard.press('Escape');
return { results };
} catch (error) {
return { results: [], error: String(error) };
}
}
/**
* Open a specific conversation
*/
async function openConversation(conversationName: string): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
// Search for the conversation
const searchInput = await page.$(SELECTORS.searchInput);
if (!searchInput) {
return { success: false, error: 'Could not find search input' };
}
await searchInput.click();
await searchInput.fill(conversationName);
await page.waitForTimeout(1000);
// Click the first result
const firstResult = await page.$('[role="listbox"] [role="option"]');
if (!firstResult) {
return { success: false, error: `Could not find conversation: ${conversationName}` };
}
await firstResult.click();
await page.waitForTimeout(500);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Send a message to a conversation
*/
export async function sendMessage(
conversationName: string,
message: string
): Promise<{ success: boolean; error?: string }> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { success: false, error: messengerStatus.error };
}
const openResult = await openConversation(conversationName);
if (!openResult.success) {
return openResult;
}
const page = await browserManager.getPage();
// Find message input and send
const messageInput = await page.$(SELECTORS.messageInput);
if (!messageInput) {
return { success: false, error: 'Could not find message input' };
}
await messageInput.click();
await messageInput.fill(message);
await page.keyboard.press('Enter');
await page.waitForTimeout(500);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Read messages from current conversation
*/
export async function readMessages(count: number = 20): Promise<{ messages: string[]; error?: string }> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { messages: [], error: messengerStatus.error };
}
const page = await browserManager.getPage();
// Get messages from the current conversation
const messages = await page.$$eval(
SELECTORS.messageList,
(rows, limit) => rows.slice(-limit).map((row) => {
const text = row.textContent?.trim() || '';
return text;
}).filter(Boolean),
count
);
return { messages };
} catch (error) {
return { messages: [], error: String(error) };
}
}
/**
* Send a file to a conversation
*/
export async function sendFile(
conversationName: string,
filePath: string
): Promise<{ success: boolean; error?: string }> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { success: false, error: messengerStatus.error };
}
const openResult = await openConversation(conversationName);
if (!openResult.success) {
return openResult;
}
const page = await browserManager.getPage();
// Click attach button and upload file
const attachButton = await page.$(SELECTORS.attachButton);
if (attachButton) {
await attachButton.click();
}
// Set file input
const fileInput = await page.$(SELECTORS.fileInput);
if (!fileInput) {
return { success: false, error: 'Could not find file input' };
}
await fileInput.setInputFiles(filePath);
await page.waitForTimeout(1000);
// Send the file
await page.keyboard.press('Enter');
await page.waitForTimeout(1000);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Get information about the current conversation
*/
export async function getConversationInfo(): Promise<{
name?: string;
participants?: string[];
error?: string;
}> {
try {
const messengerStatus = await ensureMessengerOpen();
if (!messengerStatus.success) {
return { error: messengerStatus.error };
}
const page = await browserManager.getPage();
// Get conversation name from header
const nameEl = await page.$('[role="main"] h1, [role="main"] [dir="auto"]');
const name = await nameEl?.textContent() || undefined;
return { name, participants: name ? [name] : [] };
} catch (error) {
return { error: String(error) };
}
}