feat: include raw tweet data in mapping
This commit is contained in:
@@ -227,6 +227,8 @@ export interface TweetData {
|
||||
quotedTweet?: TweetData;
|
||||
// Media attachments (photos, videos, GIFs)
|
||||
media?: TweetMedia[];
|
||||
// Raw GraphQL tweet result (only when includeRaw is enabled)
|
||||
_raw?: GraphqlTweetResult;
|
||||
}
|
||||
|
||||
export interface GetTweetResult {
|
||||
|
||||
@@ -224,7 +224,19 @@ export function unwrapTweetResult(result: GraphqlTweetResult | undefined): Graph
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mapTweetResult(result: GraphqlTweetResult | undefined, quoteDepth: number): TweetData | undefined {
|
||||
export interface MapTweetResultOptions {
|
||||
quoteDepth: number;
|
||||
includeRaw?: boolean;
|
||||
}
|
||||
|
||||
export function mapTweetResult(
|
||||
result: GraphqlTweetResult | undefined,
|
||||
quoteDepthOrOptions: number | MapTweetResultOptions,
|
||||
): TweetData | undefined {
|
||||
const options: MapTweetResultOptions =
|
||||
typeof quoteDepthOrOptions === 'number' ? { quoteDepth: quoteDepthOrOptions } : quoteDepthOrOptions;
|
||||
const { quoteDepth, includeRaw = false } = options;
|
||||
|
||||
const userResult = result?.core?.user_results?.result;
|
||||
const userLegacy = userResult?.legacy;
|
||||
const userCore = userResult?.core;
|
||||
@@ -244,13 +256,13 @@ export function mapTweetResult(result: GraphqlTweetResult | undefined, quoteDept
|
||||
if (quoteDepth > 0) {
|
||||
const quotedResult = unwrapTweetResult(result.quoted_status_result?.result);
|
||||
if (quotedResult) {
|
||||
quotedTweet = mapTweetResult(quotedResult, quoteDepth - 1);
|
||||
quotedTweet = mapTweetResult(quotedResult, { quoteDepth: quoteDepth - 1, includeRaw });
|
||||
}
|
||||
}
|
||||
|
||||
const media = extractMedia(result);
|
||||
|
||||
return {
|
||||
const tweetData: TweetData = {
|
||||
id: result.rest_id,
|
||||
text,
|
||||
createdAt: result.legacy?.created_at,
|
||||
@@ -267,6 +279,12 @@ export function mapTweetResult(result: GraphqlTweetResult | undefined, quoteDept
|
||||
quotedTweet,
|
||||
media,
|
||||
};
|
||||
|
||||
if (includeRaw) {
|
||||
(tweetData as TweetData & { _raw: GraphqlTweetResult })._raw = result;
|
||||
}
|
||||
|
||||
return tweetData;
|
||||
}
|
||||
|
||||
export function findTweetInInstructions(
|
||||
@@ -358,6 +376,11 @@ export function collectTweetResultsFromEntry(entry: {
|
||||
return results;
|
||||
}
|
||||
|
||||
export interface ParseTweetsOptions {
|
||||
quoteDepth: number;
|
||||
includeRaw?: boolean;
|
||||
}
|
||||
|
||||
export function parseTweetsFromInstructions(
|
||||
instructions:
|
||||
| Array<{
|
||||
@@ -400,8 +423,12 @@ export function parseTweetsFromInstructions(
|
||||
}>;
|
||||
}>
|
||||
| undefined,
|
||||
quoteDepth: number,
|
||||
quoteDepthOrOptions: number | ParseTweetsOptions,
|
||||
): TweetData[] {
|
||||
const options: ParseTweetsOptions =
|
||||
typeof quoteDepthOrOptions === 'number' ? { quoteDepth: quoteDepthOrOptions } : quoteDepthOrOptions;
|
||||
const { quoteDepth, includeRaw = false } = options;
|
||||
|
||||
const tweets: TweetData[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
@@ -409,7 +436,7 @@ export function parseTweetsFromInstructions(
|
||||
for (const entry of instruction.entries ?? []) {
|
||||
const results = collectTweetResultsFromEntry(entry);
|
||||
for (const result of results) {
|
||||
const mapped = mapTweetResult(result, quoteDepth);
|
||||
const mapped = mapTweetResult(result, { quoteDepth, includeRaw });
|
||||
if (!mapped || seen.has(mapped.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -185,6 +185,55 @@ describe('TwitterClient lists', () => {
|
||||
expect(result.lists?.[0].isPrivate).toBe(true);
|
||||
});
|
||||
|
||||
it('handles missing mode', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
data: {
|
||||
user: {
|
||||
result: {
|
||||
timeline: {
|
||||
timeline: {
|
||||
instructions: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
content: {
|
||||
itemContent: {
|
||||
list: {
|
||||
id_str: '9999',
|
||||
name: 'No Mode',
|
||||
mode: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const client = new TwitterClient({ cookies: validCookies });
|
||||
const clientPrivate = client as unknown as TwitterClientPrivate;
|
||||
clientPrivate.getCurrentUser = async () => ({
|
||||
success: true,
|
||||
user: { id: '12345', username: 'testuser', name: 'Test User' },
|
||||
});
|
||||
clientPrivate.getListOwnershipsQueryIds = async () => ['test'];
|
||||
|
||||
const result = await client.getOwnedLists(100);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.lists?.[0].isPrivate).toBe(false);
|
||||
});
|
||||
|
||||
it('returns empty array when no lists exist', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user