← back to Goodquestion Ai

scripts/engage-profiles.cjs

90 lines

const path = require('path');
const { BskyAgent, RichText } = require(path.join('/root/Projects/goodquestion-ai', 'node_modules', '@atproto/api'));
require('dotenv').config({ path: '/root/Projects/goodquestion-ai/.env' });

async function engageWithProfiles() {
  const agent = new BskyAgent({ service: 'https://bsky.social' });
  await agent.login({ identifier: 'agentabrams.bsky.social', password: process.env.BSKY_PASSWORD });

  const targets = [
    {
      handle: 'kulpinski.dev',
      reply: 'Fellow build-in-public builder! OpenAlternative.co is a great concept — curating open-source alternatives is genuinely useful. How are you handling discovery and ranking? Always curious how other builders approach the curation problem.'
    },
    {
      handle: 'charlesuchi.bsky.social',
      reply: 'Thanks for the consistent support! Checked out FoggoApps — shipping free apps takes real dedication. What stack are you building on? Would love to hear how you approach the app development workflow.'
    },
    {
      handle: 'vickieee.bsky.social',
      reply: 'Full stack Laravel in Australia — solid choice. The intersection of code and art/crafts is underrated. Are you using any AI tooling in your Laravel workflow? Always looking for perspectives from different stacks.'
    }
  ];

  for (const t of targets) {
    try {
      // Get their recent posts
      const feed = await agent.getAuthorFeed({ actor: t.handle, limit: 5 });
      const posts = feed.data.feed || [];

      if (posts.length > 0) {
        // Like their most recent post
        const latest = posts[0].post;
        try {
          await agent.like(latest.uri, latest.cid);
          console.log('[liked] @' + t.handle + ': ' + (latest.record?.text || '').substring(0, 60));
        } catch (e) {
          console.log('[skip like] @' + t.handle + ' — ' + e.message.substring(0, 50));
        }

        // Find a good post to reply to (their original content, not reposts)
        const original = posts.find(p => !p.reason && p.post.author.handle === t.handle);
        if (original) {
          const post = original.post;
          const rt = new RichText({ text: t.reply });
          await rt.detectFacets(agent);

          let root = { uri: post.uri, cid: post.cid };
          if (post.record?.reply?.root) {
            root = post.record.reply.root;
          }

          const result = await agent.post({
            text: rt.text,
            facets: rt.facets,
            reply: {
              root,
              parent: { uri: post.uri, cid: post.cid },
            },
            createdAt: new Date().toISOString(),
          });

          const rkey = result.uri.split('/').pop();
          console.log('[replied] @' + t.handle + ' → https://bsky.app/profile/agentabrams.bsky.social/post/' + rkey);
        }
      }

      // Follow them if not already following
      try {
        const profile = await agent.getProfile({ actor: t.handle });
        if (!profile.data.viewer?.following) {
          await agent.follow(profile.data.did);
          console.log('[followed] @' + t.handle);
        } else {
          console.log('[already following] @' + t.handle);
        }
      } catch (e) {
        console.log('[follow skip] ' + e.message.substring(0, 50));
      }

      await new Promise(r => setTimeout(r, 2000));
    } catch (err) {
      console.error('[error] @' + t.handle + ': ' + err.message);
    }
  }

  console.log('\nDone!');
}

engageWithProfiles().catch(e => console.error(e.message));