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.
···439 .sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0));
440441 if (mp4s.length > 0 && mp4s[0]) {
442- // Reverting to links for video for now as native upload is failing
443- text += `\n${media.media_url_https}`;
000000000000000444 }
445 }
446 }
···476477 // Only attach media/quotes to the first chunk
478 if (i === 0) {
479- if (images.length > 0) {
000000480 const imagesEmbed = { $type: 'app.bsky.embed.images', images };
481 if (quoteEmbed) {
482 postRecord.embed = { $type: 'app.bsky.embed.recordWithMedia', media: imagesEmbed, record: quoteEmbed };
···439 .sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0));
440441 if (mp4s.length > 0 && mp4s[0]) {
442+ const videoUrl = mp4s[0].url;
443+ try {
444+ const { buffer, mimeType } = await downloadMedia(videoUrl);
445+ if (buffer.length > 50 * 1024 * 1024) { // Reduced limit to be safer
446+ throw new Error('Video too large');
447+ }
448+ const blob = await uploadToBluesky(agent, buffer, mimeType);
449+ videoBlob = blob;
450+ videoAspectRatio = aspectRatio;
451+ } catch (err) {
452+ console.warn(`Failed to upload video ${videoUrl}, linking to tweet instead:`, (err as Error).message);
453+ // Link to the actual tweet so it unfurls with the video correctly
454+ const tweetUrl = `https://twitter.com/${twitterUsername}/status/${tweetId}`;
455+ if (!text.includes(tweetUrl)) {
456+ text += `\n${tweetUrl}`;
457+ }
458+ }
459 }
460 }
461 }
···491492 // Only attach media/quotes to the first chunk
493 if (i === 0) {
494+ if (videoBlob) {
495+ postRecord.embed = {
496+ $type: 'app.bsky.embed.video',
497+ video: videoBlob,
498+ aspectRatio: videoAspectRatio,
499+ };
500+ } else if (images.length > 0) {
501 const imagesEmbed = { $type: 'app.bsky.embed.images', images };
502 if (quoteEmbed) {
503 postRecord.embed = { $type: 'app.bsky.embed.recordWithMedia', media: imagesEmbed, record: quoteEmbed };