| | |
| | |
| | |
| |
|
| |
|
| | import { z } from 'zod';
|
| | import { browserManager } from '../browser.js';
|
| | import { config } from '../config.js';
|
| |
|
| |
|
| | 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'),
|
| | });
|
| |
|
| |
|
| | 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"]',
|
| | };
|
| |
|
| | |
| | |
| |
|
| | 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');
|
| | }
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | 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);
|
| |
|
| |
|
| | const results = await page.$$eval(
|
| | '[role="listbox"] [role="option"]',
|
| | (options) => options.map((opt) => opt.textContent?.trim() || '').filter(Boolean)
|
| | );
|
| |
|
| |
|
| | await searchInput.fill('');
|
| | await page.keyboard.press('Escape');
|
| |
|
| | return { results };
|
| | } catch (error) {
|
| | return { results: [], error: String(error) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function openConversation(conversationName: string): Promise<{ success: boolean; error?: string }> {
|
| | try {
|
| | const page = await browserManager.getPage();
|
| |
|
| |
|
| | 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);
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | const attachButton = await page.$(SELECTORS.attachButton);
|
| | if (attachButton) {
|
| | await attachButton.click();
|
| | }
|
| |
|
| |
|
| | 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);
|
| |
|
| |
|
| | await page.keyboard.press('Enter');
|
| | await page.waitForTimeout(1000);
|
| |
|
| | return { success: true };
|
| | } catch (error) {
|
| | return { success: false, error: String(error) };
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | 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();
|
| |
|
| |
|
| | 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) };
|
| | }
|
| | }
|
| |
|