Skip to content

Commit 5151796

Browse files
committed
fix(website): guard tweet entity arrays
1 parent 8b9092e commit 5151796

3 files changed

Lines changed: 93 additions & 1 deletion

File tree

website/src/components/ui/tweet-card.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { getTweet, type Tweet } from 'react-tweet/api';
1010

1111
import { cn } from '../../lib/utils';
12+
import { normalizeTweetForReactTweet } from './tweet-utils';
1213

1314
interface TwitterIconProps {
1415
className?: string;
@@ -231,7 +232,7 @@ export const MagicTweet = ({
231232
components?: TwitterComponents;
232233
className?: string;
233234
}) => {
234-
const enrichedTweet = enrichTweet(tweet);
235+
const enrichedTweet = enrichTweet(normalizeTweetForReactTweet(tweet));
235236
return (
236237
<div
237238
className={cn(
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import type { Tweet } from 'react-tweet/api';
4+
import { enrichTweet } from '../../../node_modules/react-tweet/dist/utils.js';
5+
6+
import { normalizeTweetForReactTweet } from './tweet-utils';
7+
8+
const tweetWithSparseEntities = {
9+
__typename: 'Tweet',
10+
favorite_count: 5,
11+
lang: 'en',
12+
possibly_sensitive: false,
13+
created_at: '2025-01-08T13:21:01.000Z',
14+
display_text_range: [0, 24],
15+
entities: {
16+
urls: [
17+
{
18+
display_url: 'example.com',
19+
expanded_url: 'https://example.com',
20+
indices: [13, 24],
21+
url: 'https://t.co/example',
22+
},
23+
],
24+
},
25+
id_str: '1876982441312751667',
26+
text: 'Tianji works https://t.co/example',
27+
user: {
28+
id_str: '95831222',
29+
name: 'Tianji User',
30+
screen_name: 'tianji_user',
31+
is_blue_verified: false,
32+
profile_image_shape: 'Circle',
33+
verified: false,
34+
profile_image_url_https: 'https://example.com/avatar.jpg',
35+
},
36+
edit_control: {
37+
edit_tweet_ids: ['1876982441312751667'],
38+
editable_until_msecs: '1736346061000',
39+
edits_remaining: '5',
40+
is_edit_eligible: false,
41+
},
42+
mediaDetails: [],
43+
conversation_count: 2,
44+
news_action_type: 'conversation',
45+
isEdited: false,
46+
isStaleEdit: false,
47+
} as unknown as Tweet;
48+
49+
test('fills missing tweet entity arrays before react-tweet enrichment', () => {
50+
const normalized = normalizeTweetForReactTweet(tweetWithSparseEntities);
51+
52+
assert.doesNotThrow(() => enrichTweet(normalized));
53+
assert.deepEqual(normalized.entities.hashtags, []);
54+
assert.deepEqual(normalized.entities.user_mentions, []);
55+
assert.deepEqual(normalized.entities.symbols, []);
56+
assert.equal('media' in normalized.entities, false);
57+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Tweet, TweetEntities } from 'react-tweet/api';
2+
3+
export function normalizeTweetForReactTweet<T extends Tweet>(tweet: T): T {
4+
return {
5+
...tweet,
6+
entities: normalizeTweetEntities(tweet.entities),
7+
quoted_tweet: tweet.quoted_tweet
8+
? {
9+
...tweet.quoted_tweet,
10+
entities: normalizeTweetEntities(tweet.quoted_tweet.entities),
11+
}
12+
: tweet.quoted_tweet,
13+
};
14+
}
15+
16+
function normalizeTweetEntities(
17+
entities: Partial<TweetEntities> = {}
18+
): TweetEntities {
19+
const media =
20+
Array.isArray(entities.media) && entities.media.length > 0
21+
? entities.media
22+
: undefined;
23+
24+
return {
25+
...entities,
26+
hashtags: Array.isArray(entities.hashtags) ? entities.hashtags : [],
27+
urls: Array.isArray(entities.urls) ? entities.urls : [],
28+
user_mentions: Array.isArray(entities.user_mentions)
29+
? entities.user_mentions
30+
: [],
31+
symbols: Array.isArray(entities.symbols) ? entities.symbols : [],
32+
...(media ? { media } : {}),
33+
};
34+
}

0 commit comments

Comments
 (0)