← back to Goodquestion Ai

scripts/bsky-post-image.cjs

98 lines

#!/usr/bin/env node
/**
 * Post to Bluesky with image + link card
 * Usage: node bsky-post-image.cjs --text "..." --image /path/to/image.png --url "https://..." --alt "alt text"
 */
const fs = require('fs');
const path = require('path');
const { BskyAgent, RichText } = require('@atproto/api');

const BSKY_SERVICE = 'https://bsky.social';
const BSKY_HANDLE = 'agentabrams.bsky.social';
const BSKY_PASSWORD = process.env.BSKY_PASSWORD || '*Blueaccess911*';

function parseArgs() {
  const args = process.argv.slice(2);
  const result = {};
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--text' && args[i + 1]) result.text = args[++i];
    if (args[i] === '--image' && args[i + 1]) result.image = args[++i];
    if (args[i] === '--url' && args[i + 1]) result.url = args[++i];
    if (args[i] === '--alt' && args[i + 1]) result.alt = args[++i];
  }
  return result;
}

async function post() {
  const opts = parseArgs();
  if (!opts.text) {
    console.error('Usage: node bsky-post-image.cjs --text "..." --image /path.png --url "https://..."');
    process.exit(1);
  }

  const agent = new BskyAgent({ service: BSKY_SERVICE });
  await agent.login({ identifier: BSKY_HANDLE, password: BSKY_PASSWORD });

  // Build post text with URL
  let fullText = opts.text;
  if (opts.url && !fullText.includes(opts.url)) {
    fullText += '\n\n' + opts.url;
  }

  // Trim to 300 graphemes
  if ([...fullText].length > 300) {
    const chars = [...fullText];
    fullText = chars.slice(0, 297).join('') + '...';
  }

  const rt = new RichText({ text: fullText });
  await rt.detectFacets(agent);

  // Upload image if provided
  let embed = undefined;
  if (opts.image && fs.existsSync(opts.image)) {
    const imgData = fs.readFileSync(opts.image);
    const mimeType = opts.image.endsWith('.jpg') || opts.image.endsWith('.jpeg')
      ? 'image/jpeg' : 'image/png';

    console.log(`Uploading image (${(imgData.length / 1024).toFixed(0)}KB)...`);
    const uploadResp = await agent.uploadBlob(imgData, { encoding: mimeType });

    embed = {
      $type: 'app.bsky.embed.images',
      images: [{
        alt: opts.alt || opts.text.substring(0, 100),
        image: uploadResp.data.blob,
        aspectRatio: { width: 1200, height: 675 },
      }],
    };
  } else if (opts.url) {
    // Fallback to link card if no image
    embed = {
      $type: 'app.bsky.embed.external',
      external: {
        uri: opts.url,
        title: opts.text.substring(0, 60),
        description: 'Read more at goodquestion.ai',
      },
    };
  }

  const result = await agent.post({
    text: rt.text,
    facets: rt.facets,
    embed,
    createdAt: new Date().toISOString(),
  });

  const postId = result.uri.split('/').pop();
  const postUrl = `https://bsky.app/profile/${BSKY_HANDLE}/post/${postId}`;
  console.log('Posted to Bluesky!');
  console.log('URL:', postUrl);
}

post().catch(e => {
  console.error('Error:', e.message);
  process.exit(1);
});