tangled
alpha
login
or
join now
indexx.dev
/
tweets2bsky
forked from
j4ck.xyz/tweets2bsky
0
fork
atom
A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
0
fork
atom
overview
issues
pulls
pipelines
Fix: Skip retweets and fix screenshot aspect ratio
jack
2 months ago
cfdf1825
d2236320
+29
-9
1 changed file
expand all
collapse all
unified
split
src
index.ts
+29
-9
src/index.ts
···
93
93
entities?: TweetEntities;
94
94
extended_entities?: TweetEntities;
95
95
quoted_status_id_str?: string;
96
96
+
retweeted_status_id_str?: string;
96
97
is_quote_status?: boolean;
97
98
in_reply_to_status_id_str?: string;
98
99
in_reply_to_status_id?: string;
···
281
282
// biome-ignore lint/suspicious/noExplicitAny: raw types match compatible structure
282
283
extended_entities: raw.extended_entities as any,
283
284
quoted_status_id_str: raw.quoted_status_id_str,
285
285
+
retweeted_status_id_str: raw.retweeted_status_id_str,
284
286
is_quote_status: !!raw.quoted_status_id_str,
285
287
in_reply_to_status_id_str: raw.in_reply_to_status_id_str,
286
288
// biome-ignore lint/suspicious/noExplicitAny: missing in LegacyTweetRaw type
···
427
429
return data.blob;
428
430
}
429
431
430
430
-
async function captureTweetScreenshot(tweetUrl: string): Promise<Buffer | null> {
432
432
+
interface ScreenshotResult {
433
433
+
buffer: Buffer;
434
434
+
width: number;
435
435
+
height: number;
436
436
+
}
437
437
+
438
438
+
async function captureTweetScreenshot(tweetUrl: string): Promise<ScreenshotResult | null> {
431
439
const browserPaths = [
432
440
'/usr/bin/google-chrome',
433
441
'/usr/bin/chromium-browser',
···
494
502
495
503
const element = await page.$('#container');
496
504
if (element) {
505
505
+
const box = await element.boundingBox();
497
506
const buffer = await element.screenshot({ type: 'png', omitBackground: true });
498
498
-
console.log(`[SCREENSHOT] ✅ Captured successfully (${(buffer.length / 1024).toFixed(2)} KB)`);
499
499
-
return buffer as Buffer;
507
507
+
if (box) {
508
508
+
console.log(`[SCREENSHOT] ✅ Captured successfully (${(buffer.length / 1024).toFixed(2)} KB) - ${Math.round(box.width)}x${Math.round(box.height)}`);
509
509
+
return { buffer: buffer as Buffer, width: Math.round(box.width), height: Math.round(box.height) };
510
510
+
}
500
511
}
501
512
} catch (err) {
502
513
console.error(`[SCREENSHOT] ❌ Error capturing tweet:`, (err as Error).message);
···
806
817
807
818
if (processedTweets[tweetId]) continue;
808
819
820
820
+
if (tweet.retweeted_status_id_str) {
821
821
+
console.log(`[${twitterUsername}] ⏩ Skipping retweet ${tweetId}.`);
822
822
+
continue;
823
823
+
}
824
824
+
809
825
console.log(`\n[${twitterUsername}] 🔍 Inspecting tweet: ${tweetId}`);
810
826
updateAppStatus({
811
827
state: 'processing',
···
1013
1029
1014
1030
// Try to capture screenshot for external QTs if we have space for images
1015
1031
if (images.length < 4 && !videoBlob) {
1016
1016
-
const ssBuffer = await captureTweetScreenshot(externalQuoteUrl);
1017
1017
-
if (ssBuffer) {
1032
1032
+
const ssResult = await captureTweetScreenshot(externalQuoteUrl);
1033
1033
+
if (ssResult) {
1018
1034
try {
1019
1035
let blob: BlobRef;
1020
1036
if (dryRun) {
1021
1021
-
console.log(`[${twitterUsername}] 🧪 [DRY RUN] Would upload screenshot for quote (${(ssBuffer.length/1024).toFixed(2)} KB)`);
1022
1022
-
blob = { ref: { toString: () => 'mock-ss-blob' }, mimeType: 'image/png', size: ssBuffer.length } as any;
1037
1037
+
console.log(`[${twitterUsername}] 🧪 [DRY RUN] Would upload screenshot for quote (${(ssResult.buffer.length/1024).toFixed(2)} KB)`);
1038
1038
+
blob = { ref: { toString: () => 'mock-ss-blob' }, mimeType: 'image/png', size: ssResult.buffer.length } as any;
1023
1039
} else {
1024
1024
-
blob = await uploadToBluesky(agent, ssBuffer, 'image/png');
1040
1040
+
blob = await uploadToBluesky(agent, ssResult.buffer, 'image/png');
1025
1041
}
1026
1026
-
images.push({ alt: `Quote Tweet: ${externalQuoteUrl}`, image: blob });
1042
1042
+
images.push({
1043
1043
+
alt: `Quote Tweet: ${externalQuoteUrl}`,
1044
1044
+
image: blob,
1045
1045
+
aspectRatio: { width: ssResult.width, height: ssResult.height }
1046
1046
+
});
1027
1047
} catch (e) {
1028
1048
console.warn(`[${twitterUsername}] ⚠️ Failed to upload screenshot blob.`);
1029
1049
}