Uses atcute to show how you can do upserts with atproto records
at main 3.8 kB view raw
1import 'dotenv/config'; 2import { Client, CredentialManager, ok } from '@atcute/client'; 3import * as TID from '@atcute/tid'; 4 5//You want to make sure this is always the same for the applications you are generating upsert tids for 6//If you use the same timestamp but a different clock id, you will get different tids 7const CLOCK_ID = 23; 8 9const logErrorAndThrow = (err) => { 10 console.error(err); 11 throw err; 12}; 13 14 15//Loading in env variables can ignore 16const handle = process.env.HANDLE; 17if (!handle) logErrorAndThrow('HANDLE is not set'); 18const appPassword = process.env.APP_PASSWORD; 19if (!appPassword) logErrorAndThrow('APP_PASSWORD is not set'); 20const pdsHost = process.env.PDS_HOST; 21if (!pdsHost) logErrorAndThrow('PDS_HOST is not set'); 22 23 24//Sets up the atcute client 25const manager = new CredentialManager({ service: pdsHost }); 26const rpc = new Client({ handler: manager }); 27 28// sign in with handle/email and password (or app password (please be an app password)) 29await manager.login({ identifier: handle, password: appPassword}); 30 31 32 33// The actual example starts here, everything else before is boilerplate 34 35 36//reversing your handle to make a fun example lexicon 37const reversedHandle = handle.split('.').reverse().map(word => word).join('.'); 38//An activity here is like a workout. running, walking, lifting weights, etc. 39let collection = `${reversedHandle}.feed.activity` 40 41//A list of activities that may be gotten from your phone or wherever, but you get the whole list everytime 42let activities = [] 43 44// Helper function to upload activities 45const uploadActivities = async (activities, handle, collection, rpc) => { 46 47 for (const activity of activities) { 48 49 //Creates that unique key from the startTime of the activity so we don't have duplicates 50 let rKey = TID.create(activity.startTime.getTime() * 1000, CLOCK_ID); 51 52 await ok(rpc.post('com.atproto.repo.putRecord', { 53 input: { 54 repo: handle, 55 collection, 56 rkey: rKey, 57 record: activity, 58 59 } 60 })); 61 console.log(`Uploaded activity with rkey: ${rKey}`); 62 } 63 64} 65 66 67//I just finished a run, it's saved to my phone, now uploading it to the PDS 68activities.push({ 69 $type: collection, 70 type: 'run', 71 startTime: new Date() 72}) 73 74console.log('You just finished a run. Uploading it to the PDS.'); 75 76//Upload my activities 77await uploadActivities(activities, handle, collection, rpc); 78 79//Going to generate the key here to show you can find the record from it easily if you have a date 80const rkey = TID.create(activities[0].startTime.getTime() * 1000, CLOCK_ID); 81 82const activityFromPDS = await ok(rpc.get('com.atproto.repo.getRecord', { 83 params: { 84 repo: handle, 85 collection, 86 rkey, 87 } 88})); 89 90console.log(`The PDS shows you went on a ${activityFromPDS.value.type} at ${activityFromPDS.value.startTime.toLocaleString()}.`); 91 92 93//I decide I want to go on a walk later, so I add it to the list 94activities.push({ 95 $type: collection, 96 type: 'walk', 97 startTime: new Date() 98}) 99 100console.log('You just finished a walk. Uploading it to the PDS.'); 101 102//upload the new activities so the newest one can sync 103await uploadActivities(activities, handle, collection, rpc); 104 105const listResult = await ok(rpc.get('com.atproto.repo.listRecords', { 106 params: { 107 repo: handle, 108 collection, 109 limit: 10, 110 } 111})); 112 113 114console.log("Since you did an upsert you should only have 2 records even tho you uploaded 3.") 115console.log(`You have ${listResult.records.length} activities saved in the PDS.`); 116for (const recordIndex in listResult.records){ 117 const record = listResult.records[recordIndex] 118 console.log(`The PDS shows you went on a ${record.value.type} at ${record.value.startTime.toLocaleString()}.`); 119}