Pronouns labels on Bluesky

switch to jetstream

+28 -222
package-lock.json
···
"name": "pronouns-bsky",
"version": "1.0.0",
"dependencies": {
-
"@atproto/api": "^0.13.0",
-
"@skyware/firehose": "^0.3.1",
"@skyware/labeler": "^0.1.2-fix.1",
-
"dotenv": "^16.4.5"
+
"dotenv": "^16.4.5",
+
"ws": "^8.18.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.11",
"@types/node": "^20.14.10",
+
"@types/ws": "^8.5.12",
"typescript": "^5.5.3"
}
},
···
}
},
"node_modules/@atproto/common": {
-
"version": "0.3.4",
-
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.3.4.tgz",
-
"integrity": "sha512-qjOHcyeHjGRJseYbdnl+0PViDlMRN9GR0/1ToGKAmXh0voz4LbBWU1FwtGF7CvTlbom9MZYhu21lF5pD5RqBwQ==",
+
"version": "0.4.1",
+
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.4.1.tgz",
+
"integrity": "sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==",
"dependencies": {
-
"@atproto/common-web": "^0.2.4",
+
"@atproto/common-web": "^0.3.0",
"@ipld/dag-cbor": "^7.0.3",
"cbor-x": "^1.5.1",
"iso-datestring-validator": "^2.2.2",
"multiformats": "^9.9.0",
-
"pino": "^8.15.0",
-
"zod": "3.21.4"
+
"pino": "^8.21.0"
}
},
"node_modules/@atproto/common-web": {
···
"zod": "^3.21.4"
}
},
-
"node_modules/@atproto/common/node_modules/@atproto/common-web": {
-
"version": "0.2.4",
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.2.4.tgz",
-
"integrity": "sha512-6+DOhQcTklFmeiSkZRx6iFeqi4OFtGl4yEDGATk00q4tEcPoPvyOBtYHN6+G9lrfJIfx5RfmggamvXlJv1PxxA==",
-
"dependencies": {
-
"graphemer": "^1.4.0",
-
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/common/node_modules/zod": {
-
"version": "3.21.4",
-
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
-
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
-
"funding": {
-
"url": "https://github.com/sponsors/colinhacks"
-
}
-
},
"node_modules/@atproto/crypto": {
-
"version": "0.3.0",
-
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.3.0.tgz",
-
"integrity": "sha512-bhcxRTL4fgRY2YX/St0x4o0oDUp18QIPD7ek+7v8UKA0HpsCGQYbo8w9d9hUvwwty5X5p00cYF2tbggUWaPy7A==",
+
"version": "0.4.1",
+
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.1.tgz",
+
"integrity": "sha512-7pQNHWYyx8jGhYdPbmcuPD9W73nd/5v3mfBlncO0sBzxnPbmA6aXAWOz+fNVZwHwBJPeb/Gzf/FT/uDx7/eYFg==",
"dependencies": {
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"uint8arrays": "3.0.0"
}
},
-
"node_modules/@atproto/identity": {
-
"version": "0.3.3",
-
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.3.3.tgz",
-
"integrity": "sha512-i5d3eOnXQAXuOrjdm4NcveIBJxdEywYJQ4d74/JOgy27bGdNVFgh9FdZAsOseQRkLv4r7mr0I0C6OlSA2BQIxA==",
-
"dependencies": {
-
"@atproto/common-web": "^0.2.4",
-
"@atproto/crypto": "^0.3.0",
-
"axios": "^0.27.2"
-
}
-
},
-
"node_modules/@atproto/identity/node_modules/@atproto/common-web": {
-
"version": "0.2.4",
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.2.4.tgz",
-
"integrity": "sha512-6+DOhQcTklFmeiSkZRx6iFeqi4OFtGl4yEDGATk00q4tEcPoPvyOBtYHN6+G9lrfJIfx5RfmggamvXlJv1PxxA==",
-
"dependencies": {
-
"graphemer": "^1.4.0",
-
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
-
}
-
},
"node_modules/@atproto/lexicon": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.1.tgz",
···
"zod": "^3.23.8"
}
},
-
"node_modules/@atproto/repo": {
-
"version": "0.3.9",
-
"resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.3.9.tgz",
-
"integrity": "sha512-1TZvvIy0XQihdXPd9JALh04sGbOlr82zk9TxtxVmLX2jic6g5Pl48aB4R1TjvoRiVMAsQVh3VLRVbUN4o66eVg==",
-
"dependencies": {
-
"@atproto/common": "^0.3.4",
-
"@atproto/common-web": "^0.2.4",
-
"@atproto/crypto": "^0.3.0",
-
"@atproto/identity": "^0.3.3",
-
"@atproto/lexicon": "^0.3.3",
-
"@atproto/syntax": "^0.2.1",
-
"@ipld/car": "^3.2.3",
-
"@ipld/dag-cbor": "^7.0.0",
-
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/repo/node_modules/@atproto/common-web": {
-
"version": "0.2.4",
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.2.4.tgz",
-
"integrity": "sha512-6+DOhQcTklFmeiSkZRx6iFeqi4OFtGl4yEDGATk00q4tEcPoPvyOBtYHN6+G9lrfJIfx5RfmggamvXlJv1PxxA==",
-
"dependencies": {
-
"graphemer": "^1.4.0",
-
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/repo/node_modules/@atproto/lexicon": {
-
"version": "0.3.3",
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.3.3.tgz",
-
"integrity": "sha512-6FOjdc3V05JKrtkhjfhHMS7f/4hMJOeHNtoE3Na7iFMpzBz0Lw5sw8kIFKY8pc8IG79qGcFgELyHLsljZYX+5A==",
-
"dependencies": {
-
"@atproto/common-web": "^0.2.4",
-
"@atproto/syntax": "^0.2.1",
-
"iso-datestring-validator": "^2.2.2",
-
"multiformats": "^9.9.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/repo/node_modules/@atproto/syntax": {
-
"version": "0.2.1",
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.2.1.tgz",
-
"integrity": "sha512-ImOuiICtB5h78j90hAYOfTYzr5q5Wut0irNdELiogA3i74a8EXThe+j6Tj8snanYggrShbu5c6BDc1tVj477Yw==",
-
"dependencies": {
-
"@atproto/common-web": "^0.2.4"
-
}
-
},
"node_modules/@atproto/syntax": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz",
···
}
},
"node_modules/@atproto/xrpc-server": {
-
"version": "0.4.4",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.4.4.tgz",
-
"integrity": "sha512-R4uYvuia+tUM4wATLlKmMhr/+VEaSImhuAhD7jEHIjN3a+ZxDBE5PnW3PwWf68VBqw720JJ+jmsJMZO7v9y6FA==",
+
"version": "0.6.3",
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.6.3.tgz",
+
"integrity": "sha512-0YXeBM9NjiIlR5eXWo8qzArRcBOKhwVimpH+ajKgZzlncPO53brVZ9+3BUnD5J1PG8mEQFRERi+Jt77QyF89qA==",
"dependencies": {
-
"@atproto/common": "^0.3.4",
-
"@atproto/crypto": "^0.3.0",
-
"@atproto/lexicon": "^0.3.3",
+
"@atproto/common": "^0.4.1",
+
"@atproto/crypto": "^0.4.1",
+
"@atproto/lexicon": "^0.4.1",
+
"@atproto/xrpc": "^0.6.1",
"cbor-x": "^1.5.1",
"express": "^4.17.2",
"http-errors": "^2.0.0",
···
"rate-limiter-flexible": "^2.4.1",
"uint8arrays": "3.0.0",
"ws": "^8.12.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/xrpc-server/node_modules/@atproto/common-web": {
-
"version": "0.2.4",
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.2.4.tgz",
-
"integrity": "sha512-6+DOhQcTklFmeiSkZRx6iFeqi4OFtGl4yEDGATk00q4tEcPoPvyOBtYHN6+G9lrfJIfx5RfmggamvXlJv1PxxA==",
-
"dependencies": {
-
"graphemer": "^1.4.0",
-
"multiformats": "^9.9.0",
-
"uint8arrays": "3.0.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/xrpc-server/node_modules/@atproto/lexicon": {
-
"version": "0.3.3",
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.3.3.tgz",
-
"integrity": "sha512-6FOjdc3V05JKrtkhjfhHMS7f/4hMJOeHNtoE3Na7iFMpzBz0Lw5sw8kIFKY8pc8IG79qGcFgELyHLsljZYX+5A==",
-
"dependencies": {
-
"@atproto/common-web": "^0.2.4",
-
"@atproto/syntax": "^0.2.1",
-
"iso-datestring-validator": "^2.2.2",
-
"multiformats": "^9.9.0",
-
"zod": "^3.21.4"
-
}
-
},
-
"node_modules/@atproto/xrpc-server/node_modules/@atproto/syntax": {
-
"version": "0.2.1",
-
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.2.1.tgz",
-
"integrity": "sha512-ImOuiICtB5h78j90hAYOfTYzr5q5Wut0irNdELiogA3i74a8EXThe+j6Tj8snanYggrShbu5c6BDc1tVj477Yw==",
-
"dependencies": {
-
"@atproto/common-web": "^0.2.4"
+
"zod": "^3.23.8"
}
},
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
···
"ws": "^8.0.0"
}
},
-
"node_modules/@ipld/car": {
-
"version": "3.2.4",
-
"resolved": "https://registry.npmjs.org/@ipld/car/-/car-3.2.4.tgz",
-
"integrity": "sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw==",
-
"dependencies": {
-
"@ipld/dag-cbor": "^7.0.0",
-
"multiformats": "^9.5.4",
-
"varint": "^6.0.0"
-
}
-
},
"node_modules/@ipld/dag-cbor": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz",
···
"url": "https://paulmillr.com/funding/"
}
},
-
"node_modules/@skyware/firehose": {
-
"version": "0.3.1",
-
"resolved": "https://registry.npmjs.org/@skyware/firehose/-/firehose-0.3.1.tgz",
-
"integrity": "sha512-krL44TRi3/4Q35AihnZ19WxcoubULets3cxVVVpQJKlOrm6Ups407zjgIh0Y+4mqf0g3HchYD1DHaUdcwc1+Qw==",
-
"dependencies": {
-
"@atproto/repo": "^0.3.7",
-
"@atproto/xrpc-server": "^0.4.2",
-
"ws": "^8.16.0"
-
}
-
},
"node_modules/@skyware/labeler": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@skyware/labeler/-/labeler-0.1.6.tgz",
···
"labeler": "dist/bin.js"
}
},
-
"node_modules/@skyware/labeler/node_modules/@atproto/common": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.4.1.tgz",
-
"integrity": "sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q==",
-
"dependencies": {
-
"@atproto/common-web": "^0.3.0",
-
"@ipld/dag-cbor": "^7.0.3",
-
"cbor-x": "^1.5.1",
-
"iso-datestring-validator": "^2.2.2",
-
"multiformats": "^9.9.0",
-
"pino": "^8.21.0"
-
}
-
},
-
"node_modules/@skyware/labeler/node_modules/@atproto/crypto": {
-
"version": "0.4.1",
-
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.1.tgz",
-
"integrity": "sha512-7pQNHWYyx8jGhYdPbmcuPD9W73nd/5v3mfBlncO0sBzxnPbmA6aXAWOz+fNVZwHwBJPeb/Gzf/FT/uDx7/eYFg==",
-
"dependencies": {
-
"@noble/curves": "^1.1.0",
-
"@noble/hashes": "^1.3.1",
-
"uint8arrays": "3.0.0"
-
}
-
},
-
"node_modules/@skyware/labeler/node_modules/@atproto/crypto/node_modules/uint8arrays": {
-
"version": "3.0.0",
-
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
-
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
-
"dependencies": {
-
"multiformats": "^9.4.2"
-
}
-
},
"node_modules/@skyware/labeler/node_modules/@atproto/identity": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@atproto/identity/-/identity-0.4.1.tgz",
···
"axios": "^0.27.2"
}
},
-
"node_modules/@skyware/labeler/node_modules/@atproto/xrpc-server": {
-
"version": "0.6.3",
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc-server/-/xrpc-server-0.6.3.tgz",
-
"integrity": "sha512-0YXeBM9NjiIlR5eXWo8qzArRcBOKhwVimpH+ajKgZzlncPO53brVZ9+3BUnD5J1PG8mEQFRERi+Jt77QyF89qA==",
-
"dependencies": {
-
"@atproto/common": "^0.4.1",
-
"@atproto/crypto": "^0.4.1",
-
"@atproto/lexicon": "^0.4.1",
-
"@atproto/xrpc": "^0.6.1",
-
"cbor-x": "^1.5.1",
-
"express": "^4.17.2",
-
"http-errors": "^2.0.0",
-
"mime-types": "^2.1.35",
-
"rate-limiter-flexible": "^2.4.1",
-
"uint8arrays": "3.0.0",
-
"ws": "^8.12.0",
-
"zod": "^3.23.8"
-
}
-
},
-
"node_modules/@skyware/labeler/node_modules/@atproto/xrpc-server/node_modules/uint8arrays": {
-
"version": "3.0.0",
-
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
-
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
-
"dependencies": {
-
"multiformats": "^9.4.2"
-
}
-
},
"node_modules/@skyware/labeler/node_modules/uint8arrays": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz",
···
"dev": true,
"dependencies": {
"undici-types": "~6.19.2"
+
}
+
},
+
"node_modules/@types/ws": {
+
"version": "8.5.12",
+
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
+
"integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
+
"dev": true,
+
"dependencies": {
+
"@types/node": "*"
}
},
"node_modules/abort-controller": {
···
"engines": {
"node": ">= 0.4.0"
-
},
-
"node_modules/varint": {
-
"version": "6.0.0",
-
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
-
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="
},
"node_modules/vary": {
"version": "1.1.2",
+3 -3
package.json
···
"devDependencies": {
"@types/better-sqlite3": "^7.6.11",
"@types/node": "^20.14.10",
+
"@types/ws": "^8.5.12",
"typescript": "^5.5.3"
},
"dependencies": {
-
"@atproto/api": "^0.13.0",
-
"@skyware/firehose": "^0.3.1",
"@skyware/labeler": "^0.1.2-fix.1",
-
"dotenv": "^16.4.5"
+
"dotenv": "^16.4.5",
+
"ws": "^8.18.0"
}
}
+1
src/constants.ts
···
export const DID = process.env.DID ?? "";
export const SIGNING_KEY = process.env.SIGNING_KEY ?? "";
+
export const RELAY = process.env.RELAY ?? "ws://localhost:6008/subscribe";
export const PORT = 4001;
export const LABEL_LIMIT = 4;
export const DELETE = "3kwsqucto3j2a";
+3 -11
src/label.ts
···
-
import { AppBskyActorDefs, ComAtprotoLabelDefs } from "@atproto/api";
import {
DID,
PORT,
···
SIGNING_KEY,
DELETE,
} from "./constants.js";
+
import { Label } from "./types.js";
import { LabelerServer } from "@skyware/labeler";
const server = new LabelerServer({ did: DID, signingKey: SIGNING_KEY });
···
}
});
-
export const label = async (
-
subject: string | AppBskyActorDefs.ProfileView,
-
rkey: string,
-
) => {
-
const did = AppBskyActorDefs.isProfileView(subject) ? subject.did : subject;
-
+
export const label = async (did: string, rkey: string) => {
const query = server.db
-
.prepare<
-
unknown[],
-
ComAtprotoLabelDefs.Label
-
>(`SELECT * FROM labels WHERE uri = ?`)
+
.prepare<unknown[], Label>(`SELECT * FROM labels WHERE uri = ?`)
.all(did);
const labels = query.reduce((set, label) => {
+21 -28
src/main.ts
···
-
import { AppBskyFeedLike } from "@atproto/api";
-
import { Firehose } from "@skyware/firehose";
import { label } from "./label.js";
-
import { DID } from "./constants.js";
+
import { DID, RELAY } from "./constants.js";
+
import { EventStream } from "./types.js";
import fs from "node:fs";
+
import WebSocket from "ws";
const subscribe = async () => {
-
let cursorFirehose = 0;
+
let cursor = 0;
let intervalID: NodeJS.Timeout;
const cursorFile = fs.readFileSync("cursor.txt", "utf8");
-
const firehose = new Firehose({ cursor: cursorFile ?? "" });
-
if (cursorFile) console.log(`Initiate firehose at cursor ${cursorFile}`);
+
const relay = cursorFile ? RELAY.concat("&cursor=", cursorFile) : RELAY;
+
const ws = new WebSocket(relay);
-
firehose.on("error", ({ cursor, error }) => {
-
console.log(`Firehose errored on cursor: ${cursor}`, error);
-
});
+
ws.on("error", console.error);
-
firehose.on("open", () => {
+
ws.on("open", () => {
intervalID = setInterval(() => {
const timestamp = new Date().toISOString();
-
console.log(`${timestamp} cursor: ${cursorFirehose}`);
-
fs.writeFile("cursor.txt", cursorFirehose.toString(), (err) => {
+
console.log(`${timestamp} cursor: ${cursor}`);
+
fs.writeFile("cursor.txt", cursor.toString(), (err) => {
if (err) console.error(err);
});
}, 60000);
});
-
firehose.on("close", () => {
+
ws.on("close", function close() {
clearInterval(intervalID);
});
-
firehose.on("commit", (commit) => {
-
cursorFirehose = commit.seq;
-
commit.ops.forEach(async (op) => {
-
if (op.action !== "delete" && AppBskyFeedLike.isRecord(op.record)) {
-
if (op.record.subject.uri.includes(DID)) {
-
if (op.record.subject.uri.includes("app.bsky.feed.post")) {
-
await label(
-
commit.repo,
-
op.record.subject.uri.split("/").pop()!,
-
).catch((err) => console.error(err));
-
}
-
}
+
ws.on("message", async (data) => {
+
const event: EventStream = JSON.parse(data.toString());
+
cursor = event.time_us;
+
if (event.type.includes("com")) {
+
if (event.commit.record?.subject.uri.includes(DID)) {
+
await label(
+
event.did,
+
event.commit.record.subject.uri.split("/").pop()!,
+
);
}
-
});
+
}
});
-
-
firehose.start();
};
subscribe();
+38
src/types.ts
···
+
export interface EventStream {
+
did: string;
+
time_us: number;
+
type: string;
+
commit: Commit;
+
}
+
+
export interface Commit {
+
rev: string;
+
type: string;
+
collection: string;
+
rkey: string;
+
record: Record;
+
}
+
+
export interface Record {
+
$type: string;
+
createdAt: string;
+
subject: Subject;
+
}
+
+
export interface Subject {
+
cid: string;
+
uri: string;
+
}
+
+
export interface Label {
+
ver?: number;
+
src: string;
+
uri: string;
+
cid?: string;
+
val: string;
+
neg?: boolean;
+
cts: string;
+
exp?: string;
+
sig?: Uint8Array;
+
[k: string]: unknown;
+
}