From e2efbec3eb9bf3379bb7359df232b5b49981f2a2 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 21 Feb 2025 13:32:53 -0500 Subject: [PATCH 01/14] facebook: add isReposted field to Post --- archive-static-sites/facebook-archive/src/types.ts | 1 + src/account_facebook/facebook_account_controller.ts | 2 ++ src/account_facebook/types.ts | 2 ++ 3 files changed, 5 insertions(+) diff --git a/archive-static-sites/facebook-archive/src/types.ts b/archive-static-sites/facebook-archive/src/types.ts index 4908ac72..eedaec18 100644 --- a/archive-static-sites/facebook-archive/src/types.ts +++ b/archive-static-sites/facebook-archive/src/types.ts @@ -3,6 +3,7 @@ export type Post = { createdAt: string; text: string; archivedAt: string | null; + isReposted: boolean; }; export type FacebookArchive = { diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index 300e26d2..fae739f5 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -240,6 +240,7 @@ export class FacebookAccountController { postID: post.postID, createdAt: post.createdAt, text: decodeUnicode(post.text), + isReposted: post.isReposted, archivedAt: post.archivedAt, }; return archivePost @@ -535,6 +536,7 @@ export class FacebookAccountController { id_str: post.timestamp.toString(), title: post.title || '', full_text: postText, + isReposted: isSharedPost, created_at: new Date(post.timestamp * 1000).toISOString(), }); } diff --git a/src/account_facebook/types.ts b/src/account_facebook/types.ts index def4a15d..e9790b7d 100644 --- a/src/account_facebook/types.ts +++ b/src/account_facebook/types.ts @@ -37,6 +37,7 @@ export interface FacebookArchivePost { created_at: string; full_text: string; title: string; + isReposted: boolean; // lang: string; } @@ -51,4 +52,5 @@ export interface FacebookPostRow { archivedAt: string | null; deletedPostAt: string | null; hasMedia: boolean; + isReposted: boolean; } From c35c739885bfdc969ab935dced79a4b5db6123b4 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 21 Feb 2025 13:49:02 -0500 Subject: [PATCH 02/14] facebook: current posts are not reshares --- src/account_facebook/facebook_account_controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index fae739f5..e176d8ed 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -521,7 +521,7 @@ export class FacebookAccountController { // Check if it's a shared post by looking for external_context in attachments const isSharedPost = post.attachments?.[0]?.data?.[0]?.external_context !== undefined; - // Skip if it's a shared/repost, group post, shares a group, etc. We will extend the import logic + // Skip if it's a group post, shares a group, etc. We will extend the import logic // to include other data types in the future. if (isSharedPost) { log.info("FacebookAccountController.importFacebookArchive: skipping shared post", JSON.stringify(post)); From 21ef0e24e3d687ea303e3249f3df31cc242133cf Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 21 Feb 2025 13:56:37 -0500 Subject: [PATCH 03/14] facebook: try to pull out post types from HTML content --- .../facebook_account_controller.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index e176d8ed..a9f38aa6 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -597,4 +597,27 @@ export class FacebookAccountController { skipCount: skipCount, }; } -} \ No newline at end of file +} + +const getPostType = (element: Element): 'status' | 'shared_post' | 'shared_group' | 'other' => { + const pinDivs = element.querySelectorAll('._2pin'); + + if (pinDivs.length === 1) { + return 'status'; + } + + if (pinDivs.length === 2) { + // Check for group name structure + const firstPinContent = pinDivs[0].textContent?.trim(); + if (firstPinContent && !firstPinContent.includes('div')) { + return 'shared_group'; + } + // Shared posts have empty nested divs + const emptyDivs = pinDivs[0].querySelectorAll('div div div div'); + if (emptyDivs.length > 0) { + return 'shared_post'; + } + } + + return 'other'; +}; \ No newline at end of file From 8244c092e02f1a4cd2079c2a62bb4196d4c16883 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 21 Feb 2025 17:24:37 -0500 Subject: [PATCH 04/14] facebook: add db migration to add isReposted column --- .../facebook_account_controller.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index a9f38aa6..e4647ce5 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -162,6 +162,24 @@ export class FacebookAccountController { );` ] }, + { + name: "20250220_add_isReposted_to_post", + sql: [ + `CREATE TABLE post_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + postID TEXT NOT NULL UNIQUE, + createdAt DATETIME NOT NULL, + title TEXT, + text TEXT, + isReposted BOOLEAN NOT NULL DEFAULT 0, + addedToDatabaseAt DATETIME NOT NULL + );`, + `INSERT INTO post_new (id, postID, createdAt, title, text, addedToDatabaseAt) + SELECT id, postID, createdAt, title, text, addedToDatabaseAt FROM post;`, + `DROP TABLE post;`, + `ALTER TABLE post_new RENAME TO post;` + ] + }, ]) log.info("FacebookAccountController.initDB: database initialized"); } From 37037664a38a8264bcea8370dc8e783b3ea471ec Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 21 Feb 2025 17:44:03 -0500 Subject: [PATCH 05/14] facebook: show reposts on the static archive site --- .../facebook-archive/src/types.ts | 1 + .../facebook-archive/src/views/PostsView.vue | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/archive-static-sites/facebook-archive/src/types.ts b/archive-static-sites/facebook-archive/src/types.ts index eedaec18..8ee738b8 100644 --- a/archive-static-sites/facebook-archive/src/types.ts +++ b/archive-static-sites/facebook-archive/src/types.ts @@ -2,6 +2,7 @@ export type Post = { postID: string; createdAt: string; text: string; + title: string; archivedAt: string | null; isReposted: boolean; }; diff --git a/archive-static-sites/facebook-archive/src/views/PostsView.vue b/archive-static-sites/facebook-archive/src/views/PostsView.vue index 568f0e3e..3a745674 100644 --- a/archive-static-sites/facebook-archive/src/views/PostsView.vue +++ b/archive-static-sites/facebook-archive/src/views/PostsView.vue @@ -16,7 +16,10 @@ const filteredPosts = computed(() => { const lowerCaseFilterText = filterText.value.toLowerCase(); const postText = post.text ? post.text.toLowerCase() : ''; const postUsername = archiveData.value.username ? archiveData.value.username.toLowerCase() : ''; - return postText.includes(lowerCaseFilterText) || postUsername.includes(lowerCaseFilterText); + const postTitle = post.title ? post.title.toLowerCase() : ''; + return postText.includes(lowerCaseFilterText) || + postUsername.includes(lowerCaseFilterText) || + postTitle.includes(lowerCaseFilterText); }); }); @@ -25,11 +28,18 @@ const filteredPosts = computed(() => {

-

Showing {{ filteredPosts.length.toLocaleString() }} posts

+

Showing {{ filteredPosts.length.toLocaleString() }} posts + ({{ filteredPosts.filter(p => p.isReposted).length.toLocaleString() }} reposts) +

- +
From 80c1ea28fec1c5584e8f49e0495d61cd76a3198e Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Sat, 22 Feb 2025 09:21:53 -0500 Subject: [PATCH 06/14] facebook: display title on archive --- archive-static-sites/facebook-archive/src/views/PostsView.vue | 1 + src/account_facebook/facebook_account_controller.ts | 1 + src/account_facebook/types.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/archive-static-sites/facebook-archive/src/views/PostsView.vue b/archive-static-sites/facebook-archive/src/views/PostsView.vue index 3a745674..26681a0e 100644 --- a/archive-static-sites/facebook-archive/src/views/PostsView.vue +++ b/archive-static-sites/facebook-archive/src/views/PostsView.vue @@ -39,6 +39,7 @@ const filteredPosts = computed(() => { :key="post.postID" :post="post" :isRepost="post.isReposted" + :title="post.title" /> diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index e4647ce5..23925db1 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -258,6 +258,7 @@ export class FacebookAccountController { postID: post.postID, createdAt: post.createdAt, text: decodeUnicode(post.text), + title: post.title, isReposted: post.isReposted, archivedAt: post.archivedAt, }; diff --git a/src/account_facebook/types.ts b/src/account_facebook/types.ts index e9790b7d..99bc22e2 100644 --- a/src/account_facebook/types.ts +++ b/src/account_facebook/types.ts @@ -46,6 +46,7 @@ export interface FacebookPostRow { username: string; postID: string; createdAt: string; + title: string; text: string; path: string; addedToDatabaseAt: string; From 2a1fad7c5377297222f6ca565e1b371a0888db98 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Sun, 2 Mar 2025 14:05:45 -0500 Subject: [PATCH 07/14] fix: populate db with repost status --- src/account_facebook/facebook_account_controller.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index 23925db1..ae0f518c 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -539,15 +539,12 @@ export class FacebookAccountController { // Check if it's a shared post by looking for external_context in attachments const isSharedPost = post.attachments?.[0]?.data?.[0]?.external_context !== undefined; + log.info("FacebookAccountController.importFacebookArchive: isSharedPost", isSharedPost); // Skip if it's a group post, shares a group, etc. We will extend the import logic // to include other data types in the future. - if (isSharedPost) { - log.info("FacebookAccountController.importFacebookArchive: skipping shared post", JSON.stringify(post)); - continue; - } - else if (post.attachments) { - log.info("FacebookAccountController.importFacebookArchive: skipping unknown post type", JSON.stringify(post)); + if (post.attachments && !isSharedPost) { + log.info("FacebookAccountController.importFacebookArchive: skipping unknown post type"); continue; } @@ -582,11 +579,12 @@ export class FacebookAccountController { // TODO: implement urls import for facebook // Import it - exec(this.db, 'INSERT INTO post (postID, createdAt, title, text, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?)', [ + exec(this.db, 'INSERT INTO post (postID, createdAt, title, text, isReposted, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?)', [ post.id_str, new Date(post.created_at), post.title, post.full_text, + post.isReposted ? 1 : 0, new Date(), ]); importCount++; From 66ae851677e8b78818e9825df75266424a3880b5 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Mon, 3 Mar 2025 13:28:53 -0500 Subject: [PATCH 08/14] fix: remove now unused function --- .../facebook_account_controller.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index ae0f518c..85b6f500 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -615,26 +615,3 @@ export class FacebookAccountController { }; } } - -const getPostType = (element: Element): 'status' | 'shared_post' | 'shared_group' | 'other' => { - const pinDivs = element.querySelectorAll('._2pin'); - - if (pinDivs.length === 1) { - return 'status'; - } - - if (pinDivs.length === 2) { - // Check for group name structure - const firstPinContent = pinDivs[0].textContent?.trim(); - if (firstPinContent && !firstPinContent.includes('div')) { - return 'shared_group'; - } - // Shared posts have empty nested divs - const emptyDivs = pinDivs[0].querySelectorAll('div div div div'); - if (emptyDivs.length > 0) { - return 'shared_post'; - } - } - - return 'other'; -}; \ No newline at end of file From d2f23f6cd8a5b55e1a188a3492865ffc52f8f66e Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Mon, 3 Mar 2025 15:35:08 -0500 Subject: [PATCH 09/14] facebook: support media imports --- .../facebook_account_controller.ts | 77 ++++++++++++++++++- src/account_facebook/types.ts | 8 ++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index 85b6f500..df17bcfd 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -32,6 +32,7 @@ import { FacebookJobRow, convertFacebookJobRowToFacebookJob, FacebookArchivePost, + FacebookArchiveMedia, FacebookPostRow } from './types' import * as FacebookArchiveTypes from '../../archive-static-sites/facebook-archive/src/types'; @@ -180,6 +181,22 @@ export class FacebookAccountController { `ALTER TABLE post_new RENAME TO post;` ] }, + { + name: "20250302_add_media_table", + sql: [ + `CREATE TABLE media ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mediaId TEXT NOT NULL UNIQUE, + postId TEXT NOT NULL, + type TEXT NOT NULL, + uri TEXT NOT NULL, + description TEXT, + createdAt DATETIME, + addedToDatabaseAt DATETIME NOT NULL, + FOREIGN KEY(postId) REFERENCES post(postID) + );` + ] + }, ]) log.info("FacebookAccountController.initDB: database initialized"); } @@ -541,6 +558,23 @@ export class FacebookAccountController { const isSharedPost = post.attachments?.[0]?.data?.[0]?.external_context !== undefined; log.info("FacebookAccountController.importFacebookArchive: isSharedPost", isSharedPost); + // Process media attachments + const media: FacebookArchiveMedia[] = []; + if (post.attachments) { + for (const attachment of post.attachments) { + for (const data of attachment.data) { + if (data.media) { + media.push({ + uri: data.media.uri, + type: data.media.uri.endsWith('.mp4') ? 'video' : 'photo', + description: data.media.description, + creationTimestamp: data.media.creation_timestamp + }); + } + } + } + } + // Skip if it's a group post, shares a group, etc. We will extend the import logic // to include other data types in the future. if (post.attachments && !isSharedPost) { @@ -554,6 +588,7 @@ export class FacebookAccountController { full_text: postText, isReposted: isSharedPost, created_at: new Date(post.timestamp * 1000).toISOString(), + media: media.length > 0 ? media : undefined, }); } } catch (e) { @@ -567,7 +602,7 @@ export class FacebookAccountController { // Loop through the posts and add them to the database try { - postsData.forEach((post) => { + postsData.forEach(async (post) => { // Is this post already there? const existingPost = exec(this.db, 'SELECT * FROM post WHERE postID = ?', [post.id_str], "get") as FacebookPostRow; if (existingPost) { @@ -575,7 +610,10 @@ export class FacebookAccountController { exec(this.db, 'DELETE FROM post WHERE postID = ?', [post.id_str]); } - // TODO: implement media import for facebook + if (post.media && post.media.length > 0) { + await this.importFacebookArchiveMedia(post.id_str, post.media, archivePath); + } + // TODO: implement urls import for facebook // Import it @@ -614,4 +652,39 @@ export class FacebookAccountController { skipCount: skipCount, }; } + + async importFacebookArchiveMedia(postId: string, media: FacebookArchiveMedia[], archivePath: string): Promise { + for (const mediaItem of media) { + const sourcePath = path.join(archivePath, mediaItem.uri); + const mediaId = `${postId}_${path.basename(mediaItem.uri)}`; + + // Create destination directory if it doesn't exist + const mediaDir = path.join(this.accountDataPath, 'media'); + if (!fs.existsSync(mediaDir)) { + fs.mkdirSync(mediaDir, { recursive: true }); + } + + const destPath = path.join(mediaDir, path.basename(mediaItem.uri)); + + try { + await fs.promises.copyFile(sourcePath, destPath); + + // Store media info in database + exec(this.db, + 'INSERT INTO media (mediaId, postId, type, uri, description, createdAt, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?)', + [ + mediaId, + postId, + mediaItem.type, + path.basename(mediaItem.uri), + mediaItem.description || null, + mediaItem.creationTimestamp ? new Date(mediaItem.creationTimestamp * 1000) : null, + new Date() + ] + ); + } catch (error) { + log.error(`FacebookAccountController.importFacebookArchiveMedia: Error importing media: ${error}`); + } + } + } } diff --git a/src/account_facebook/types.ts b/src/account_facebook/types.ts index 99bc22e2..e38d1c1a 100644 --- a/src/account_facebook/types.ts +++ b/src/account_facebook/types.ts @@ -38,9 +38,17 @@ export interface FacebookArchivePost { full_text: string; title: string; isReposted: boolean; + media?: FacebookArchiveMedia[]; // Media attachments // lang: string; } +export interface FacebookArchiveMedia { + uri: string; + type: 'photo' | 'video'; + description?: string; // Some media items have descriptions + creationTimestamp?: number; // From media.creation_timestamp +} + export interface FacebookPostRow { id: number; username: string; From 0bf6090f671dbbef2121a24d5fa7b87d9310a55c Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Mon, 3 Mar 2025 16:14:16 -0500 Subject: [PATCH 10/14] fix: media gets saved to post_media, and media/ dir --- .../facebook_account_controller.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index df17bcfd..f03632df 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -184,7 +184,7 @@ export class FacebookAccountController { { name: "20250302_add_media_table", sql: [ - `CREATE TABLE media ( + `CREATE TABLE post_media ( id INTEGER PRIMARY KEY AUTOINCREMENT, mediaId TEXT NOT NULL UNIQUE, postId TEXT NOT NULL, @@ -574,10 +574,11 @@ export class FacebookAccountController { } } } + log.info("FacebookAccountController.importFacebookArchive: media", media); // Skip if it's a group post, shares a group, etc. We will extend the import logic // to include other data types in the future. - if (post.attachments && !isSharedPost) { + if (post.attachments && !isSharedPost && media.length === 0) { log.info("FacebookAccountController.importFacebookArchive: skipping unknown post type"); continue; } @@ -610,10 +611,6 @@ export class FacebookAccountController { exec(this.db, 'DELETE FROM post WHERE postID = ?', [post.id_str]); } - if (post.media && post.media.length > 0) { - await this.importFacebookArchiveMedia(post.id_str, post.media, archivePath); - } - // TODO: implement urls import for facebook // Import it @@ -625,9 +622,15 @@ export class FacebookAccountController { post.isReposted ? 1 : 0, new Date(), ]); + + if (post.media && post.media.length > 0) { + log.info("FacebookAccountController.importFacebookArchive: importing media for post", post.id_str); + await this.importFacebookArchiveMedia(post.id_str, post.media, archivePath); + } importCount++; }); } catch (e) { + log.error("FacebookAccountController.importFacebookArchive: error importing posts", e); return { status: "error", errorMessage: "Error importing posts: " + e, @@ -665,13 +668,12 @@ export class FacebookAccountController { } const destPath = path.join(mediaDir, path.basename(mediaItem.uri)); - try { await fs.promises.copyFile(sourcePath, destPath); // Store media info in database exec(this.db, - 'INSERT INTO media (mediaId, postId, type, uri, description, createdAt, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?)', + 'INSERT INTO post_media (mediaId, postId, type, uri, description, createdAt, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?)', [ mediaId, postId, From 49b66ceb093cd1fb806727ebac6999cefda05f88 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Mon, 3 Mar 2025 20:55:45 -0500 Subject: [PATCH 11/14] facebook: add video/image support to static site --- .../src/components/PostComponent.vue | 27 ++++++++++-- .../facebook-archive/src/types.ts | 13 ++++-- .../facebook_account_controller.ts | 44 ++++++++++++++++--- src/account_facebook/types.ts | 14 ++++++ 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/archive-static-sites/facebook-archive/src/components/PostComponent.vue b/archive-static-sites/facebook-archive/src/components/PostComponent.vue index c1c2172d..c00b9237 100644 --- a/archive-static-sites/facebook-archive/src/components/PostComponent.vue +++ b/archive-static-sites/facebook-archive/src/components/PostComponent.vue @@ -30,18 +30,39 @@ const formattedText = computed(() => {

+ + +
+
+ + +

{{ media.description }}

+
+
archived {{ formattedDate(post.archivedAt) }}
- \ No newline at end of file diff --git a/archive-static-sites/facebook-archive/src/types.ts b/archive-static-sites/facebook-archive/src/types.ts index 8ee738b8..9359f247 100644 --- a/archive-static-sites/facebook-archive/src/types.ts +++ b/archive-static-sites/facebook-archive/src/types.ts @@ -1,11 +1,18 @@ -export type Post = { +export interface Post { postID: string; createdAt: string; text: string; title: string; - archivedAt: string | null; isReposted: boolean; -}; + archivedAt: string | null; + media?: { + mediaId: string; + type: string; + uri: string; + description: string | null; + createdAt: string | null; + }[]; +} export type FacebookArchive = { posts: Post[]; diff --git a/src/account_facebook/facebook_account_controller.ts b/src/account_facebook/facebook_account_controller.ts index f03632df..551f5e4b 100644 --- a/src/account_facebook/facebook_account_controller.ts +++ b/src/account_facebook/facebook_account_controller.ts @@ -33,6 +33,7 @@ import { convertFacebookJobRowToFacebookJob, FacebookArchivePost, FacebookArchiveMedia, + FacebookPostWithMedia, FacebookPostRow } from './types' import * as FacebookArchiveTypes from '../../archive-static-sites/facebook-archive/src/types'; @@ -251,14 +252,38 @@ export class FacebookAccountController { } log.info("FacebookAccountController.archiveBuild: building archive"); - - // Posts - const posts: FacebookPostRow[] = exec( + // Posts with optional media + const postsFromDb = exec( this.db, - "SELECT * FROM post ORDER BY createdAt DESC", + `SELECT + p.*, + CASE + WHEN pm.mediaId IS NOT NULL + THEN GROUP_CONCAT( + json_object( + 'mediaId', pm.mediaId, + 'postId', pm.postId, + 'type', pm.type, + 'uri', pm.uri, + 'description', pm.description, + 'createdAt', pm.createdAt, + 'addedToDatabaseAt', pm.addedToDatabaseAt + ) + ) + ELSE NULL + END as media + FROM post p + LEFT JOIN post_media pm ON p.postID = pm.postId + GROUP BY p.postID + ORDER BY p.createdAt DESC`, [], "all" - ) as FacebookPostRow[]; + ); + // Transform into FacebookPostWithMedia + const posts: FacebookPostWithMedia[] = (postsFromDb as Array).map((post) => ({ + ...post, + media: post.media ? JSON.parse(`[${post.media}]`) : undefined + })); // Get the current account's userID // const accountUser = users.find((user) => user.screenName == this.account?.username); @@ -278,8 +303,15 @@ export class FacebookAccountController { title: post.title, isReposted: post.isReposted, archivedAt: post.archivedAt, + media: (post as FacebookPostWithMedia).media?.map(m => ({ + mediaId: m.mediaId, + type: m.type, + uri: m.uri, + description: m.description, + createdAt: m.createdAt + })) }; - return archivePost + return archivePost; } // Build the archive object diff --git a/src/account_facebook/types.ts b/src/account_facebook/types.ts index e38d1c1a..866fbf42 100644 --- a/src/account_facebook/types.ts +++ b/src/account_facebook/types.ts @@ -63,3 +63,17 @@ export interface FacebookPostRow { hasMedia: boolean; isReposted: boolean; } + +export interface FacebookPostMediaRow { + mediaId: string; + postId: string; // Foreign key to post.postID + type: string; + uri: string; + description: string | null; + createdAt: string | null; + addedToDatabaseAt: string; +} + +export interface FacebookPostWithMedia extends FacebookPostRow { + media?: FacebookPostMediaRow[]; +} From dcb2078e50fb312d7cc24d6901dd3bacc1f62781 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Mon, 3 Mar 2025 21:03:38 -0500 Subject: [PATCH 12/14] fix: don't show post text twice if it's the same text as the image --- .../src/components/PostComponent.vue | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/archive-static-sites/facebook-archive/src/components/PostComponent.vue b/archive-static-sites/facebook-archive/src/components/PostComponent.vue index c00b9237..2d33aa69 100644 --- a/archive-static-sites/facebook-archive/src/components/PostComponent.vue +++ b/archive-static-sites/facebook-archive/src/components/PostComponent.vue @@ -14,6 +14,14 @@ const formattedText = computed(() => { text = text.replace(/(?:\r\n|\r|\n)/g, '
'); return text.trim(); }); + +const shouldShowText = computed(() => { + if (!props.post.text) return false; + if (!props.post.media || props.post.media.length === 0) return true; + + // Show text if it's different from all media descriptions + return !props.post.media.some(media => media.description === props.post.text); +});