From 822df5f40d335084c8bca3b91124b267d42b4754 Mon Sep 17 00:00:00 2001 From: stijn-uva <42036349+stijn-uva@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:46:01 +0000 Subject: [PATCH] chore: sync map_item for twitter-import from 4CAT 7cba15f91a5321aa5049bbe8814c3c85009f6d12 --- modules/twitter.js | 374 ++++++++++++++++++++++----------------------- 1 file changed, 185 insertions(+), 189 deletions(-) diff --git a/modules/twitter.js b/modules/twitter.js index 5920ca7..2840506 100644 --- a/modules/twitter.js +++ b/modules/twitter.js @@ -189,10 +189,10 @@ export function capture(response, source_platform_url, source_url) { // === auto-generated by 4cat map_item sync — BLOCK REPLACED AUTOMATICALLY === // (regenerated from datasources/twitter-import/search_twitter.py) function map_item_modern(tweet) { - // Resolve author information - const userResult = (tweet.core && tweet.core.user_results && tweet.core.user_results.result) || {}; + // Resolve author information – user object may be missing + const userResult = (tweet.core?.user_results?.result) || {}; const author = map_user(userResult); - const authorScreenName = author.screen_name || _screen_name_from_media(py_get(tweet, "legacy", {})); + const authorScreenName = author.screen_name || _screen_name_from_media(py_get(tweet, 'legacy') || {}); const authorFullname = author.fullname; const authorAvatarUrl = author.avatar_url; const authorBannerUrl = author.banner_url; @@ -203,211 +203,207 @@ function map_item_modern(tweet) { const authorLocation = author.location; const tweetLink = authorScreenName - ? `https://x.com/${authorScreenName}/status/${py_get(tweet, "id")}` - : `https://x.com/i/web/status/${py_get(tweet, "rest_id")}`; + ? `https://x.com/${authorScreenName}/status/${tweet.id}` + : `https://x.com/i/web/status/${tweet.rest_id}`; - const createdAtStr = py_get(py_get(tweet, "legacy", {}), "created_at"); - const createdDate = new Date(createdAtStr); - const unixTimestamp = Math.floor(createdDate.getTime() / 1000); - const timestamp = formatUtcTimestamp(unixTimestamp); - let withheld = false; + // Parse timestamp – X uses a format compatible with Date parsing + const dateObj = new Date(py_get(tweet, 'legacy').created_at); + const unixSeconds = Math.floor(dateObj.getTime() / 1000); + const timestamp = formatUtcTimestamp(unixSeconds); + let withheld = false; let body = get_full_text(tweet); - const retweetObj = py_get(py_get(tweet, "legacy", {}), "retweeted_status_result"); - let retweetedUser = ""; - if (retweetObj) { - let rtResult = retweetObj.result; - if (rtResult && rtResult.tweet) { - rtResult = rtResult.tweet; + // Retweet handling + const retweet = py_get(tweet, 'legacy').retweeted_status_result; + let retweetedUser = ''; + if (retweet) { + if (retweet.result && retweet.result.tweet) { + retweet.result = retweet.result.tweet; } - const rtUserResult = (rtResult && rtResult.core && rtResult.core.user_results && rtResult.core.user_results.result) || {}; - retweetedUser = map_user(rtUserResult).screen_name || _screen_name_from_media(py_get(rtResult, "legacy", {})); - if (py_get(rtResult, "legacy", {}) && py_get(py_get(rtResult, "legacy", {}), "withheld_scope")) { + const rtResult = retweet.result; + const rtUserResult = (rtResult.core?.user_results?.result) || {}; + retweetedUser = map_user(rtUserResult).screen_name || _screen_name_from_media(rtResult.legacy || {}); + if (rtResult.legacy && rtResult.legacy.withheld_scope) { withheld = true; body = get_full_text(rtResult); } else { - body = "RT @" + retweetedUser + ": " + get_full_text(rtResult); + body = `RT @${retweetedUser}: ${get_full_text(rtResult)}`; } } - let quoteTweet = py_get(tweet, "quoted_status_result"); + // Quote tweet handling + let quoteTweet = tweet.quoted_status_result; if (quoteTweet && quoteTweet.result && quoteTweet.result.tweet) { quoteTweet.result = quoteTweet.result.tweet; } const quoteWithheld = !!(quoteTweet && quoteTweet.result && quoteTweet.result.tombstone); - - let quoteAuthor = ""; - let quoteBody = ""; + let quoteAuthor = ''; + let quoteBody = ''; let quoteImages = []; let quoteVideos = []; if (quoteTweet && !quoteWithheld) { const quoteResult = quoteTweet.result; - const quoteUserResult = (quoteResult && quoteResult.core && quoteResult.core.user_results && quoteResult.core.user_results.result) || {}; - quoteAuthor = map_user(quoteUserResult).screen_name || _screen_name_from_media(py_get(quoteResult, "legacy", {})); + const quoteUserResult = (quoteResult.core?.user_results?.result) || {}; + quoteAuthor = map_user(quoteUserResult).screen_name || _screen_name_from_media(quoteResult.legacy || {}); quoteBody = get_full_text(quoteResult); - const media = get_media(quoteResult); - quoteImages = media[0]; - quoteVideos = media[1]; + const mediaRes = get_media(quoteResult); + quoteImages = mediaRes[0]; + quoteVideos = mediaRes[1]; } - let quoteTweetId = py_get(py_get(tweet, "legacy", {}), "quoted_status_id_str", ""); + // Quote tweet ID – may be present even when the quoted tweet object is missing + let quoteTweetId = py_get(tweet, 'legacy').quoted_status_id_str || ''; if (!quoteTweetId && quoteTweet && quoteTweet.result) { - quoteTweetId = py_get(quoteTweet.result, "rest_id", ""); + quoteTweetId = py_get(quoteTweet.result, 'rest_id') || ''; } - const isQuoteTweet = Boolean(quoteTweetId || quoteTweet || py_get(py_get(tweet, "legacy", {}), "is_quote_status")); + const isQuoteTweet = Boolean(quoteTweetId || quoteTweet || py_get(tweet, 'legacy').is_quote_status); if (!quoteAuthor) { - const permalink = py_get(py_get(py_get(tweet, "legacy", {}), "quoted_status_permalink", {}), "expanded", ""); - quoteAuthor = _screen_name_from_url(permalink); + quoteAuthor = _screen_name_from_url(py_get(py_get(tweet, 'legacy').quoted_status_permalink, 'expanded', '')); } - const mediaResult = get_media(tweet); - const images = mediaResult[0]; - const videos = mediaResult[1]; + // Media and entities for the main tweet + const mediaRes = get_media(tweet); + const images = mediaRes[0]; + const videos = mediaRes[1]; const entities = get_entities(tweet); return { - "collected_from_url": normalize_url_encoding(py_get(py_get(tweet, "__import_meta", {}), "source_platform_url", "")), - "id": py_get(tweet, "rest_id"), - "thread_id": py_get(py_get(tweet, "legacy", {}), "conversation_id_str"), - "timestamp": timestamp, - "unix_timestamp": unixTimestamp, - "link": tweetLink, - "body": body, - "author": authorScreenName, - "author_fullname": authorFullname, - "author_id": py_get(py_get(tweet, "legacy", {}), "user_id_str"), - "author_avatar_url": authorAvatarUrl, - "author_banner_url": authorBannerUrl, - "author_followers": authorFollowers, - "author_following": authorFollowing, - "author_bio": authorBio, - "author_location": authorLocation, - "verified": authorVerified, - "source": strip_tags(py_get(tweet, "source", "")), - "language_guess": py_get(py_get(tweet, "legacy", {}), "lang"), - "possibly_sensitive": (py_get(tweet, "possibly_sensitive", false) || py_get(py_get(tweet, "legacy", {}), "possibly_sensitive", false)) ? "yes" : "no", - "retweet_count": py_get(py_get(tweet, "legacy", {}), "retweet_count"), - "reply_count": py_get(py_get(tweet, "legacy", {}), "reply_count"), - "like_count": py_get(py_get(tweet, "legacy", {}), "favorite_count"), - "quote_count": py_get(py_get(tweet, "legacy", {}), "quote_count"), - "impression_count": py_get(py_get(tweet, "views", {}), "count", ""), - "is_retweet": retweetObj ? "yes" : "no", - "retweeted_user": retweetedUser, - "is_quote_tweet": isQuoteTweet ? "yes" : "no", - "quote_tweet_id": quoteTweetId, - "quote_author": quoteAuthor, - "quote_body": quoteBody, - "quote_images": quoteImages.join(","), - "quote_videos": quoteVideos.join(","), - "is_quote_withheld": quoteWithheld ? "yes" : "no", - "is_reply": String(py_get(py_get(tweet, "legacy", {}), "conversation_id_str")) !== String(py_get(tweet, "rest_id")) ? "yes" : "no", - "replied_author": py_get(py_get(tweet, "legacy", {}), "in_reply_to_screen_name", ""), - "is_withheld": withheld ? "yes" : "no", - "hashtags": (entities.hashtags || []).map(h => h.text).join(","), - "urls": (entities.urls || []).map(u => u.expanded_url || u.display_url).join(","), - "images": images.join(","), - "videos": videos.join(","), - "mentions": (entities.user_mentions || []).map(m => m.screen_name).join(","), - "long_lat": py_get(py_get(tweet, "legacy", {}), "place") ? get_centroid(py_get(py_get(py_get(tweet, "legacy", {}), "place", {}), "bounding_box", {}).coordinates) : "", - "place_name": py_get(py_get(py_get(tweet, "legacy", {}), "place", {}), "full_name", "") + collected_from_url: normalize_url_encoding(py_get(item, '__import_meta', {}).source_platform_url || ''), + id: tweet.rest_id, + thread_id: py_get(tweet, 'legacy').conversation_id_str, + timestamp: timestamp, + unix_timestamp: unixSeconds, + link: tweetLink, + body: body, + author: authorScreenName, + author_fullname: authorFullname, + author_id: py_get(tweet, 'legacy').user_id_str, + author_avatar_url: authorAvatarUrl, + author_banner_url: authorBannerUrl, + author_followers: authorFollowers, + author_following: authorFollowing, + author_bio: authorBio, + author_location: authorLocation, + verified: authorVerified, + source: strip_tags(py_get(tweet, 'source', '')), + language_guess: py_get(tweet, 'legacy').lang, + possibly_sensitive: (tweet.possibly_sensitive || py_get(tweet, 'legacy').possibly_sensitive) ? 'yes' : 'no', + retweet_count: py_get(tweet, 'legacy').retweet_count, + reply_count: py_get(tweet, 'legacy').reply_count, + like_count: py_get(tweet, 'legacy').favorite_count, + quote_count: py_get(tweet, 'legacy').quote_count, + impression_count: py_get(py_get(tweet, 'views') || {}, 'count', ''), + is_retweet: retweet ? 'yes' : 'no', + retweeted_user: retweetedUser, + is_quote_tweet: isQuoteTweet ? 'yes' : 'no', + quote_tweet_id: quoteTweetId, + quote_author: quoteAuthor, + quote_body: quoteBody, + quote_images: quoteImages.join(','), + quote_videos: quoteVideos.join(','), + is_quote_withheld: quoteWithheld ? 'yes' : 'no', + is_reply: String(py_get(tweet, 'legacy').conversation_id_str) !== String(tweet.rest_id) ? 'yes' : 'no', + replied_author: py_get(tweet, 'legacy').in_reply_to_screen_name || '', + is_withheld: withheld ? 'yes' : 'no', + hashtags: (entities.hashtags || []).map(h => h.text).join(','), + urls: (entities.urls || []).map(u => u.expanded_url || u.display_url).join(','), + images: images.join(','), + videos: videos.join(','), + mentions: (entities.user_mentions || []).map(m => m.screen_name).join(','), + long_lat: py_get(tweet, 'legacy').place ? get_centroid(py_get(tweet, 'legacy').place.bounding_box.coordinates) : '', + place_name: py_get(tweet, 'legacy').place ? py_get(py_get(tweet, 'legacy').place, 'full_name', '') : '' }; } function map_item_legacy(tweet) { - const createdAtStr = py_get(py_get(tweet, "legacy", {}), "created_at"); - const createdDate = new Date(createdAtStr); - const unixTimestamp = Math.floor(createdDate.getTime() / 1000); - const timestamp = formatUtcTimestamp(unixTimestamp); - const tweetId = py_get(py_get(tweet, "legacy", {}), "id_str"); + const dateObj = new Date(py_get(tweet, 'legacy').created_at); + const unixSeconds = Math.floor(dateObj.getTime() / 1000); + const timestamp = formatUtcTimestamp(unixSeconds); + const tweetId = py_get(tweet, 'legacy').id_str; let withheld = false; - const retweetObj = py_get(py_get(tweet, "legacy", {}), "retweeted_status_result"); - if (retweetObj) { - const rtLegacy = py_get(retweetObj, "result", {}); - if (py_get(py_get(rtLegacy, "legacy", {}), "withheld_status")) { + const retweet = py_get(tweet, 'legacy').retweeted_status_result; + if (retweet) { + if (py_get(retweet, 'result').legacy && py_get(retweet, 'result').legacy.withheld_status) { withheld = true; - py_get(tweet, "legacy").full_text = py_get(rtLegacy, "legacy", {}).full_text; + py_get(tweet, 'legacy').full_text = py_get(retweet, 'result').legacy.full_text; } else { - const rtUserScreen = py_get(py_get(py_get(rtLegacy, "core", {}), "user_results", {}), "result", {}).legacy.screen_name; - const tText = "RT @" + rtUserScreen + " " + py_get(rtLegacy, "legacy", {}).full_text; - py_get(tweet, "legacy").full_text = tText; + const rtUserScreen = py_get(retweet, 'result').core.user_results.result.legacy.screen_name; + const tText = `RT @${rtUserScreen} ${py_get(retweet, 'result').legacy.full_text}`; + py_get(tweet, 'legacy').full_text = tText; } } - let quoteTweet = py_get(tweet, "quoted_status_result"); + let quoteTweet = tweet.quoted_status_result; if (quoteTweet && quoteTweet.result && quoteTweet.result.tweet) { quoteTweet.result = quoteTweet.result.tweet; } - let quoteTweetId = py_get(py_get(tweet, "legacy", {}), "quoted_status_id_str", ""); - if (!quoteTweetId && quoteTweet && quoteTweet.result) { - quoteTweetId = py_get(quoteTweet.result, "rest_id", ""); + const quoteTweetId = py_get(tweet, 'legacy').quoted_status_id_str || ''; + let finalQuoteTweetId = quoteTweetId; + if (!finalQuoteTweetId && quoteTweet && quoteTweet.result) { + finalQuoteTweetId = py_get(quoteTweet.result, 'rest_id') || ''; } - const isQuoteTweet = Boolean(quoteTweetId || quoteTweet || py_get(py_get(tweet, "legacy", {}), "is_quote_status")); - const retweetUser = retweetObj - ? py_get(py_get(py_get(py_get(retweetObj, "result", {}), "core", {}), "user_results", {}), "result", {}) - : {}; - let quoteAuthor = ""; + const isQuoteTweet = Boolean(finalQuoteTweetId || quoteTweet || py_get(tweet, 'legacy').is_quote_status); + let quoteAuthor = ''; if (quoteTweet) { - const quoteUser = py_get(py_get(py_get(py_get(quoteTweet, "result", {}), "core", {}), "user_results", {}), "result", {}); - quoteAuthor = py_get(py_get(quoteUser, "legacy", {}), "screen_name", ""); + quoteAuthor = py_get(quoteTweet.result.core.user_results.result.legacy, 'screen_name', ''); } if (!quoteAuthor) { - const permalink = py_get(py_get(py_get(tweet, "legacy", {}), "quoted_status_permalink", {}), "expanded", ""); - quoteAuthor = _screen_name_from_url(permalink); + quoteAuthor = _screen_name_from_url(py_get(py_get(tweet, 'legacy').quoted_status_permalink, 'expanded', '')); } return { - "collected_from_url": normalize_url_encoding(py_get(py_get(tweet, "__import_meta", {}), "source_platform_url", "")), - "id": tweetId, - "thread_id": py_get(py_get(tweet, "legacy", {}), "conversation_id_str"), - "timestamp": timestamp, - "unix_timestamp": unixTimestamp, - "link": `https://x.com/${py_get(tweet, "user", {}).screen_name}/status/${tweetId}`, - "body": py_get(py_get(tweet, "legacy", {}), "full_text"), - "author": py_get(tweet, "user", {}).screen_name, - "author_fullname": py_get(tweet, "user", {}).name, - "author_id": py_get(tweet, "user", {}).id_str, - "author_avatar_url": "", - "author_banner_url": "", - "author_followers": py_get(py_get(tweet, "user", {}), "followers_count", ""), - "author_following": py_get(py_get(tweet, "user", {}), "friends_count", ""), - "author_bio": py_get(py_get(tweet, "user", {}), "description", ""), - "author_location": py_get(py_get(tweet, "user", {}), "location", ""), - "verified": "", - "source": strip_tags(py_get(py_get(tweet, "legacy", {}), "source", "")), - "language_guess": py_get(py_get(tweet, "legacy", {}), "lang"), - "possibly_sensitive": py_get(py_get(tweet, "legacy", {}), "possibly_sensitive") ? "yes" : "no", - "retweet_count": py_get(py_get(tweet, "legacy", {}), "retweet_count"), - "reply_count": py_get(py_get(tweet, "legacy", {}), "reply_count"), - "like_count": py_get(py_get(tweet, "legacy", {}), "favorite_count"), - "quote_count": py_get(py_get(tweet, "legacy", {}), "quote_count"), - "impression_count": py_get(py_get(tweet, "ext_views", {}), "count", ""), - "is_retweet": retweetObj ? "yes" : "no", - "retweeted_user": retweetObj ? py_get(py_get(retweetUser, "legacy", {}), "screen_name", "") : "", - "is_quote_tweet": isQuoteTweet ? "yes" : "no", - "quote_tweet_id": quoteTweetId, - "quote_author": quoteAuthor, - "quote_body": "", - "quote_images": "", - "quote_videos": "", - "is_quote_withheld": "", - "is_reply": String(py_get(py_get(tweet, "legacy", {}), "conversation_id_str")) !== tweetId ? "yes" : "no", - "replied_author": py_get(py_get(tweet, "legacy", {}), "in_reply_to_screen_name", ""), - "is_withheld": withheld ? "yes" : "no", - "hashtags": (py_get(py_get(py_get(tweet, "legacy", {}), "entities", {}), "hashtags", [])).map(h => h.text).join(","), - "urls": (py_get(py_get(py_get(tweet, "legacy", {}), "entities", {}), "urls", [])).map(u => u.expanded_url || u.display_url).join(","), - "images": (py_get(py_get(py_get(tweet, "legacy", {}), "extended_entities", {}), "media", []).filter(m => m.type === "photo").map(m => m.media_url_https)).join(","), - "videos": (py_get(py_get(py_get(tweet, "legacy", {}), "extended_entities", {}), "media", []).filter(m => m.type === "video").map(m => m.video_info.variants[0].url)).join(","), - "mentions": (py_get(py_get(py_get(tweet, "legacy", {}), "entities", {}), "user_mentions", [])).map(m => m.screen_name).join(","), - "long_lat": py_get(py_get(tweet, "legacy", {}), "place") ? get_centroid(py_get(py_get(py_get(tweet, "legacy", {}), "place", {}), "bounding_box", {}).coordinates) : "", - "place_name": py_get(py_get(py_get(tweet, "legacy", {}), "place", {}), "full_name", "") + collected_from_url: normalize_url_encoding(py_get(item, '__import_meta', {}).source_platform_url || ''), + id: tweetId, + thread_id: py_get(tweet, 'legacy').conversation_id_str, + timestamp: timestamp, + unix_timestamp: unixSeconds, + link: `https://x.com/${tweet.user.screen_name}/status/${tweetId}`, + body: py_get(tweet, 'legacy').full_text, + author: tweet.user.screen_name, + author_fullname: tweet.user.name, + author_id: tweet.user.id_str, + author_avatar_url: '', // todo: add + author_banner_url: '', // todo: add + author_followers: tweet.user.followers_count || '', + author_following: tweet.user.friends_count || '', + author_bio: tweet.user.description || '', + author_location: tweet.user.location || '', + verified: '', // todo: add + source: strip_tags(py_get(tweet, 'legacy').source), + language_guess: py_get(tweet, 'legacy').lang, + possibly_sensitive: py_get(tweet, 'legacy').possibly_sensitive ? 'yes' : 'no', + retweet_count: py_get(tweet, 'legacy').retweet_count, + reply_count: py_get(tweet, 'legacy').reply_count, + like_count: py_get(tweet, 'legacy').favorite_count, + quote_count: py_get(tweet, 'legacy').quote_count, + impression_count: py_get(py_get(tweet, 'ext_views') || {}, 'count', ''), + is_retweet: retweet ? 'yes' : 'no', + retweeted_user: retweet ? py_get(retweet, 'result.core.user_results.result.legacy').screen_name || '' : '', + is_quote_tweet: isQuoteTweet ? 'yes' : 'no', + quote_tweet_id: finalQuoteTweetId, + quote_author: quoteAuthor, + quote_body: '', // todo: add + quote_images: '', // todo: add + quote_videos: '', // todo: add + is_quote_withheld: '', // todo: add + is_reply: String(py_get(tweet, 'legacy').conversation_id_str) !== tweetId ? 'yes' : 'no', + replied_author: py_get(tweet, 'legacy').in_reply_to_screen_name || '', + is_withheld: withheld ? 'yes' : 'no', + hashtags: (py_get(tweet, 'legacy').entities.hashtags || []).map(h => h.text).join(','), + urls: (py_get(tweet, 'legacy').entities.urls || []).map(u => u.expanded_url || u.display_url).join(','), + images: (py_get(tweet, 'legacy').extended_entities.media || []).filter(m => m.type === 'photo').map(m => m.media_url_https).join(','), + videos: (py_get(tweet, 'legacy').extended_entities.media || []).filter(m => m.type === 'video').map(m => m.video_info.variants[0].url).join(','), + mentions: (py_get(tweet, 'legacy').entities.user_mentions || []).map(m => m.screen_name).join(','), + long_lat: py_get(tweet, 'legacy').place ? get_centroid(py_get(tweet, 'legacy').place.bounding_box.coordinates) : '', + place_name: py_get(tweet, 'legacy').place ? py_get(py_get(tweet, 'legacy').place, 'full_name', '') : '' }; } function map_user(userResult) { - if (typeof userResult !== "object" || userResult === null) { + if (typeof userResult !== 'object' || userResult === null) { userResult = {}; } const core = userResult.core || {}; @@ -420,19 +416,19 @@ function map_user(userResult) { function first(...values) { for (const v of values) { - if (v !== undefined && v !== null) { + if (v !== null && v !== undefined) { return v; } } - return ""; + return ''; } return { - screen_name: core.screen_name || legacy.screen_name || "", - fullname: core.name || legacy.name || "", - avatar_url: avatar.image_url || legacy.profile_image_url_https || "", - banner_url: banner.image_url || legacy.profile_banner_url || "", - verified: py_get(userResult, "is_blue_verified", ""), + screen_name: core.screen_name || legacy.screen_name || '', + fullname: core.name || legacy.name || '', + avatar_url: avatar.image_url || legacy.profile_image_url_https || '', + banner_url: banner.image_url || legacy.profile_banner_url || '', + verified: userResult.is_blue_verified || '', followers: first(counts.followers, legacy.followers_count), following: first(counts.following, legacy.friends_count), bio: first(profileBio.description, legacy.description), @@ -441,26 +437,24 @@ function map_user(userResult) { } function get_note_result(tweet) { - const note = py_get(tweet, "note_tweet", {}); - return py_get(py_get(note, "note_tweet_results", {}), "result", {}); + const note = py_get(tweet, 'note_tweet') || {}; + return py_get(note, 'note_tweet_results', {}).result || {}; } function get_full_text(tweet) { - const text = py_get(py_get(tweet, "legacy", {}), "full_text", ""); - const noteText = py_get(get_note_result(tweet), "text", ""); + const text = py_get(tweet, 'legacy').full_text || ''; + const noteText = py_get(get_note_result(tweet), 'text', ''); return noteText.length > text.length ? noteText : text; } function get_entities(tweet) { - const entities = py_get(py_get(tweet, "legacy", {}), "entities", {}); + const entities = py_get(tweet, 'legacy').entities || {}; const note = get_note_result(tweet); const noteEntities = note.entity_set; if (!noteEntities) { return entities; } - const legacyFullText = py_get(py_get(tweet, "legacy", {}), "full_text", ""); - const noteText = note.text || ""; - if (noteText.length <= legacyFullText.length) { + if (note.text && note.text.length <= (py_get(tweet, 'legacy').full_text || '').length) { return entities; } const combined = Object.assign({}, noteEntities); @@ -473,22 +467,20 @@ function get_entities(tweet) { function get_media(tweet) { const images = []; const videos = []; - const legacy = py_get(tweet, "legacy", {}); - const extended = py_get(legacy, "extended_entities", {}); - const entities = py_get(legacy, "entities", {}); + const legacy = py_get(tweet, 'legacy') || {}; const mediaItems = []; - if (Array.isArray(extended.media)) { - mediaItems.push(...extended.media); + if (legacy.extended_entities && legacy.extended_entities.media) { + mediaItems.push(...legacy.extended_entities.media); } - if (Array.isArray(entities.media)) { - mediaItems.push(...entities.media); + if (legacy.entities && legacy.entities.media) { + mediaItems.push(...legacy.entities.media); } for (const media of mediaItems) { if (!media.media_url_https) continue; images.push(media.media_url_https); - if (media.type !== "video" && media.type !== "animated_gif") continue; - const variants = (media.video_info && media.video_info.variants) || []; - const videoVariants = variants.filter(v => (v.content_type || "").startsWith("video/")); + if (media.type !== 'video' && media.type !== 'animated_gif') continue; + const variants = (media.video_info || {}).variants || []; + const videoVariants = variants.filter(v => (v.content_type || '').startsWith('video/')); if (videoVariants.length) { videoVariants.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0)); videos.push(videoVariants[0].url); @@ -501,45 +493,49 @@ function get_media(tweet) { } function _screen_name_from_url(url) { - if (typeof url !== "string") return ""; + if (typeof url !== 'string') return ''; const m = url.match(/^https?:\/\/(?:x|twitter)\.com\/([^/]+)\/status\//); - return m ? m[1] : ""; + return m ? m[1] : ''; } function _screen_name_from_media(legacyObj) { - if (typeof legacyObj !== "object" || legacyObj === null) return ""; - for (const container of ["extended_entities", "entities"]) { - const mediaArray = py_get(legacyObj, container, {}).media || []; + if (typeof legacyObj !== 'object' || legacyObj === null) return ''; + const containers = ['extended_entities', 'entities']; + for (const container of containers) { + const mediaArray = (legacyObj[container] && legacyObj[container].media) || []; for (const m of mediaArray) { - const url = typeof m === "object" ? py_get(m, "expanded_url", "") : ""; + const url = typeof m === 'object' ? (m.expanded_url || '') : ''; const screen = _screen_name_from_url(url); if (screen) return screen; } } - return ""; + return ''; } function get_centroid(box) { try { const ring = box[0]; if (!Array.isArray(ring) || ring.length < 2 || !ring[0] || !ring[1]) { - return ""; + return ''; } const lon = String(Number(((ring[0][0] + ring[1][0]) / 2).toFixed(6))); const lat = String(Number(((ring[0][1] + ring[1][1]) / 2).toFixed(6))); return `${lon},${lat}`; } catch (e) { - return ""; + return ''; } } export function map_item(item) { - if (py_get(item, "rest_id")) { + // Determine which mapping function to use based on the shape of the imported tweet + const hasRestId = py_get(item, 'rest_id') !== null && py_get(item, 'rest_id') !== undefined; + const isLegacy = py_get(item, 'type') === 'adaptive'; + if (hasRestId) { return new MappedItem(map_item_modern(item)); - } else if (py_get(item, "type") === "adaptive") { + } else if (isLegacy) { return new MappedItem(map_item_legacy(item)); } else { - throw new MapItemException("Unsupported item shape"); + throw new MapItemException('Unrecognised tweet shape'); } } // === end auto-generated ===