목록으로

블루스카이 봇 만들기

블루스카이 봇(선택봇 @choicebot.bsky.social)을 만든 일지.

봇 기능

공백으로 구분된 선택지와 함께 봇을 멘션하면, 그 중 하나를 골라서 reply 해주는 봇

예: “고양이 강아지 망아지 부엉이” 멘션 ➡️ “부엉이” 응답

기술 스택

기능이 매우 간단하므로 Deno를 썼다. 무료 오라클 클라우드 인스턴스에 배포했다.

Jetstream

https://github.com/bluesky-social/jetstream : 블루스카이는 개발자를 위해서 모든 데이터 스트림을 ws로 제공한다. 봇 핸들을 태그한 포스트를 감지하기 위해 사용.

websocket을 그냥 쓰면 타입이 안 되어 있으니 @skyware/jetstream을 사용한다.

const jetstream = new Jetstream({
  wantedCollections: ["app.bsky.feed.post"], // 포스트 정보만 가져오기
});

jetstream.onCreate("app.bsky.feed.post", (event) => {
  if (shouldSendMessage()) {
    sendMessage();
  }
});

jetstream.start();

event 속에 본문, 멘션 정보 등 봇 동작에 필요한 정보가 담겨 있다.

포스트 감지

멘션 감지

export function isMention(
  event: CommitCreateEvent<"app.bsky.feed.post">,
  botDid: string
) {
  return (
    event.commit.record.facets?.some((facet) =>
      facet.features.some(
        (feature) =>
          feature.$type === "app.bsky.richtext.facet#mention" &&
          feature.did === botDid
      )
    ) ?? false
  );
}

event.commit.record.facets 안에 멘션 정보가 들어있다. 멘션한 DID가 봇의 DID와 같을 경우 true.

reply 감지

reply(스레드)에는 멘션 정보가 없기 때문에 따로 처리해줘야 한다.

export function isReply(
  event: CommitCreateEvent<"app.bsky.feed.post">,
  botDid: string
) {
  if (event.commit.record.reply) {
    const uri = new AtUri(event.commit.record.reply.parent.uri);
    if (uri.host === botDid) {
      return true;
    }
  }
  return false;
}

reply의 부모 포스트의 uri에 봇 DID가 있는 경우 true.

보내기

@atproto/api를 써서 포스트를 생성하려면 com.atproto.repo.createRecord를 사용하면 된다.

agent.com.atproto.repo.createRecord({
  collection: "app.bsky.feed.post",
  record: {
    $type: "app.bsky.feed.post",
    text,
    createdAt: new Date().toISOString(),
    reply: {
      root: { uri, cid },
      parent: { uri, cid },
    },
  },
  repo: BOT_DID,
});

포스트의 reply가 되는 것이므로 멘션 정보는 적지 않고 부모와 최상위 포스트의 uri cid 를 작성해준다. repo 는 봇의 DID.

Rate Limiting

블루스카이의 rate limits는 포스트 생성 기준 1시간 1666개다(https://docs.bsky.app/docs/advanced-guides/rate-limits). p-ratelimit을 써서 널널하게 1시간에 1600개로 설정했다.

배포

오라클 클라우드의 인스턴스에 pup을 써서 구동했다(왜 pm2를 안 썼냐면… 인스턴스에 node.js를 깔고 싶지 않았음).

‼️미래의 나에게 조언: 절대로 오라클 리눅스를 쓰지 말 것. 한국 패키지 매니저 서버가 너무 느림. 우분투를 써야 함.

후기

봇 기능이 간단해서 그런지 코드도 100줄 정도밖에 안 된다. SNS 자동봇 주제에 기능이 복잡하면 그게 이상한 거지만…

단어 선택 말고 다른 기능을 넣는다고 해도 기존 코드 90%는 재활용이 가능하니까 아주 간단하게 만들 수 있을 듯.