clone of repo on github
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

266 lines
8.1 KiB

import { finalizeEvent, generateSecretKey, getPublicKey } from "nostr-tools";
import WebSocket from "ws";
// Test user keys (generate fresh ones)
const testUserKey = generateSecretKey();
const testUserPubkey = getPublicKey(testUserKey);
const testUser2Key = generateSecretKey();
const testUser2Pubkey = getPublicKey(testUser2Key);
console.log("Test User 1 pubkey:", testUserPubkey);
console.log("Test User 2 pubkey:", testUser2Pubkey);
// The publication details from the article (REAL VALUES)
const publicationPubkey =
"dc4cd086cd7ce5b1832adf4fdd1211289880d2c7e295bcb0e684c01acee77c06";
const rootAddress =
`30040:${publicationPubkey}:anarchistic-knowledge-the-art-of-thinking-without-permission`;
// Section addresses (from the actual publication structure)
const sections = [
`30041:${publicationPubkey}:the-art-of-thinking-without-permission`,
`30041:${publicationPubkey}:the-natural-promiscuity-of-understanding`,
`30041:${publicationPubkey}:institutional-capture-and-knowledge-enclosure`,
`30041:${publicationPubkey}:the-persistent-escape-of-knowledge`,
];
// Relays to publish to (matching CommentLayer's relay list)
const relays = [
"wss://relay.damus.io",
"wss://relay.nostr.band",
"wss://nostr.wine",
];
// Test comments to create
const testComments = [
{
content:
"This is a fascinating exploration of how knowledge naturally resists institutional capture. The analogy to flowing water is particularly apt.",
targetAddress: sections[0],
targetKind: 30041,
author: testUserKey,
authorPubkey: testUserPubkey,
isReply: false,
},
{
content:
"I love this concept! It reminds me of how open source projects naturally organize without top-down control.",
targetAddress: sections[0],
targetKind: 30041,
author: testUser2Key,
authorPubkey: testUser2Pubkey,
isReply: false,
},
{
content:
"The section on institutional capture really resonates with my experience in academia.",
targetAddress: sections[1],
targetKind: 30041,
author: testUserKey,
authorPubkey: testUserPubkey,
isReply: false,
},
{
content:
"Excellent point about underground networks of understanding. This is exactly how most practical knowledge develops.",
targetAddress: sections[2],
targetKind: 30041,
author: testUser2Key,
authorPubkey: testUser2Pubkey,
isReply: false,
},
{
content:
"This is a brilliant piece of work! Really captures the tension between institutional knowledge and living understanding.",
targetAddress: rootAddress,
targetKind: 30040,
author: testUserKey,
authorPubkey: testUserPubkey,
isReply: false,
},
];
async function publishEvent(event, relayUrl) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(relayUrl);
let published = false;
ws.on("open", () => {
console.log(`Connected to ${relayUrl}`);
ws.send(JSON.stringify(["EVENT", event]));
});
ws.on("message", (data) => {
const message = JSON.parse(data.toString());
if (message[0] === "OK" && message[1] === event.id) {
if (message[2]) {
console.log(
`✓ Published event ${event.id.substring(0, 8)} to ${relayUrl}`,
);
published = true;
ws.close();
resolve();
} else {
console.error(`✗ Relay rejected event: ${message[3]}`);
ws.close();
reject(new Error(message[3]));
}
}
});
ws.on("error", (error) => {
console.error(`WebSocket error: ${error.message}`);
reject(error);
});
ws.on("close", () => {
if (!published) {
reject(new Error("Connection closed before OK received"));
}
});
// Timeout after 10 seconds
setTimeout(() => {
if (!published) {
ws.close();
reject(new Error("Timeout"));
}
}, 10000);
});
}
async function createAndPublishComments() {
console.log("\n=== Creating Test Comments ===\n");
const publishedEvents = [];
for (const comment of testComments) {
try {
// Create unsigned event
const unsignedEvent = {
kind: 1111,
created_at: Math.floor(Date.now() / 1000),
tags: [
// Root scope - uppercase tags
["A", comment.targetAddress, relays[0], publicationPubkey],
["K", comment.targetKind.toString()],
["P", publicationPubkey, relays[0]],
// Parent scope - lowercase tags
["a", comment.targetAddress, relays[0]],
["k", comment.targetKind.toString()],
["p", publicationPubkey, relays[0]],
],
content: comment.content,
pubkey: comment.authorPubkey,
};
// If this is a reply, add reply tags
if (comment.isReply && comment.replyToId) {
unsignedEvent.tags.push(["e", comment.replyToId, relay, "reply"]);
unsignedEvent.tags.push(["p", comment.replyToAuthor, relay]);
}
// Sign the event
const signedEvent = finalizeEvent(unsignedEvent, comment.author);
console.log(
`\nCreating comment on ${
comment.targetKind === 30040 ? "collection" : "section"
}:`,
);
console.log(` Content: "${comment.content.substring(0, 60)}..."`);
console.log(` Target: ${comment.targetAddress}`);
console.log(` Event ID: ${signedEvent.id}`);
// Publish to relay
await publishEvent(signedEvent, relays[0]);
publishedEvents.push(signedEvent);
// Store event ID for potential replies
comment.eventId = signedEvent.id;
// Delay between publishes to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 1500));
} catch (error) {
console.error(`Failed to publish comment: ${error.message}`);
}
}
// Now create some threaded replies
console.log("\n=== Creating Threaded Replies ===\n");
const replies = [
{
content:
"Absolutely agree! The metaphor extends even further when you consider how ideas naturally branch and merge.",
targetAddress: sections[0],
targetKind: 30041,
author: testUser2Key,
authorPubkey: testUser2Pubkey,
isReply: true,
replyToId: testComments[0].eventId,
replyToAuthor: testComments[0].authorPubkey,
},
{
content:
"Great connection! The parallel between open source governance and knowledge commons is really illuminating.",
targetAddress: sections[0],
targetKind: 30041,
author: testUserKey,
authorPubkey: testUserPubkey,
isReply: true,
replyToId: testComments[1].eventId,
replyToAuthor: testComments[1].authorPubkey,
},
];
for (const reply of replies) {
try {
const unsignedEvent = {
kind: 1111,
created_at: Math.floor(Date.now() / 1000),
tags: [
// Root scope
["A", reply.targetAddress, relays[0], publicationPubkey],
["K", reply.targetKind.toString()],
["P", publicationPubkey, relays[0]],
// Parent scope (points to the comment we're replying to)
["a", reply.targetAddress, relays[0]],
["k", reply.targetKind.toString()],
["p", reply.replyToAuthor, relays[0]],
// Reply markers
["e", reply.replyToId, relays[0], "reply"],
],
content: reply.content,
pubkey: reply.authorPubkey,
};
const signedEvent = finalizeEvent(unsignedEvent, reply.author);
console.log(`\nCreating reply:`);
console.log(` Content: "${reply.content.substring(0, 60)}..."`);
console.log(` Reply to: ${reply.replyToId.substring(0, 8)}`);
console.log(` Event ID: ${signedEvent.id}`);
await publishEvent(signedEvent, relays[0]);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Longer delay to avoid rate limiting
} catch (error) {
console.error(`Failed to publish reply: ${error.message}`);
}
}
console.log("\n=== Done! ===");
console.log(
`\nPublished ${
publishedEvents.length + replies.length
} total comments/replies`,
);
console.log("\nRefresh the page to see the comments in the Comment Panel.");
}
// Run it
createAndPublishComments().catch(console.error);