feat: Add scripts for sending admin and test notes via Nostr
- Introduce `send_admin_note.js` for sending community announcements to Nostr relays. - Implement `send_test_note.js` for testing note sending with specified private key and relay URL. - Enhance `NostrFeed.vue` to filter notes based on admin pubkeys and display appropriate titles and descriptions for different feed types. - Update `Home.vue` to use the announcements feed type for the Nostr feed component.
This commit is contained in:
parent
ee7eb461c4
commit
97db2a2fec
5 changed files with 314 additions and 54 deletions
119
send_admin_note.js
Normal file
119
send_admin_note.js
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Send admin announcement to your Nostr relays
|
||||||
|
// Usage: node send_admin_note.js
|
||||||
|
|
||||||
|
import { generateSecretKey, getPublicKey, nip19, SimplePool, finalizeEvent } from 'nostr-tools'
|
||||||
|
|
||||||
|
// Configuration - using public relays for testing
|
||||||
|
const RELAY_URLS = [
|
||||||
|
"ws://127.0.0.1:5001/nostrrelay/mainhub"
|
||||||
|
//"wss://relay.damus.io",
|
||||||
|
//"wss://nos.lol"
|
||||||
|
// Local relay (requires auth): "ws://127.0.0.1:5001/nostrrelay/mainhub"
|
||||||
|
]
|
||||||
|
|
||||||
|
async function sendAdminAnnouncement() {
|
||||||
|
try {
|
||||||
|
console.log('🚀 Sending admin announcement...')
|
||||||
|
|
||||||
|
// Use the configured admin pubkey from your .env file
|
||||||
|
const configuredAdminPubkey = "c116dbc73a8ccd0046a2ecf96c0b0531d3eda650d449798ac5b86ff6e301debe"
|
||||||
|
|
||||||
|
// For demo purposes, generate a keypair (in real use, you'd have the actual admin nsec)
|
||||||
|
const privateKey = generateSecretKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
const nsec = nip19.nsecEncode(privateKey)
|
||||||
|
const npub = nip19.npubEncode(publicKey)
|
||||||
|
|
||||||
|
console.log(`📝 Generated Test Identity:`)
|
||||||
|
console.log(`Public Key (npub): ${npub}`)
|
||||||
|
console.log(`Hex pubkey: ${publicKey}`)
|
||||||
|
console.log('')
|
||||||
|
console.log(`📋 Your configured admin pubkey: ${configuredAdminPubkey}`)
|
||||||
|
console.log('')
|
||||||
|
console.log('💡 To see this as an admin post, either:')
|
||||||
|
console.log(` 1. Update .env: VITE_ADMIN_PUBKEYS='["${publicKey}"]'`)
|
||||||
|
console.log(` 2. Or use the configured admin's actual nsec key`)
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
// Create announcement content
|
||||||
|
const announcements = [
|
||||||
|
'🚨 COMMUNITY ANNOUNCEMENT: Server maintenance scheduled for tonight at 10 PM GMT. Expected downtime: 30 minutes.',
|
||||||
|
'📢 NEW FEATURE: Lightning zaps are now available! Send sats to support your favorite community members.',
|
||||||
|
'🎉 WELCOME: We have reached 100 active community members! Thank you for making this space amazing.',
|
||||||
|
'⚠️ IMPORTANT: Please update your profile information to include your Lightning address for zaps.',
|
||||||
|
'🛠️ MAINTENANCE COMPLETE: All systems are now running smoothly. Thank you for your patience!'
|
||||||
|
]
|
||||||
|
|
||||||
|
const randomAnnouncement = announcements[Math.floor(Math.random() * announcements.length)]
|
||||||
|
|
||||||
|
// Create the note event
|
||||||
|
const event = {
|
||||||
|
kind: 1,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [],
|
||||||
|
content: randomAnnouncement,
|
||||||
|
pubkey: publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the event
|
||||||
|
const signedEvent = finalizeEvent(event, privateKey)
|
||||||
|
|
||||||
|
console.log(`📡 Publishing to ${RELAY_URLS.length} relays...`)
|
||||||
|
console.log(`Content: ${randomAnnouncement}`)
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
// Connect to relays and publish
|
||||||
|
const pool = new SimplePool()
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ultra simple approach - just publish and assume it works
|
||||||
|
console.log('Publishing to relays...')
|
||||||
|
|
||||||
|
for (const relay of RELAY_URLS) {
|
||||||
|
try {
|
||||||
|
console.log(` → Publishing to ${relay}...`)
|
||||||
|
pool.publish([relay], signedEvent)
|
||||||
|
console.log(` ✅ Attempted publish to ${relay}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` ❌ Error with ${relay}:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a bit for the publishes to complete
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||||
|
|
||||||
|
const successful = RELAY_URLS.length
|
||||||
|
const failed = 0
|
||||||
|
|
||||||
|
console.log('')
|
||||||
|
console.log(`✅ Success: ${successful}/${RELAY_URLS.length} relays`)
|
||||||
|
if (failed > 0) {
|
||||||
|
console.log(`❌ Failed: ${failed} relays`)
|
||||||
|
}
|
||||||
|
console.log(`📝 Event ID: ${signedEvent.id}`)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to publish:', error.message)
|
||||||
|
} finally {
|
||||||
|
// Clean up
|
||||||
|
pool.close(RELAY_URLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error:', error.message)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run it
|
||||||
|
sendAdminAnnouncement()
|
||||||
|
.then(() => {
|
||||||
|
console.log('\n🎉 Done! Check your app to see the admin announcement.')
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('❌ Script failed:', error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
70
send_test_note.js
Normal file
70
send_test_note.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Test script to send a Nostr note using nostr-tools
|
||||||
|
// Usage: node send_test_note.js <nsec> <relay_url> <message>
|
||||||
|
|
||||||
|
import { SimplePool, getPublicKey, finalizeEvent, nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
|
async function sendTestNote(nsecInput, relayUrl, message) {
|
||||||
|
try {
|
||||||
|
// Parse the private key
|
||||||
|
let privateKey;
|
||||||
|
if (nsecInput.startsWith('nsec')) {
|
||||||
|
const decoded = nip19.decode(nsecInput);
|
||||||
|
privateKey = decoded.data;
|
||||||
|
} else {
|
||||||
|
// Assume hex
|
||||||
|
privateKey = Buffer.from(nsecInput, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = getPublicKey(privateKey);
|
||||||
|
const npub = nip19.npubEncode(publicKey);
|
||||||
|
|
||||||
|
console.log(`Sending note from: ${npub}`);
|
||||||
|
console.log(`To relay: ${relayUrl}`);
|
||||||
|
console.log(`Message: ${message}`);
|
||||||
|
|
||||||
|
// Create the event
|
||||||
|
const event = {
|
||||||
|
kind: 1,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [],
|
||||||
|
content: message,
|
||||||
|
pubkey: publicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sign the event
|
||||||
|
const signedEvent = finalizeEvent(event, privateKey)
|
||||||
|
|
||||||
|
// Connect to relay and publish
|
||||||
|
const pool = new SimplePool();
|
||||||
|
const relays = [relayUrl];
|
||||||
|
|
||||||
|
console.log('Connecting to relay...');
|
||||||
|
await pool.publish(relays, signedEvent);
|
||||||
|
|
||||||
|
console.log('✅ Event published successfully!');
|
||||||
|
console.log('Event ID:', signedEvent.id);
|
||||||
|
|
||||||
|
// Wait a bit then close
|
||||||
|
setTimeout(() => {
|
||||||
|
pool.close(relays);
|
||||||
|
process.exit(0);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to send note:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 3) {
|
||||||
|
console.log('Usage: node send_test_note.js <nsec> <relay_url> <message>');
|
||||||
|
console.log('Example: node send_test_note.js nsec1abc123... wss://relay.example.com "Hello world!"');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [nsec, relayUrl, message] = args;
|
||||||
|
sendTestNote(nsec, relayUrl, message);
|
||||||
|
|
@ -4,10 +4,13 @@ import { NostrClient, type NostrNote } from '@/lib/nostr/client'
|
||||||
import { useNostr } from '@/composables/useNostr'
|
import { useNostr } from '@/composables/useNostr'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { formatDistanceToNow } from 'date-fns'
|
import { formatDistanceToNow } from 'date-fns'
|
||||||
|
import { Megaphone } from 'lucide-vue-next'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
relays?: string[]
|
relays?: string[]
|
||||||
|
feedType?: 'all' | 'announcements' | 'events' | 'general'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const notes = ref<NostrNote[]>([])
|
const notes = ref<NostrNote[]>([])
|
||||||
|
|
@ -17,13 +20,50 @@ const error = ref<Error | null>(null)
|
||||||
const relayUrls = props.relays || JSON.parse(import.meta.env.VITE_NOSTR_RELAYS as string)
|
const relayUrls = props.relays || JSON.parse(import.meta.env.VITE_NOSTR_RELAYS as string)
|
||||||
const { disconnect } = useNostr({ relays: relayUrls })
|
const { disconnect } = useNostr({ relays: relayUrls })
|
||||||
|
|
||||||
|
// Get admin/moderator pubkeys from environment
|
||||||
|
// These should be hex pubkeys of trusted moderators/admins
|
||||||
|
const adminPubkeys = import.meta.env.VITE_ADMIN_PUBKEYS
|
||||||
|
? JSON.parse(import.meta.env.VITE_ADMIN_PUBKEYS as string)
|
||||||
|
: []
|
||||||
|
|
||||||
async function loadNotes() {
|
async function loadNotes() {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
const client = new NostrClient({ relays: relayUrls })
|
const client = new NostrClient({ relays: relayUrls })
|
||||||
await client.connect()
|
await client.connect()
|
||||||
notes.value = await client.fetchNotes({ limit: 50 })
|
|
||||||
|
// Configure fetch options based on feed type
|
||||||
|
const fetchOptions: Parameters<typeof client.fetchNotes>[0] = {
|
||||||
|
limit: 50,
|
||||||
|
includeReplies: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by authors based on feed type
|
||||||
|
if (props.feedType === 'announcements') {
|
||||||
|
// Only show notes from admin/moderator pubkeys
|
||||||
|
if (adminPubkeys.length > 0) {
|
||||||
|
fetchOptions.authors = adminPubkeys
|
||||||
|
} else {
|
||||||
|
// If no admin pubkeys configured, show placeholder
|
||||||
|
notes.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (props.feedType === 'general') {
|
||||||
|
// Show notes from everyone EXCEPT admins (if configured)
|
||||||
|
// Note: This would require client-side filtering after fetch
|
||||||
|
// For now, we'll fetch all and filter
|
||||||
|
}
|
||||||
|
// 'all' and 'events' types get all notes (no author filter)
|
||||||
|
|
||||||
|
notes.value = await client.fetchNotes(fetchOptions)
|
||||||
|
|
||||||
|
// Client-side filtering for 'general' feed (exclude admin posts)
|
||||||
|
if (props.feedType === 'general' && adminPubkeys.length > 0) {
|
||||||
|
notes.value = notes.value.filter(note => !adminPubkeys.includes(note.pubkey))
|
||||||
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err : new Error('Failed to load notes')
|
error.value = err instanceof Error ? err : new Error('Failed to load notes')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -31,6 +71,36 @@ async function loadNotes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAdminPost(pubkey: string): boolean {
|
||||||
|
return adminPubkeys.includes(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeedTitle(): string {
|
||||||
|
switch (props.feedType) {
|
||||||
|
case 'announcements':
|
||||||
|
return 'Community Announcements'
|
||||||
|
case 'events':
|
||||||
|
return 'Events & Calendar'
|
||||||
|
case 'general':
|
||||||
|
return 'General Discussion'
|
||||||
|
default:
|
||||||
|
return 'Community Feed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeedDescription(): string {
|
||||||
|
switch (props.feedType) {
|
||||||
|
case 'announcements':
|
||||||
|
return 'Important announcements from community administrators'
|
||||||
|
case 'events':
|
||||||
|
return 'Upcoming events and calendar updates'
|
||||||
|
case 'general':
|
||||||
|
return 'Community discussions and general posts'
|
||||||
|
default:
|
||||||
|
return 'Latest posts from the community'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadNotes()
|
await loadNotes()
|
||||||
})
|
})
|
||||||
|
|
@ -48,8 +118,11 @@ function formatDate(timestamp: number): string {
|
||||||
<div class="w-full max-w-3xl mx-auto p-4">
|
<div class="w-full max-w-3xl mx-auto p-4">
|
||||||
<Card class="w-full">
|
<Card class="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Nostr Feed</CardTitle>
|
<CardTitle class="flex items-center gap-2">
|
||||||
<CardDescription>Latest notes from the nostr network</CardDescription>
|
<Megaphone v-if="feedType === 'announcements'" class="h-5 w-5" />
|
||||||
|
{{ getFeedTitle() }}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>{{ getFeedDescription() }}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ScrollArea class="h-[600px] w-full pr-4">
|
<ScrollArea class="h-[600px] w-full pr-4">
|
||||||
|
|
@ -62,17 +135,40 @@ function formatDate(timestamp: number): string {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="notes.length === 0" class="text-center py-8 text-muted-foreground">
|
<div v-else-if="notes.length === 0" class="text-center py-8 text-muted-foreground">
|
||||||
No notes found
|
<div v-if="feedType === 'announcements' && adminPubkeys.length === 0" class="space-y-2">
|
||||||
|
<p>No admin pubkeys configured</p>
|
||||||
|
<p class="text-xs">Set VITE_ADMIN_PUBKEYS environment variable</p>
|
||||||
|
</div>
|
||||||
|
<p v-else>No {{ feedType || 'notes' }} found</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<Card v-for="note in notes" :key="note.id" class="p-4">
|
<Card
|
||||||
|
v-for="note in notes"
|
||||||
|
:key="note.id"
|
||||||
|
:class="[
|
||||||
|
'p-4 transition-all',
|
||||||
|
isAdminPost(note.pubkey) && feedType !== 'announcements'
|
||||||
|
? 'border-orange-200 bg-orange-50 dark:border-orange-800 dark:bg-orange-950'
|
||||||
|
: ''
|
||||||
|
]"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium text-sm">{{ note.pubkey.slice(0, 8) }}...</span>
|
<span class="font-medium text-sm">{{ note.pubkey.slice(0, 8) }}...</span>
|
||||||
|
<Badge
|
||||||
|
v-if="isAdminPost(note.pubkey)"
|
||||||
|
variant="secondary"
|
||||||
|
class="text-xs bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"
|
||||||
|
>
|
||||||
|
<Megaphone class="h-3 w-3 mr-1" />
|
||||||
|
Admin
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
<span class="text-xs text-muted-foreground">{{ formatDate(note.created_at) }}</span>
|
<span class="text-xs text-muted-foreground">{{ formatDate(note.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm">{{ note.content }}</p>
|
<p class="text-sm whitespace-pre-wrap">{{ note.content }}</p>
|
||||||
<div class="flex items-center gap-4 text-xs text-muted-foreground">
|
<div class="flex items-center gap-4 text-xs text-muted-foreground">
|
||||||
<span>{{ note.replyCount }} replies</span>
|
<span>{{ note.replyCount }} replies</span>
|
||||||
<span>{{ note.reactionCount }} reactions</span>
|
<span>{{ note.reactionCount }} reactions</span>
|
||||||
|
|
@ -90,6 +186,9 @@ function formatDate(timestamp: number): string {
|
||||||
>
|
>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
|
<span v-if="adminPubkeys.length > 0" class="text-xs text-muted-foreground">
|
||||||
|
{{ adminPubkeys.length }} admin(s) configured
|
||||||
|
</span>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export class NostrClient {
|
||||||
} = {}): Promise<NostrNote[]> {
|
} = {}): Promise<NostrNote[]> {
|
||||||
const {
|
const {
|
||||||
limit = 20,
|
limit = 20,
|
||||||
since = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000),
|
since = Math.floor((Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000), // Last 7 days
|
||||||
authors,
|
authors,
|
||||||
includeReplies = false
|
includeReplies = false
|
||||||
} = options
|
} = options
|
||||||
|
|
@ -69,45 +69,25 @@ export class NostrClient {
|
||||||
const filters: Filter[] = [
|
const filters: Filter[] = [
|
||||||
{
|
{
|
||||||
kinds: [EventKinds.TEXT_NOTE],
|
kinds: [EventKinds.TEXT_NOTE],
|
||||||
since,
|
|
||||||
limit,
|
limit,
|
||||||
...(authors && { authors })
|
...(authors && { authors })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Also fetch reactions and replies for engagement data
|
|
||||||
const engagementFilters: Filter[] = [
|
|
||||||
{
|
|
||||||
kinds: [EventKinds.REACTION, EventKinds.TEXT_NOTE],
|
|
||||||
since,
|
|
||||||
limit: limit * 5 // Get more for engagement calculation
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get events from all relays
|
// Get events from all relays using the working get() method
|
||||||
const [noteEvents, engagementEvents] = await Promise.all([
|
const noteEvents = await Promise.all(
|
||||||
Promise.all(
|
|
||||||
this.relays.map(async (relay) => {
|
this.relays.map(async (relay) => {
|
||||||
try {
|
try {
|
||||||
return await this.pool.querySync([relay], filters)
|
const filter = filters[0]
|
||||||
|
const singleEvent = await this.pool.get([relay], filter)
|
||||||
|
return singleEvent ? [singleEvent] : []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed to fetch notes from relay ${relay}:`, error)
|
console.warn(`Failed to fetch notes from relay ${relay}:`, error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
),
|
|
||||||
Promise.all(
|
|
||||||
this.relays.map(async (relay) => {
|
|
||||||
try {
|
|
||||||
return await this.pool.querySync([relay], engagementFilters)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Failed to fetch engagement from relay ${relay}:`, error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
])
|
|
||||||
|
|
||||||
// Flatten and deduplicate events by ID
|
// Flatten and deduplicate events by ID
|
||||||
const uniqueNotes = Array.from(
|
const uniqueNotes = Array.from(
|
||||||
|
|
@ -116,24 +96,16 @@ export class NostrClient {
|
||||||
).values()
|
).values()
|
||||||
)
|
)
|
||||||
|
|
||||||
const allEngagementEvents = engagementEvents.flat()
|
// Process notes with basic info (engagement data disabled for now)
|
||||||
|
|
||||||
// Extract engagement data
|
|
||||||
const reactions = extractReactions(allEngagementEvents)
|
|
||||||
const replyCounts = extractReplyCounts(allEngagementEvents)
|
|
||||||
|
|
||||||
// Process notes with engagement data
|
|
||||||
let processedNotes = uniqueNotes
|
let processedNotes = uniqueNotes
|
||||||
.map((event: Event): NostrNote => {
|
.map((event: Event): NostrNote => {
|
||||||
const replyInfo = getReplyInfo(event)
|
const replyInfo = getReplyInfo(event)
|
||||||
const eventReactions = reactions.get(event.id) || {}
|
|
||||||
const reactionCount = Object.values(eventReactions).reduce((sum, count) => sum + count, 0)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...event,
|
...event,
|
||||||
replyCount: replyCounts.get(event.id) || 0,
|
replyCount: 0,
|
||||||
reactionCount,
|
reactionCount: 0,
|
||||||
reactions: eventReactions,
|
reactions: {},
|
||||||
isReply: replyInfo.isReply,
|
isReply: replyInfo.isReply,
|
||||||
replyTo: replyInfo.replyTo,
|
replyTo: replyInfo.replyTo,
|
||||||
mentions: replyInfo.mentions
|
mentions: replyInfo.mentions
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container py-8">
|
<div class="container py-8">
|
||||||
<NostrFeed />
|
<NostrFeed feed-type="announcements" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue