Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b5e762e
Adds types and migrations for tweet_media table
SaptakS Feb 6, 2025
57e8833
Saves and Indexes tweet media
SaptakS Feb 6, 2025
8708243
Adds reply and quote related informations to the tweet table
SaptakS Feb 6, 2025
41fc9a0
Adds additional fields to tweet_media to store url and index informat…
SaptakS Feb 7, 2025
b2bf23f
Index tweet URLs in the database
SaptakS Feb 7, 2025
870f333
Add new types for tweet media, and support downloading videos
micahflee Feb 14, 2025
e4c7a5c
Add more fields to the XTweetMediaRow type, and update the migration …
micahflee Feb 14, 2025
5f49465
Add a test that ensures tweets with videos and photos are indexed cor…
micahflee Feb 14, 2025
ca55615
Fix indexing URLs, and write test for it
micahflee Feb 16, 2025
217ed5e
Add media and URLs to archive.js in buildArchive; and also, delete UR…
micahflee Feb 16, 2025
76e849f
Update x-archive deps
micahflee Feb 16, 2025
dd6564a
Properly display links and media in X archive
micahflee Feb 16, 2025
44e89ac
Imports media, urls, and reply information from tweet archives
SaptakS Feb 17, 2025
d53a26b
Merge pull request #401 from lockdown-systems/354-index-media-video
SaptakS Feb 17, 2025
21dba91
Adds types to the archive import functions
SaptakS Feb 17, 2025
dbfbdad
Make quote tweets work in the archive
micahflee Feb 17, 2025
acaba64
Simplify quote tweets
micahflee Feb 17, 2025
df22fed
Merge branch '354-index-media' into 354-index-media-archive
micahflee Feb 17, 2025
7107144
Merge branch 'main' into 354-index-media
micahflee Feb 17, 2025
c2b387b
Merge branch '354-index-media' into 354-index-media-archive
micahflee Feb 17, 2025
045dfa9
Fix INSERT query when importing tweets -- there were 20 ?s but 15 params
micahflee Feb 17, 2025
d52675c
When importing X archive, only delete the archive folder if we unzipp…
micahflee Feb 17, 2025
fab76b9
Fix importing media items so it sets mediaList to the extended entiti…
micahflee Feb 17, 2025
f2863db
Switch to using extended_entities for media, and just use entities fo…
micahflee Feb 17, 2025
19a1e45
Merge pull request #402 from lockdown-systems/354-index-media-archive
micahflee Feb 17, 2025
72f78f0
When importing tweets from an X archive, always delete the tweet if i…
micahflee Feb 17, 2025
c957404
Remove dead code
micahflee Feb 17, 2025
2e71e52
Remove IPC functions from X that should have been removed in a differ…
micahflee Feb 17, 2025
47f9fde
Fix bug where when you import an X archive from a ZIP file, it unzips…
micahflee Feb 17, 2025
7deb52c
fix: load videos from X archive export
redshiftzero Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/account_x/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export interface XJobRow {
error: string | null;
}

export interface XTweetMediaRow {
id: number;
mediaID: string;
tweetID: string;
type: string;
}

export interface XTweetRow {
id: number;
username: string;
Expand Down
81 changes: 80 additions & 1 deletion src/account_x/x_account_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import {
XJobRow,
XTweetRow,
XTweetMediaRow,
XUserRow,
XConversationRow,
XMessageRow,
Expand Down Expand Up @@ -317,6 +318,20 @@
`UPDATE tweet SET deletedLikeAt = deletedAt WHERE deletedAt IS NOT NULL AND isLiked = 1;`
]
},
// Add hasMediato the tweet table, and update isBookarked for all tweets
{
name: "20250206_add_hasMedia_and_tweet_media",
sql: [
`CREATE TABLE tweet_media (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mediaID TEXT NOT NULL UNIQUE,
mediaType TEXT NOT NULL,
tweetID TEXT NOT NULL
);`,
`ALTER TABLE tweet ADD COLUMN hasMedia BOOLEAN;`,
`UPDATE tweet SET hasMedia = 0;`
]
},
])
log.info("XAccountController.initDB: database initialized");
}
Expand Down Expand Up @@ -461,8 +476,15 @@
exec(this.db, 'DELETE FROM tweet WHERE tweetID = ?', [tweetLegacy["id_str"]]);
}

