open source is social v-it.org
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'hopper-qvcb3x2d-env-plc-test'

+163 -1
+3
.gitignore
··· 5 5 node_modules/ 6 6 bun.lockb 7 7 bun.lock 8 + 9 + # Environment 10 + .env
+21
README.md
··· 26 26 1. Start a temporary localhost callback server 27 27 2. Open your browser to the Bluesky authorization page 28 28 3. After you approve, print the DPoP-bound access token and DID 29 + 4. Save credentials (`BSKY_DID`, `BSKY_ACCESS_TOKEN`, `BSKY_REFRESH_TOKEN`, `BSKY_EXPIRES_AT`) to `.env` 30 + 31 + If `.env` already exists, only the `BSKY_*` variables are updated — other variables are preserved. 29 32 30 33 ### Options 31 34 ··· 36 39 ### Notes 37 40 38 41 The access token is DPoP-bound, meaning it requires a DPoP proof JWT for each API request. The token cannot be used as a simple Bearer token. Token refresh is not implemented in this version. 42 + 43 + ## PLC Test 44 + 45 + Verify your saved DID against the PLC directory: 46 + 47 + ``` 48 + bun plc_test.js 49 + ``` 50 + 51 + This reads `BSKY_DID` from `.env` and: 52 + - Resolves the DID document via `https://plc.directory/{did}` 53 + - Fetches the audit log 54 + - Prints a summary of handles, services, verification methods, and operation history 55 + 56 + ### Options 57 + 58 + - `--did <did>` — Check a specific DID (overrides `.env`) 59 + - `-v, --verbose` — Show full API responses
+33 -1
bsky_oauth.js
··· 2 2 3 3 import { NodeOAuthClient } from '@atproto/oauth-client-node'; 4 4 import { Command } from 'commander'; 5 - import { writeFileSync } from 'node:fs'; 5 + import { readFileSync, writeFileSync } from 'node:fs'; 6 6 7 7 function createStore() { 8 8 const map = new Map(); ··· 16 16 map.delete(key); 17 17 }, 18 18 }; 19 + } 20 + 21 + function saveToEnv(vars) { 22 + const envPath = new URL('.env', import.meta.url).pathname; 23 + let lines = []; 24 + try { 25 + lines = readFileSync(envPath, 'utf-8').split('\n'); 26 + } catch {} 27 + // Strip trailing empty lines from parsed content 28 + while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop(); 29 + const updated = new Set(); 30 + for (let i = 0; i < lines.length; i++) { 31 + const m = lines[i].match(/^([A-Za-z_][A-Za-z0-9_]*)=/); 32 + if (m && m[1] in vars) { 33 + lines[i] = `${m[1]}=${vars[m[1]]}`; 34 + updated.add(m[1]); 35 + } 36 + } 37 + for (const [key, value] of Object.entries(vars)) { 38 + if (!updated.has(key)) { 39 + lines.push(`${key}=${value}`); 40 + } 41 + } 42 + writeFileSync(envPath, lines.join('\n') + '\n'); 19 43 } 20 44 21 45 async function main() { ··· 169 193 if (output) { 170 194 writeFileSync(output, `${JSON.stringify(outputData, null, 2)}\n`); 171 195 } 196 + 197 + saveToEnv({ 198 + BSKY_DID: outputData.did, 199 + BSKY_ACCESS_TOKEN: outputData.accessToken ?? '', 200 + BSKY_REFRESH_TOKEN: outputData.refreshToken ?? '', 201 + BSKY_EXPIRES_AT: outputData.expiresAt ?? '', 202 + }); 203 + console.log('Saved credentials to .env'); 172 204 } catch (err) { 173 205 console.error(err instanceof Error ? err.message : String(err)); 174 206 process.exitCode = 1;
+106
plc_test.js
··· 1 + #!/usr/bin/env bun 2 + 3 + import { readFileSync } from 'node:fs'; 4 + import { Command } from 'commander'; 5 + 6 + function loadEnv() { 7 + const envPath = new URL('.env', import.meta.url).pathname; 8 + const vars = {}; 9 + let content; 10 + try { 11 + content = readFileSync(envPath, 'utf-8'); 12 + } catch { 13 + return vars; 14 + } 15 + for (const line of content.split('\n')) { 16 + const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)/); 17 + if (m) vars[m[1]] = m[2]; 18 + } 19 + return vars; 20 + } 21 + 22 + async function main() { 23 + const program = new Command(); 24 + 25 + program 26 + .name('plc_test') 27 + .description('Verify PLC directory entry for saved Bluesky DID') 28 + .option('-v, --verbose', 'Show full API responses') 29 + .option('--did <did>', 'DID to check (overrides .env)') 30 + .parse(); 31 + 32 + const opts = program.opts(); 33 + 34 + try { 35 + const env = loadEnv(); 36 + const did = opts.did || env.BSKY_DID; 37 + 38 + if (!did) { 39 + throw new Error('No DID found. Run bsky_oauth.js first or pass --did <did>.'); 40 + } 41 + 42 + if (!did.startsWith('did:plc:')) { 43 + throw new Error(`Expected a did:plc: identifier, got: ${did}`); 44 + } 45 + 46 + console.log(`Checking DID: ${did}\n`); 47 + 48 + // Resolve DID document 49 + const docUrl = `https://plc.directory/${did}`; 50 + const docRes = await fetch(docUrl); 51 + if (!docRes.ok) { 52 + throw new Error(`PLC directory returned ${docRes.status} for ${docUrl}`); 53 + } 54 + const doc = await docRes.json(); 55 + 56 + if (opts.verbose) { 57 + console.log('[verbose] DID document:'); 58 + console.log(JSON.stringify(doc, null, 2)); 59 + console.log(); 60 + } 61 + 62 + // Validate expected fields 63 + const handles = doc.alsoKnownAs ?? []; 64 + const services = doc.service ?? []; 65 + const verificationMethods = doc.verificationMethod ?? []; 66 + 67 + console.log(`DID document:`); 68 + console.log(` id: ${doc.id}`); 69 + console.log(` handles: ${handles.join(', ') || '(none)'}`); 70 + console.log(` services: ${services.map(s => `${s.id} (${s.type})`).join(', ') || '(none)'}`); 71 + console.log(` verification methods: ${verificationMethods.length}`); 72 + console.log(); 73 + 74 + // Fetch audit log 75 + const auditUrl = `https://plc.directory/${did}/log/audit`; 76 + const auditRes = await fetch(auditUrl); 77 + if (!auditRes.ok) { 78 + throw new Error(`PLC directory returned ${auditRes.status} for ${auditUrl}`); 79 + } 80 + const auditLog = await auditRes.json(); 81 + 82 + if (opts.verbose) { 83 + console.log('[verbose] Audit log:'); 84 + console.log(JSON.stringify(auditLog, null, 2)); 85 + console.log(); 86 + } 87 + 88 + console.log(`Audit log: ${auditLog.length} operation(s)`); 89 + if (auditLog.length > 0) { 90 + const first = auditLog[0]; 91 + const last = auditLog[auditLog.length - 1]; 92 + console.log(` first: ${first.createdAt}`); 93 + if (auditLog.length > 1) { 94 + console.log(` latest: ${last.createdAt}`); 95 + } 96 + } 97 + console.log(); 98 + 99 + console.log('All checks passed.'); 100 + } catch (err) { 101 + console.error(err instanceof Error ? err.message : String(err)); 102 + process.exitCode = 1; 103 + } 104 + } 105 + 106 + await main();