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.

fix: restore working video post structure and correct syntax

jack 3fcbccff 6c973a4f

+17 -22
+17 -22
src/index.ts
··· 437 437 const variants = media.video_info?.variants || []; 438 438 const mp4s = variants 439 439 .filter((v) => v.content_type === 'video/mp4') 440 - .sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0)); // Largest first 440 + .sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0)); 441 441 442 442 if (mp4s.length > 0) { 443 - for (const variant of mp4s) { 444 - const videoUrl = variant.url; 443 + const firstVariant = mp4s[0]; 444 + if (firstVariant) { 445 + const videoUrl = firstVariant.url; 445 446 try { 446 447 const { buffer, mimeType } = await downloadMedia(videoUrl); 447 - if (buffer.length > 95 * 1024 * 1024) continue; 448 - 449 - const blob = await uploadToBluesky(agent, buffer, mimeType); 450 - videoBlob = blob; 451 - videoAspectRatio = aspectRatio; 452 - 453 - if (media.media_url_https) { 454 - try { 455 - const thumb = await downloadMedia(media.media_url_https); 456 - videoThumbnailBlob = await uploadToBluesky(agent, thumb.buffer, thumb.mimeType); 457 - } catch (e) { 458 - console.warn('Failed to upload video thumbnail'); 459 - } 448 + if (buffer.length <= 95 * 1024 * 1024) { 449 + const blob = await uploadToBluesky(agent, buffer, mimeType); 450 + videoBlob = blob; 451 + videoAspectRatio = aspectRatio; 452 + break; 460 453 } 461 - break; 454 + console.warn('Video too large (>95MB). Linking to tweet.'); 455 + const tweetUrl = `https://twitter.com/${twitterUsername}/status/${tweetId}`; 456 + if (!text.includes(tweetUrl)) text += `\n${tweetUrl}`; 462 457 } catch (err) { 463 - console.warn(`Variant failed, trying next:`, (err as Error).message); 458 + console.error(`Failed to upload video ${videoUrl}:`, (err as Error).message); 459 + const tweetUrl = `https://twitter.com/${twitterUsername}/status/${tweetId}`; 460 + if (!text.includes(tweetUrl)) text += `\n${tweetUrl}`; 464 461 } 465 462 } 466 463 } ··· 513 510 // Only attach media/quotes to the first chunk 514 511 if (i === 0) { 515 512 if (videoBlob) { 516 - const videoEmbed: any = { 513 + postRecord.embed = { 517 514 $type: 'app.bsky.embed.video', 518 515 video: videoBlob, 516 + aspectRatio: videoAspectRatio, 519 517 }; 520 - if (videoAspectRatio) videoEmbed.aspectRatio = videoAspectRatio; 521 - if (videoThumbnailBlob) videoEmbed.thumbnail = videoThumbnailBlob; 522 - postRecord.embed = videoEmbed; 523 518 } else if (images.length > 0) { 524 519 const imagesEmbed = { $type: 'app.bsky.embed.images', images }; 525 520 if (quoteEmbed) {