import { TwitterAuth } from './auth'; import { updateCookieJar } from './requests'; export interface DirectMessage { id: string; text: string; senderId: string; recipientId: string; createdAt: string; mediaUrls?: string[]; senderScreenName?: string; recipientScreenName?: string; } export interface DirectMessageConversation { conversationId: string; messages: DirectMessage[]; participants: { id: string; screenName: string; }[]; } export interface DirectMessageEvent { id: string; type: string; message_create: { sender_id: string; target: { recipient_id: string; }; message_data: { text: string; created_at: string; entities?: { urls?: Array<{ url: string; expanded_url: string; display_url: string; }>; media?: Array<{ url: string; type: string; }>; }; }; }; } export interface DirectMessagesResponse { conversations: DirectMessageConversation[]; users: TwitterUser[]; cursor?: string; lastSeenEventId?: string; trustedLastSeenEventId?: string; untrustedLastSeenEventId?: string; inboxTimelines?: { trusted?: { status: string; minEntryId?: string; }; untrusted?: { status: string; minEntryId?: string; }; }; userId: string; } export interface TwitterUser { id: string; screenName: string; name: string; profileImageUrl: string; description?: string; verified?: boolean; protected?: boolean; followersCount?: number; friendsCount?: number; } export interface SendDirectMessageResponse { entries: { message: { id: string; time: string; affects_sort: boolean; conversation_id: string; message_data: { id: string; time: string; recipient_id: string; sender_id: string; text: string; }; }; }[]; users: Record; } function parseDirectMessageConversations( data: any, userId: string, ): DirectMessagesResponse { try { const inboxState = data?.inbox_initial_state; const conversations = inboxState?.conversations || {}; const entries = inboxState?.entries || []; const users = inboxState?.users || {}; // Parse users first const parsedUsers: TwitterUser[] = Object.values(users).map( (user: any) => ({ id: user.id_str, screenName: user.screen_name, name: user.name, profileImageUrl: user.profile_image_url_https, description: user.description, verified: user.verified, protected: user.protected, followersCount: user.followers_count, friendsCount: user.friends_count, }), ); // Group messages by conversation_id const messagesByConversation: Record = {}; entries.forEach((entry: any) => { if (entry.message) { const convId = entry.message.conversation_id; if (!messagesByConversation[convId]) { messagesByConversation[convId] = []; } messagesByConversation[convId].push(entry.message); } }); // Convert to DirectMessageConversation array const parsedConversations = Object.entries(conversations).map( ([convId, conv]: [string, any]) => { const messages = messagesByConversation[convId] || []; // Sort messages by time in ascending order messages.sort((a, b) => Number(a.time) - Number(b.time)); return { conversationId: convId, messages: parseDirectMessages(messages, users), participants: conv.participants.map((p: any) => ({ id: p.user_id, screenName: users[p.user_id]?.screen_name || p.user_id, })), }; }, ); return { conversations: parsedConversations, users: parsedUsers, cursor: inboxState?.cursor, lastSeenEventId: inboxState?.last_seen_event_id, trustedLastSeenEventId: inboxState?.trusted_last_seen_event_id, untrustedLastSeenEventId: inboxState?.untrusted_last_seen_event_id, inboxTimelines: { trusted: inboxState?.inbox_timelines?.trusted && { status: inboxState.inbox_timelines.trusted.status, minEntryId: inboxState.inbox_timelines.trusted.min_entry_id, }, untrusted: inboxState?.inbox_timelines?.untrusted && { status: inboxState.inbox_timelines.untrusted.status, minEntryId: inboxState.inbox_timelines.untrusted.min_entry_id, }, }, userId, }; } catch (error) { console.error('Error parsing DM conversations:', error); return { conversations: [], users: [], userId, }; } } function parseDirectMessages(messages: any[], users: any): DirectMessage[] { try { return messages.map((msg: any) => ({ id: msg.message_data.id, text: msg.message_data.text, senderId: msg.message_data.sender_id, recipientId: msg.message_data.recipient_id, createdAt: msg.message_data.time, mediaUrls: extractMediaUrls(msg.message_data), senderScreenName: users[msg.message_data.sender_id]?.screen_name, recipientScreenName: users[msg.message_data.recipient_id]?.screen_name, })); } catch (error) { console.error('Error parsing DMs:', error); return []; } } function extractMediaUrls(messageData: any): string[] | undefined { const urls: string[] = []; // Extract URLs from entities if they exist if (messageData.entities?.urls) { messageData.entities.urls.forEach((url: any) => { urls.push(url.expanded_url); }); } // Extract media URLs if they exist if (messageData.entities?.media) { messageData.entities.media.forEach((media: any) => { urls.push(media.media_url_https || media.media_url); }); } return urls.length > 0 ? urls : undefined; } export async function getDirectMessageConversations( userId: string, auth: TwitterAuth, cursor?: string, ): Promise { if (!auth.isLoggedIn()) { throw new Error('Authentication required to fetch direct messages'); } const url = 'https://twitter.com/i/api/graphql/7s3kOODhC5vgXlO0OlqYdA/DMInboxTimeline'; const messageListUrl = 'https://x.com/i/api/1.1/dm/inbox_initial_state.json'; const params = new URLSearchParams(); if (cursor) { params.append('cursor', cursor); } const finalUrl = `${messageListUrl}${ params.toString() ? '?' + params.toString() : '' }`; const cookies = await auth.cookieJar().getCookies(url); const xCsrfToken = cookies.find((cookie) => cookie.key === 'ct0'); const headers = new Headers({ authorization: `Bearer ${(auth as any).bearerToken}`, cookie: await auth.cookieJar().getCookieString(url), 'content-type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; Nokia G20) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.88 Mobile Safari/537.36', 'x-guest-token': (auth as any).guestToken, 'x-twitter-auth-type': 'OAuth2Client', 'x-twitter-active-user': 'yes', 'x-csrf-token': xCsrfToken?.value as string, }); const response = await fetch(finalUrl, { method: 'GET', headers, }); await updateCookieJar(auth.cookieJar(), response.headers); if (!response.ok) { throw new Error(await response.text()); } // parse the response const data = await response.json(); return parseDirectMessageConversations(data, userId); } export async function sendDirectMessage( auth: TwitterAuth, conversation_id: string, text: string, ): Promise { if (!auth.isLoggedIn()) { throw new Error('Authentication required to send direct messages'); } const url = 'https://twitter.com/i/api/graphql/7s3kOODhC5vgXlO0OlqYdA/DMInboxTimeline'; const messageDmUrl = 'https://x.com/i/api/1.1/dm/new2.json'; const cookies = await auth.cookieJar().getCookies(url); const xCsrfToken = cookies.find((cookie) => cookie.key === 'ct0'); const headers = new Headers({ authorization: `Bearer ${(auth as any).bearerToken}`, cookie: await auth.cookieJar().getCookieString(url), 'content-type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; Nokia G20) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.88 Mobile Safari/537.36', 'x-guest-token': (auth as any).guestToken, 'x-twitter-auth-type': 'OAuth2Client', 'x-twitter-active-user': 'yes', 'x-csrf-token': xCsrfToken?.value as string, }); const payload = { conversation_id: `${conversation_id}`, recipient_ids: false, text: text, cards_platform: 'Web-12', include_cards: 1, include_quote_count: true, dm_users: false, }; const response = await fetch(messageDmUrl, { method: 'POST', headers, body: JSON.stringify(payload), }); await updateCookieJar(auth.cookieJar(), response.headers); if (!response.ok) { throw new Error(await response.text()); } return await response.json(); }