// Check if tweet has media and call indexTweetMedia
let hasMedia: boolean = false;
if (tweetLegacy["entities"]["media"] && tweetLegacy["entities"]["media"].length){
hasMedia = true;
this.indexTweetMedia(tweetLegacy)
}

// Add the tweet
exec(this.db, 'INSERT INTO tweet (username, tweetID, conversationID, createdAt, likeCount, quoteCount, replyCount, retweetCount, isLiked, isRetweeted, isBookmarked, text, path, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
exec(this.db, 'INSERT INTO tweet (username, tweetID, conversationID, createdAt, likeCount, quoteCount, replyCount, retweetCount, isLiked, isRetweeted, isBookmarked, text, path, hasMedia, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
userLegacy["screen_name"],
tweetLegacy["id_str"],
tweetLegacy["conversation_id_str"],
Expand All @@ -476,9 +498,13 @@
tweetLegacy["bookmarked"] ? 1 : 0,
tweetLegacy["full_text"],
`${userLegacy['screen_name']}/status/${tweetLegacy['id_str']}`,
hasMedia ? 1 : 0,
new Date(),
]);

// Add media information to tweet_media table


// Update progress
if (tweetLegacy["favorited"]) {
// console.log("DEBUG-### LIKE: ", tweetLegacy["id_str"], userLegacy["screen_name"], tweetLegacy["full_text"]);
Expand Down Expand Up @@ -655,6 +681,59 @@
return this.progress;
}

async saveTweetMedia(mediaPath: string, filename: string) {
if (this.account) {
// Create path to store tweet media if it doesn't exist already
const accountDataPath = getAccountDataPath("X", this.account.username);
const outputPath = path.join(accountDataPath, "Tweet Media");
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath);
}

// Download and save media from the mediaPath
try {
const response = await fetch(mediaPath, {});
if (!response.ok) {
return "";
}

const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const fileType = mediaPath.substring(mediaPath.lastIndexOf(".") + 1);
const outputFileName = path.join(outputPath, `${filename}.${fileType}`);
fs.createWriteStream(outputFileName).write(buffer);
return outputFileName;
} catch {
return "";
}
}
throw new Error("Account not found");
}

async indexTweetMedia(tweetLegacy: XAPILegacyTweet) {
log.debug("XAccountController.indexMedia");

// Loop over all media items
tweetLegacy["entities"]["media"].forEach((media: any) => {

Check failure on line 717 in src/account_x/x_account_controller.ts

View workflow job for this annotation

GitHub Actions / run-tests

Unexpected any. Specify a different type
// Download media locally
this.saveTweetMedia(media["media_url_https"], media["media_key"]);

// Have we seen this media before?
const existing: XTweetMediaRow[] = exec(this.db, 'SELECT * FROM tweet_media WHERE mediaID = ?', [media["media_key"]], "all") as XTweetMediaRow[];
if (existing.length > 0) {
// Delete it, so we can re-add it
exec(this.db, 'DELETE FROM tweet_media WHERE mediaID = ?', [media["media_key"]]);
}

// Index media information in tweet_media table
exec(this.db, 'INSERT INTO tweet_media (mediaID, mediaType, tweetID) VALUES (?, ?, ?)', [
media["media_key"],
media["type"],
tweetLegacy["id_str"],
]);
})
}

async getProfileImageDataURI(user: XAPIUser): Promise<string> {
if (!user.profile_image_url_https) {
return "";
Expand Down
Loading