@postfetch/core 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2315,7 +2315,7 @@ function twitterMetadata(tweet) {
2315
2315
  extra.parentTweet = { id: parentId, metadata: twitterMetadata(parent) };
2316
2316
  }
2317
2317
  return {
2318
- text: twitterText(tweet) ?? undefined,
2318
+ text: cleanTwitterText(twitterText(tweet), tweet) ?? undefined,
2319
2319
  author: user ? {
2320
2320
  handle: string(user.screen_name) ?? undefined,
2321
2321
  name: string(user.name) ?? undefined,
@@ -2339,6 +2339,23 @@ function noteText(tweet) {
2339
2339
  function twitterText(tweet) {
2340
2340
  return noteText(tweet) ?? string(tweet.full_text) ?? string(tweet.text);
2341
2341
  }
2342
+ function cleanTwitterText(text, tweet) {
2343
+ if (text === null)
2344
+ return null;
2345
+ const entities = object(tweet.entities) ? tweet.entities : null;
2346
+ const media = [
2347
+ ...entities && Array.isArray(entities.media) ? entities.media : [],
2348
+ ...Array.isArray(tweet.mediaDetails) ? tweet.mediaDetails : []
2349
+ ];
2350
+ const urls = new Set(media.filter(object).map((entry) => string(entry.url)).filter(Boolean));
2351
+ while (true) {
2352
+ const trailing = text.match(/(?:^|\s)(https?:\/\/\S+)\s*$/);
2353
+ if (!trailing || !urls.has(trailing[1]))
2354
+ break;
2355
+ text = text.slice(0, trailing.index).trimEnd();
2356
+ }
2357
+ return text.replace(/&(?:gt|lt);/g, (entity) => entity === "&gt;" ? ">" : "<");
2358
+ }
2342
2359
  function needsFullText(tweet) {
2343
2360
  if (noteText(tweet) || string(tweet.full_text)) {
2344
2361
  return false;
@@ -2569,7 +2586,7 @@ function twitterComment(reply, id) {
2569
2586
  url: `https://x.com/i/status/${id}`,
2570
2587
  items,
2571
2588
  metadata: {
2572
- text: string(reply.text) ?? undefined,
2589
+ text: cleanTwitterText(string(reply.text), reply) ?? undefined,
2573
2590
  author: {
2574
2591
  handle: string(author2.screen_name) ?? undefined,
2575
2592
  name: string(author2.name) ?? undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postfetch/core",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Zero-dependency typed core that turns Facebook, Instagram, LinkedIn, Pinterest, Reddit, SoundCloud, TikTok, X and YouTube post URLs into media files.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/twitter.ts CHANGED
@@ -67,7 +67,7 @@ export function twitterMetadata(tweet: Json): PostMetadata & { extra?: TwitterEx
67
67
  extra.parentTweet = { id: parentId, metadata: twitterMetadata(parent) };
68
68
  }
69
69
  return {
70
- text: twitterText(tweet) ?? undefined,
70
+ text: cleanTwitterText(twitterText(tweet), tweet) ?? undefined,
71
71
  author: user
72
72
  ? {
73
73
  handle: string(user.screen_name) ?? undefined,
@@ -96,6 +96,24 @@ function twitterText(tweet: Json): string | null {
96
96
  return noteText(tweet) ?? string(tweet.full_text) ?? string(tweet.text);
97
97
  }
98
98
 
99
+ // X serializes attachments as URLs in text. Only remove a trailing URL when
100
+ // the same post explicitly identifies it as media; ordinary links stay intact.
101
+ function cleanTwitterText(text: string | null, tweet: Json): string | null {
102
+ if (text === null) return null;
103
+ const entities = object(tweet.entities) ? tweet.entities : null;
104
+ const media = [
105
+ ...(entities && Array.isArray(entities.media) ? entities.media : []),
106
+ ...(Array.isArray(tweet.mediaDetails) ? tweet.mediaDetails : []),
107
+ ];
108
+ const urls = new Set(media.filter(object).map((entry) => string(entry.url)).filter(Boolean));
109
+ while (true) {
110
+ const trailing: RegExpMatchArray | null = text.match(/(?:^|\s)(https?:\/\/\S+)\s*$/);
111
+ if (!trailing || !urls.has(trailing[1])) break;
112
+ text = text.slice(0, trailing.index).trimEnd();
113
+ }
114
+ return text.replace(/&(?:gt|lt);/g, (entity) => entity === "&gt;" ? ">" : "<");
115
+ }
116
+
99
117
  function needsFullText(tweet: Json): boolean {
100
118
  if (noteText(tweet) || string(tweet.full_text)) {
101
119
  return false;
@@ -350,7 +368,7 @@ function twitterComment(reply: Json, id: string): PostComment {
350
368
  url: `https://x.com/i/status/${id}`,
351
369
  items,
352
370
  metadata: {
353
- text: string(reply.text) ?? undefined,
371
+ text: cleanTwitterText(string(reply.text), reply) ?? undefined,
354
372
  author: {
355
373
  handle: string(author.screen_name) ?? undefined,
356
374
  name: string(author.name) ?? undefined,