import pino from "pino"; import WebSocket from "ws"; import { lambdaRequestTracker, pinoLambdaDestination } from "pino-lambda"; import { MicroBlogBackend } from "./pocketbase"; // custom destination formatter const destination = pinoLambdaDestination(); const logger = pino({}, destination); const withRequest = lambdaRequestTracker(); const pb = new MicroBlogBackend(logger); const fetcherNpub = process.env.NOSTR_FETCHER_NPUB!; const myNpub = process.env.NOSTR_ID!; type NostrTag = ["t" | "r" | "imeta", string]; type NostrEvent = [ "EVENT" | "EOSE", string, { id: string; pubkey: string; created_at: number; kind: number; // 1 tags: NostrTag[]; content: string; sig: string; }, ]; exports.run = async (event: any, context: any) => { withRequest(event, context); const events: NostrEvent[] = []; // figure out when the last post of wasved const lastSavedPost = await pb.getLatestPostBySource("nostr"); let since: number | undefined; if (lastSavedPost) { since = new Date(lastSavedPost.posted).getTime() / 1000; } // listen for new events for 30 seconds logger.info("trying to connecting to nostr relay"); const ws = new WebSocket(process.env.NOSTR_RELAY!); // Other Relay URLs // "wss://nos.lol", // "wss://nostr.wine", // "wss://nostr.einundzwanzig.space", ws.on("error", logger.error); ws.on("message", function message(data: Buffer) { const decodedData = JSON.parse( Buffer.from(data).toString("utf8") ) as NostrEvent; logger.info({ decodedData }, "recived a message from nostr relay"); if (decodedData[0] === "EVENT") { events.push(decodedData); } }); ws.on("open", function open() { logger.info("connection established"); ws.send( JSON.stringify([ "REQ", fetcherNpub, { kinds: [1], authors: [myNpub], ...(since ? { since } : {}) }, ]) ); }); await new Promise((resolve) => setTimeout(resolve, 30000)); logger.info("closing connection to nostr relay"); ws.close(); logger.info("saving nostr posts"); for (const event of events) { const post = await pb.savePost({ remoteId: event[2].id, fullPost: event[2], posted: new Date(event[2].created_at * 1000).toISOString(), source: "nostr", authorId: event[1], }); for (const tag of event[2].tags) { if (tag[0] === "t") { await pb.setTag(tag[1], post.id); } else if (tag[0] === "imeta") { const value = tag[1]; // remove "url " from the start of the string const url = value.slice(4); await pb.saveAndSetImage( { remoteURL: url, }, post.id ); } } } };