agent-twitter-summary
Version:
A twitter client for agents
329 lines (291 loc) • 9.09 kB
text/typescript
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<string, TwitterUser>;
}
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<string, any[]> = {};
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<DirectMessagesResponse> {
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<SendDirectMessageResponse> {
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();
}