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