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.
 
 
 
 

249 lines
8.0 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);