this repo has no description

init

+1
.gitignore
···
··· 1 + .env
+66
README.md
···
··· 1 + # Chicago ATProto Meetup Activities 2 + 3 + Quick hands-on activities to get started with the AT Protocol and Bluesky API. 4 + 5 + ## Setup (5 minutes) 6 + 7 + 1. **Create a Bluesky account** at https://bsky.app 8 + 2. **Generate an app password**: Settings → App Passwords → Add 9 + 3. **Install Bun** (if needed): `curl -fsSL https://bun.sh/install | bash` 10 + 4. **Clone this repo** and create `.env` file: 11 + ``` 12 + BSKY_USERNAME=your-handle.bsky.social 13 + BSKY_PASSWORD=xxxx-xxxx-xxxx-xxxx 14 + ``` 15 + 16 + ## Activities 17 + 18 + ### Activity 1a: Hello World (5 minutes) 19 + ```bash 20 + bun activities/01a-hello-world.js 21 + ``` 22 + Posts a simple message to Bluesky. 23 + 24 + ### Activity 1b: Emoji Art Generator (10 minutes) 25 + ```bash 26 + bun activities/01b-emoji-art.js 27 + ``` 28 + Creates a random emoji art grid and posts it. 29 + 30 + ### Activity 2: Rich Text with Links & Hashtags (10 minutes) 31 + ```bash 32 + bun activities/02-rich-text.js 33 + ``` 34 + Demonstrates auto-detection of links and hashtags in posts. 35 + 36 + ### Activity 3: Feed Generator Metadata (15 minutes) 37 + ```bash 38 + bun activities/03-feed-generator.js 39 + ``` 40 + Creates metadata for a custom feed (note: actual feed requires a server). 41 + 42 + ### Activity 4: Profile Updater (10 minutes) 43 + ```bash 44 + bun activities/04-profile-updater.js 45 + ``` 46 + Updates your profile description with a timestamp. 47 + 48 + ## Starter Template 49 + 50 + Use `starter-template.js` to build your own ideas: 51 + ```bash 52 + bun starter-template.js 53 + ``` 54 + 55 + ## Tips 56 + 57 + - All scripts use Bun's built-in TypeScript support 58 + - The AT Protocol API docs: https://atproto.com 59 + - View your posts at: https://bsky.app/profile/YOUR-HANDLE 60 + - Use `#ATProtoChicago` to find other meetup participants! 61 + 62 + ## Common Issues 63 + 64 + - **"Invalid identifier or password"**: Make sure you're using an app password, not your main password 65 + - **Rate limits**: The API has rate limits, wait a minute if you hit them 66 + - **Module not found**: Run the scripts from the repo root directory
+28
activities/01a-hello-world.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent } from '@atproto/api' 3 + 4 + // Read credentials from .env file 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + process.exit(1) 11 + } 12 + 13 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 14 + 15 + console.log('Logging in...') 16 + await agent.login({ 17 + identifier: username, 18 + password: password 19 + }) 20 + 21 + console.log('Connected! Your DID:', agent.session?.did) 22 + 23 + const post = await agent.post({ 24 + text: 'Hello from the Chicago ATProto meetup! 🚀' 25 + }) 26 + 27 + console.log('Posted successfully!') 28 + console.log('View your post at:', `https://bsky.app/profile/${username}/post/${post.uri.split('/').pop()}`)
+40
activities/01b-emoji-art.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent } from '@atproto/api' 3 + 4 + // Read credentials from .env 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + process.exit(1) 11 + } 12 + 13 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 14 + 15 + console.log('Logging in...') 16 + await agent.login({ 17 + identifier: username, 18 + password: password 19 + }) 20 + 21 + // Generate emoji art 22 + const emojis = ['🎨', '🌟', '🔥', '💫', '🌈', '❄️', '🎭', '🎪', '✨', '🎯', '🚀', '💎'] 23 + const gridSize = 6 24 + const grid = Array(gridSize).fill(null).map(() => 25 + Array(gridSize).fill(null).map(() => 26 + emojis[Math.floor(Math.random() * emojis.length)] 27 + ).join('') 28 + ).join('\n') 29 + 30 + const postText = `Generated emoji art at ${new Date().toLocaleTimeString()}:\n\n${grid}\n\n#ATProtoChicago` 31 + 32 + console.log('Posting emoji art...') 33 + console.log(postText) 34 + 35 + const post = await agent.post({ 36 + text: postText 37 + }) 38 + 39 + console.log('\nPosted successfully!') 40 + console.log('View your post at:', `https://bsky.app/profile/${username}/post/${post.uri.split('/').pop()}`)
+40
activities/02-rich-text.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent, RichText } from '@atproto/api' 3 + 4 + // Read credentials from .env 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + process.exit(1) 11 + } 12 + 13 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 14 + 15 + console.log('Logging in...') 16 + await agent.login({ 17 + identifier: username, 18 + password: password 19 + }) 20 + 21 + // Create rich text with auto-detected links and hashtags 22 + const rt = new RichText({ 23 + text: 'Check out the ATProto docs at https://atproto.com! 🔗\n\nBuilding with the AT Protocol at the Chicago meetup! 🚀\n\n#ATProtoChicago #BuildingOnATProto' 24 + }) 25 + 26 + console.log('Detecting facets (links and hashtags)...') 27 + await rt.detectFacets(agent) 28 + 29 + console.log('\nRich text details:') 30 + console.log('Text:', rt.text) 31 + console.log('Facets:', JSON.stringify(rt.facets, null, 2)) 32 + 33 + console.log('\nPosting with rich text...') 34 + const post = await agent.post({ 35 + text: rt.text, 36 + facets: rt.facets, 37 + }) 38 + 39 + console.log('\nPosted successfully!') 40 + console.log('View your post at:', `https://bsky.app/profile/${username}/post/${post.uri.split('/').pop()}`)
+72
activities/03-feed-generator.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent } from '@atproto/api' 3 + 4 + // Read credentials from .env 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + process.exit(1) 11 + } 12 + 13 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 14 + 15 + console.log('Logging in...') 16 + await agent.login({ 17 + identifier: username, 18 + password: password 19 + }) 20 + 21 + console.log('Connected! Your DID:', agent.session?.did) 22 + 23 + // Create a simple feed generator record 24 + // Note: This creates the metadata for a feed, but you'd need a server to actually generate the feed content 25 + const feedRecord = { 26 + did: `did:plc:${agent.session.did.split(':')[2]}`, // Use your actual DID 27 + displayName: 'Chicago ATProto Meetup', 28 + description: 'Posts from our Chicago ATProto meetup! Tag your posts with #ATProtoChicago', 29 + avatar: undefined, 30 + createdAt: new Date().toISOString() 31 + } 32 + 33 + const rkey = 'chicago-meetup-feed' 34 + 35 + console.log('\nCreating feed generator record...') 36 + console.log('Feed details:', JSON.stringify(feedRecord, null, 2)) 37 + 38 + try { 39 + // First check if it already exists 40 + const existing = await agent.com.atproto.repo.getRecord({ 41 + repo: agent.session.did, 42 + collection: 'app.bsky.feed.generator', 43 + rkey: rkey 44 + }).catch(() => null) 45 + 46 + if (existing) { 47 + console.log('\nFeed already exists! Updating...') 48 + await agent.com.atproto.repo.putRecord({ 49 + repo: agent.session.did, 50 + collection: 'app.bsky.feed.generator', 51 + rkey: rkey, 52 + record: feedRecord, 53 + swapRecord: existing.data.cid 54 + }) 55 + } else { 56 + await agent.com.atproto.repo.putRecord({ 57 + repo: agent.session.did, 58 + collection: 'app.bsky.feed.generator', 59 + rkey: rkey, 60 + record: feedRecord 61 + }) 62 + } 63 + 64 + console.log('\nFeed generator record created successfully!') 65 + console.log(`\nNote: This creates the feed metadata. To make it functional, you would need:`) 66 + console.log('1. A server running the feed generation logic') 67 + console.log('2. The feed to be published and indexed by Bluesky') 68 + console.log(`\nYour feed URI: at://${agent.session.did}/app.bsky.feed.generator/${rkey}`) 69 + 70 + } catch (error) { 71 + console.error('Error creating feed:', error.message) 72 + }
+71
activities/04-profile-updater.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent } from '@atproto/api' 3 + 4 + // Read credentials from .env 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + process.exit(1) 11 + } 12 + 13 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 14 + 15 + console.log('Logging in...') 16 + await agent.login({ 17 + identifier: username, 18 + password: password 19 + }) 20 + 21 + console.log('Fetching current profile...') 22 + const profile = await agent.getProfile({ actor: agent.session.did }) 23 + 24 + console.log('\nCurrent profile:') 25 + console.log('Display name:', profile.data.displayName || '(not set)') 26 + console.log('Description:', profile.data.description || '(not set)') 27 + 28 + // Simple approach: just add a timestamp to the description 29 + const timestamp = `[Updated at Chicago Meetup ${new Date().toLocaleTimeString()}]` 30 + const currentDescription = profile.data.description || '' 31 + const newDescription = currentDescription.includes('[Updated at Chicago Meetup') 32 + ? currentDescription.replace(/\[Updated at Chicago Meetup .*?\]/, timestamp) 33 + : `${currentDescription}\n\n${timestamp}`.trim() 34 + 35 + console.log('\nUpdating profile description...') 36 + console.log('New description:', newDescription) 37 + 38 + try { 39 + // Use the upsertProfile method if available 40 + if (agent.upsertProfile) { 41 + await agent.upsertProfile((existing) => ({ 42 + ...existing, 43 + displayName: profile.data.displayName, 44 + description: newDescription 45 + })) 46 + } else { 47 + // Fallback to manual update 48 + const profileRecord = { 49 + $type: 'app.bsky.actor.profile', 50 + displayName: profile.data.displayName || '', 51 + description: newDescription 52 + } 53 + 54 + // Copy avatar and banner if they exist 55 + if (profile.data.avatar) profileRecord.avatar = profile.data.avatar 56 + if (profile.data.banner) profileRecord.banner = profile.data.banner 57 + 58 + await agent.com.atproto.repo.putRecord({ 59 + repo: agent.session.did, 60 + collection: 'app.bsky.actor.profile', 61 + rkey: 'self', 62 + record: profileRecord 63 + }) 64 + } 65 + 66 + console.log('\nProfile updated successfully!') 67 + console.log(`View your profile at: https://bsky.app/profile/${username}`) 68 + } catch (error) { 69 + console.error('Error updating profile:', error) 70 + console.error('Details:', error.message) 71 + }
+34
starter-template.js
···
··· 1 + #!/usr/bin/env bun 2 + import { BskyAgent } from '@atproto/api' 3 + 4 + // Read credentials from .env file 5 + const username = process.env.BSKY_USERNAME 6 + const password = process.env.BSKY_PASSWORD 7 + 8 + if (!username || !password) { 9 + console.error('Please provide BSKY_USERNAME and BSKY_PASSWORD in .env file') 10 + console.error('Format:') 11 + console.error('BSKY_USERNAME=your-handle.bsky.social') 12 + console.error('BSKY_PASSWORD=xxxx-xxxx-xxxx-xxxx') 13 + process.exit(1) 14 + } 15 + 16 + async function main() { 17 + const agent = new BskyAgent({ service: 'https://bsky.social' }) 18 + 19 + await agent.login({ 20 + identifier: username, 21 + password: password 22 + }) 23 + 24 + console.log('Connected! Your DID:', agent.session?.did) 25 + 26 + // Your code here! 27 + // Try: 28 + // - agent.post({ text: 'Hello world!' }) 29 + // - agent.getProfile({ actor: agent.session.did }) 30 + // - agent.getTimeline() 31 + 32 + } 33 + 34 + main().catch(console.error)