this repo has no description
0
fork

Configure Feed

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

PLC scripts

futurGH d463fa78 31a4c304

+179 -1
+2
.eslintrc.json
··· 10 10 "@typescript-eslint/no-misused-promises": "off", 11 11 "@typescript-eslint/no-unnecessary-condition": "off", 12 12 "@typescript-eslint/no-unsafe-assignment": "off", 13 + "@typescript-eslint/no-unsafe-call": "off", 14 + "@typescript-eslint/no-unsafe-member-access": "off", 13 15 "@typescript-eslint/restrict-plus-operands": "off", 14 16 "@typescript-eslint/restrict-template-expressions": "off", 15 17 "@typescript-eslint/unified-signatures": "off"
+7 -1
package.json
··· 20 20 "fmt": "dprint fmt", 21 21 "prepublish": "pnpm lint && pnpm fmt && pnpm build" 22 22 }, 23 + "exports": { 24 + ".": "./dist/index.js", 25 + "./scripts/*": "./dist/scripts/*.js" 26 + }, 23 27 "devDependencies": { 24 28 "@atproto/api": "^0.13.1", 25 29 "@types/better-sqlite3": "^7.6.11", ··· 34 38 "typescript": "^5.2.2" 35 39 }, 36 40 "dependencies": { 41 + "@atproto/crypto": "^0.4.0", 37 42 "@atproto/xrpc-server": "^0.6.2", 38 43 "better-sqlite3": "^11.1.2", 39 44 "express": "^4.19.2", 40 - "express-ws": "^5.0.2" 45 + "express-ws": "^5.0.2", 46 + "uint8arrays": "^5.1.0" 41 47 }, 42 48 "files": [ 43 49 "dist"
+16
pnpm-lock.yaml
··· 5 5 excludeLinksFromLockfile: false 6 6 7 7 dependencies: 8 + '@atproto/crypto': 9 + specifier: ^0.4.0 10 + version: 0.4.0 8 11 '@atproto/xrpc-server': 9 12 specifier: ^0.6.2 10 13 version: 0.6.2 ··· 17 20 express-ws: 18 21 specifier: ^5.0.2 19 22 version: 5.0.2(express@4.19.2) 23 + uint8arrays: 24 + specifier: ^5.1.0 25 + version: 5.1.0 20 26 21 27 devDependencies: 22 28 '@atproto/api': ··· 1522 1528 resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1523 1529 dev: false 1524 1530 1531 + /multiformats@13.2.2: 1532 + resolution: {integrity: sha512-RWI+nyf0q64vyOxL8LbKtjJMki0sogRL/8axvklNtiTM0iFCVtHwME9w6+0P1/v4dQvsIg8A45oT3ka1t/M/+A==} 1533 + dev: false 1534 + 1525 1535 /multiformats@9.9.0: 1526 1536 resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} 1527 1537 ··· 2061 2071 resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} 2062 2072 dependencies: 2063 2073 multiformats: 9.9.0 2074 + 2075 + /uint8arrays@5.1.0: 2076 + resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==} 2077 + dependencies: 2078 + multiformats: 13.2.2 2079 + dev: false 2064 2080 2065 2081 /undici-types@5.26.5: 2066 2082 resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+154
src/scripts/plc.ts
··· 1 + import { AtpAgent, ComAtprotoIdentitySignPlcOperation } from "@atproto/api"; 2 + import { P256Keypair, Secp256k1Keypair } from "@atproto/crypto"; 3 + import * as ui8 from "uint8arrays"; 4 + 5 + /** Options for the {@link plcSetupLabeler} function. */ 6 + export interface PlcSetupLabelerOptions { 7 + /** The HTTPS URL where the labeler is hosted. */ 8 + endpoint: string; 9 + 10 + /** 11 + * The token to use to sign the PLC operation. 12 + * If you don't have a token, first call {@link plcRequestToken} to receive one via email. 13 + */ 14 + plcToken: string; 15 + 16 + /** The URL of the PDS where the labeler account is located, if different from bsky.social. */ 17 + pds?: string; 18 + /** The DID of the labeler account. */ 19 + did: string; 20 + /** The password of the labeler account. You must provide either `password` or `agent`. */ 21 + password?: string; 22 + /** An agent logged into the labeler account. You must provide either `password` or `agent`. */ 23 + agent?: AtpAgent; 24 + 25 + /** You may choose to provide your own signing key to use for the labeler. */ 26 + privateKey?: string | Uint8Array; 27 + /** The algorithm of the provided private key. */ 28 + privateKeyAlgorithm?: "secp256k1" | "secp256r1"; 29 + /** Whether to overwrite the existing label signing key if one is already set. */ 30 + overwriteExistingKey?: boolean; 31 + } 32 + 33 + /** 34 + * This function will update the labeler account's DID document to include the 35 + * provided labeler endpoint and signing key. If no private key is provided, a 36 + * new keypair will be generated, and the private key will be printed to the 37 + * console. This private key will be needed to sign any labels created. 38 + * @param options Options for the function. 39 + */ 40 + export async function plcSetupLabeler(options: PlcSetupLabelerOptions) { 41 + if (!options.agent && !options.password) { 42 + throw new Error( 43 + "Either a logged-in agent or a password must be provided for the labeler account.", 44 + ); 45 + } 46 + 47 + const agent = options.agent ?? new AtpAgent({ service: options.pds || "https://bsky.social" }); 48 + if (!agent.hasSession) { 49 + if (!options.password) { 50 + throw new Error("A password must be provided to log in to the labeler account."); 51 + } 52 + await agent.login({ identifier: options.did, password: options.password }); 53 + } 54 + 55 + let keypair: Secp256k1Keypair | P256Keypair; 56 + if (options.privateKey) { 57 + if (options.privateKeyAlgorithm === "secp256r1") { 58 + keypair = await P256Keypair.import(options.privateKey); 59 + } else if (options.privateKeyAlgorithm === "secp256k1") { 60 + keypair = await Secp256k1Keypair.import(options.privateKey); 61 + } else { 62 + throw new Error("Invalid private key algorithm."); 63 + } 64 + } else { 65 + keypair = await Secp256k1Keypair.create({ exportable: true }); 66 + } 67 + 68 + const keyDid = keypair.did(); 69 + 70 + const operation: ComAtprotoIdentitySignPlcOperation.InputSchema = {}; 71 + 72 + const credentials = await agent.com.atproto.identity.getRecommendedDidCredentials(); 73 + if (!credentials.success) { 74 + throw new Error("Failed to fetch DID document."); 75 + } 76 + 77 + if ( 78 + !credentials.data.verificationMethods 79 + || !("atproto_label" in credentials.data.verificationMethods) 80 + || !credentials.data.verificationMethods["atproto_label"] 81 + || (credentials.data.verificationMethods["atproto_label"] !== keyDid 82 + && options.overwriteExistingKey) 83 + ) { 84 + operation.verificationMethods = { 85 + ...(credentials.data.verificationMethods || {}), 86 + atproto_label: keyDid, 87 + }; 88 + } 89 + 90 + if ( 91 + !credentials.data.services 92 + || !("atproto_label" in credentials.data.services) 93 + || !credentials.data.services["atproto_label"] 94 + || typeof credentials.data.services["atproto_label"] !== "object" 95 + || !("endpoint" in credentials.data.services["atproto_label"]) 96 + || credentials.data.services["atproto_label"].endpoint !== options.endpoint 97 + ) { 98 + operation.services = { 99 + ...(credentials.data.services || {}), 100 + atproto_label: { type: "AtprotoLabeler", endpoint: options.endpoint }, 101 + }; 102 + } 103 + 104 + if (Object.keys(operation).length === 0) { 105 + return; 106 + } 107 + 108 + const plcOp = await agent.com.atproto.identity.signPlcOperation({ 109 + token: options.plcToken, 110 + ...operation, 111 + }); 112 + 113 + await agent.com.atproto.identity.submitPlcOperation({ operation: plcOp.data.operation }); 114 + 115 + if (!options.privateKey && operation.verificationMethods) { 116 + const privateKey = ui8.toString(await keypair.export(), "hex"); 117 + console.log( 118 + "This is your labeler's signing key. It will be needed to sign any labels you create.", 119 + "You will not be able to retrieve this key again, so make sure to save it somewhere safe.", 120 + "If you lose this key, you can call this function again without passing a private key to generate a new one.", 121 + ); 122 + console.log("Signing key:", privateKey); 123 + } 124 + } 125 + 126 + /** 127 + * Request a PLC token, needed for {@link plcSetupLabeler}. The token will be sent to the email 128 + * associated with the labeler account. 129 + * @param agent An agent logged into the labeler account. 130 + */ 131 + export async function plcRequestToken(agent: AtpAgent): Promise<void>; 132 + /** 133 + * Request a PLC token, needed for {@link plcSetupLabeler}. The token will be sent to the email 134 + * associated with the labeler account. 135 + * @param credentials The credentials of the labeler account. 136 + */ 137 + export async function plcRequestToken( 138 + credentials: { pds?: string; identifier: string; password: string }, 139 + ): Promise<void>; 140 + export async function plcRequestToken( 141 + agentOrCredentials: AtpAgent | { pds?: string; identifier: string; password: string }, 142 + ) { 143 + const agent = agentOrCredentials instanceof AtpAgent 144 + ? agentOrCredentials 145 + : new AtpAgent({ service: agentOrCredentials.pds || "https://bsky.social" }); 146 + if (!agent.hasSession) { 147 + if (!(agentOrCredentials instanceof AtpAgent)) { 148 + await agent.login(agentOrCredentials); 149 + } else { 150 + throw new Error("A password must be provided to log in to the labeler account."); 151 + } 152 + } 153 + await agent.com.atproto.identity.requestPlcOperationSignature(); 154 + }