Skip to content

Instantly share code, notes, and snippets.

@hrbrmstr
Forked from haileyok/bean-labeler.ts
Created March 16, 2024 08:56

Revisions

  1. @haileyok haileyok revised this gist Mar 16, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion bean-labeler.ts
    Original file line number Diff line number Diff line change
    @@ -132,7 +132,7 @@ const handleIncoming = async (parentUri: string, root: {uri: string, cid: string
    uri: post.uri,
    cid: post.cid,
    },
    createdBy: (await agent.resolveHandle({handle: process.env.BSKY_HANDLE ?? ''})).data.did,
    createdBy: agent.session?.did ?? '',
    createdAt: new Date().toISOString(),
    subjectBlobCids: [],
    })
  2. @haileyok haileyok created this gist Mar 16, 2024.
    183 changes: 183 additions & 0 deletions bean-labeler.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,183 @@
    import {
    AppBskyEmbedImages,
    AppBskyFeedPost,
    BskyAgent,
    } from '@atproto/api'
    import * as dotenv from 'dotenv'
    import {ComAtprotoSyncSubscribeRepos, subscribeRepos, SubscribeReposMessage} from 'atproto-firehose'
    import Anthropic from '@anthropic-ai/sdk'
    import axios from 'axios'

    dotenv.config()

    const processedUris = new Set<string>()

    let working = false

    const agent = new BskyAgent({
    service: 'https://bsky.social/',
    })
    const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY ?? '',
    })

    const login = async () => {
    await agent.login({
    identifier: process.env.BSKY_HANDLE ?? '',
    password: process.env.BSKY_PASSWORD ?? '',
    })
    }

    const makePost = async (text: string, root: {uri: string, cid: string}, initialParent: {uri: string, cid: string}) => {
    if (text.length <= 250) {
    return [text];
    }

    const words = text.split(' ');
    const parts = [];
    let currentLine = '';

    for (const word of words) {
    if ((currentLine + word).length <= 250) {
    currentLine += (currentLine === '' ? '' : ' ') + word;
    } else {
    parts.push(currentLine);
    currentLine = word;
    }
    }

    if (currentLine !== '') {
    parts.push(currentLine);
    }

    let nextParent = initialParent
    for (const part of parts) {
    const res = await agent.post({
    text: part,
    reply: {
    root,
    parent: nextParent
    },
    })

    nextParent = {uri: res.uri, cid: res.cid}
    }
    }

    const getImageBase64 = async (uri: string) => {
    // This should get the base64 of an image from a remote URI
    const res = await axios.get(uri, {responseType: 'arraybuffer'})
    return Buffer.from(res.data, 'binary').toString('base64')
    }

    const checkImage = async (base64: string) => {
    return anthropic.messages.create({
    model: 'claude-3-sonnet-20240229',
    max_tokens: 400,
    temperature: 0,
    system: 'I am the best bean identifier in the world! I am so good at finding beans even when they are hidden in images! ' +
    'My assigned task is to identify images that include beans of any kind. I am very skilled at identifying beans! However, ' +
    'users have begun to realize that I am indeed pretty good at identifying beans, so they have started trying to hide the ' +
    'beans in photos. For example, a Listerine bottle might be filled with beans, or a popsicle might be made out of frozen ' +
    'beans. The users put beans in everything!\n\nI must take my job very seriously to identify beans of any kind in photos. ' +
    'Be sure to look everywhere for beans!\n\nMy assigned task is: Identify photos that have beans of any kind. If I identify beans, ' +
    'I should provide my reasoning for believing there are or are not beans in the photo, enter a blank line, and then enter \'TRUE\' ' +
    'if I find beans or \'FALSE\' if I do not find any beans.\n\nThere\'s some special things I should pay attention to, since I am one ' +
    'of the top - if not THE top - bean identifier.\n\nI should also weigh the chances of a questionable item being a bean. If I cannot ' +
    'tell for sure that an item is not a bean, but I believe it has an 50% or higher chance of being a bean, I should respond TRUE. For ' +
    'example, if there is a liquid visible, it MIGHT be bean liquid and I should take that into consideration. If there are many small, ' +
    'brown items inside of a liquid, I should also consider whether this might be a bean. If there is even a possibility that something ' +
    'might be a bean, I should say it is a bean.',
    messages: [
    {
    'role': 'user',
    'content': [
    {
    'type': 'image',
    'source': {
    'type': 'base64',
    'media_type': 'image/jpeg',
    'data': base64
    }
    }
    ]
    }
    ]
    })
    }

    const handleIncoming = async (parentUri: string, root: {uri: string, cid: string}, curr: {uri: string, cid: string}) => {
    if(processedUris.has(parentUri)) return

    const post = (await agent.getPosts({uris: [parentUri]})).data.posts[0]
    console.log('try')

    if (post.embed && AppBskyEmbedImages.isView(post.embed)) {
    if(working) return
    try {
    working = true
    const image = post.embed.images[0]
    const base64 = await getImageBase64(image.fullsize)
    const result = await checkImage(base64)

    if (result.content[0].text.includes('TRUE')) {
    await agent.withProxy('atproto_labeler', 'did:plc:3ehw5dwwptcy3xuzugwq2u6t').api.tools.ozone.moderation.emitEvent({
    event: {
    $type: 'tools.ozone.moderation.defs#modEventLabel',
    createLabelVals: ['beans'],
    negateLabelVals: [],
    },
    subject: {
    $type: 'com.atproto.repo.strongRef',
    uri: post.uri,
    cid: post.cid,
    },
    createdBy: (await agent.resolveHandle({handle: process.env.BSKY_HANDLE ?? ''})).data.did,
    createdAt: new Date().toISOString(),
    subjectBlobCids: [],
    })
    }

    makePost(result.content[0].text.replace('TRUE', 'Labeling this HORRIBLE bean post!'), root, curr)
    } catch(e) {
    console.log(e)
    } finally {
    working = false
    }
    } else {
    makePost('There is not a image here for me, the Best Identifier of Beans, to identify!', root, curr)
    }

    processedUris.add(parentUri)
    }

    const handleMessage = (message: SubscribeReposMessage): void => {
    if (ComAtprotoSyncSubscribeRepos.isCommit(message)) {
    const repo = message.repo
    const op = message.ops[0]

    if(
    AppBskyFeedPost.isRecord(op?.payload) &&
    op.payload.text.includes(process.env.BSKY_HANDLE ?? '') &&
    op.payload.reply
    ) {
    const uri = `at://${repo}/${op.path}`
    const cid = op.cid?.toString()

    if(!cid) return

    handleIncoming(op.payload.reply.parent.uri, op.payload.reply.root, {uri, cid})
    }
    }
    }

    const run = async () => {
    await login()

    const firehose = subscribeRepos('wss://bsky.network', {
    decodeRepoOps: true,
    })
    firehose.on('message', handleMessage)
    }

    run()