- Added several new dependencies related to the Applesauce library, including 'applesauce-accounts', 'applesauce-common', 'applesauce-core', 'applesauce-loaders', 'applesauce-relay', and 'applesauce-signers', all at version 5.1.0. - Updated the development script in package.json to specify a port for Vite and added new seed scripts for profiles and activity. - Removed outdated image files from the public directory to clean up unused assets. - Enhanced the App.vue structure by integrating shared components like AppHeader and AuthModal for improved user experience. - Refactored ContentDetailModal and MobileNav components to support new features and improve usability. These changes improve the overall functionality and maintainability of the application while ensuring it utilizes the latest libraries for better performance.
341 lines
12 KiB
TypeScript
341 lines
12 KiB
TypeScript
/**
|
|
* Seeds the local relay with reactions (kind 17) and comments (kind 1111)
|
|
* for all IndeeHub content, so the UI has real data to display.
|
|
*
|
|
* Run after seed-profiles.ts and with the relay already running.
|
|
*/
|
|
import { Relay } from 'applesauce-relay'
|
|
import { PrivateKeySigner } from 'applesauce-signers/signers/private-key-signer'
|
|
import {
|
|
TEST_PERSONAS,
|
|
TASTEMAKER_PERSONAS,
|
|
} from '../src/data/testPersonas.js'
|
|
|
|
const RELAY_URL = 'ws://localhost:7777'
|
|
const ORIGIN = 'http://localhost:5174'
|
|
|
|
// ── Content catalog (matching src/data/indeeHubFilms.ts) ──────────
|
|
const CONTENT = [
|
|
{ id: 'god-bless-bitcoin', title: 'God Bless Bitcoin' },
|
|
{ id: 'thethingswecarry', title: 'The Things We Carry' },
|
|
{ id: 'duel', title: 'Duel' },
|
|
{ id: '2b0d7349-c010-47a0-b584-49e1bf86ab2f', title: 'Hard Money' },
|
|
{ id: '665a4095-73b9-480d-a0a4-b2aafaf2bce4', title: 'Bitcoiners' },
|
|
{ id: '3c113b66-3bb5-4cac-90eb-965ecedc4aa2', title: 'Lekker Feeling' },
|
|
{ id: 'stranded', title: 'STRANDED' },
|
|
{ id: 'bbdb0178-0b96-4ab5-addf-ba1f029c1cb3', title: 'The Housing Bubble' },
|
|
{ id: '584f310b-2269-4b05-a09d-261a0a3c1f78', title: 'Menger' },
|
|
{ id: 'ef92cd99-7188-4c48-b4bf-0b31fdd8934e', title: 'Everybody Does It' },
|
|
{ id: 'e1bd64d6-63c9-4c91-8d91-c69f5376286e', title: 'Gods of Their Own Religion' },
|
|
{ id: 'forgingacountry', title: 'Forging a Country' },
|
|
{ id: 'home', title: 'HOME' },
|
|
{ id: 'e1f58162-9288-418e-803d-196dcde00782', title: 'Kismet' },
|
|
{ id: 'identity-theft', title: 'Identity Theft' },
|
|
{ id: 'comingto', title: 'Coming To' },
|
|
{ id: 'down-the-pch', title: 'Down the P.C.H.' },
|
|
{ id: '0cb9de15-566d-4130-b80c-d42e952bb803', title: 'Breaking Up Is Hard to Do' },
|
|
{ id: '24b6f7c6-8f56-40f2-831a-54f40b03c427', title: 'The Florist' },
|
|
{ id: '311f772f-6559-4982-8918-d0f4be9e1b76', title: 'Plastic Money' },
|
|
{ id: '5bd753b7-9ff1-4966-a1c4-b3b93c62ed5d', title: 'Time Traveling Thieves' },
|
|
{ id: '34f042bd-23d6-40f4-9707-4b3bb62fdd58', title: 'Little Billy' },
|
|
]
|
|
|
|
// ── helpers ──────────────────────────────────────────────────────
|
|
type Persona = { name: string; nsec: string; pubkey: string }
|
|
|
|
function contentUrl(contentId: string): string {
|
|
return `${ORIGIN}/content/${contentId}`
|
|
}
|
|
|
|
function pick<T>(arr: T[], n: number): T[] {
|
|
const shuffled = [...arr].sort(() => Math.random() - 0.5)
|
|
return shuffled.slice(0, n)
|
|
}
|
|
|
|
function randomInt(min: number, max: number): number {
|
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
}
|
|
|
|
// ── personas ────────────────────────────────────────────────────
|
|
const allPersonas: Persona[] = [
|
|
...(TEST_PERSONAS as unknown as Persona[]),
|
|
...(TASTEMAKER_PERSONAS as unknown as Persona[]),
|
|
]
|
|
const tastemakers: Persona[] = TASTEMAKER_PERSONAS as unknown as Persona[]
|
|
|
|
const now = Math.floor(Date.now() / 1000)
|
|
const ONE_DAY = 86400
|
|
const ONE_WEEK = 7 * ONE_DAY
|
|
|
|
// Content subsets for different activity patterns
|
|
const topContent = CONTENT.slice(0, 8)
|
|
const midContent = CONTENT.slice(8, 16)
|
|
const trendingContent = pick(CONTENT.slice(0, 12), 5)
|
|
const tastemakerFaves = pick(CONTENT.slice(0, 10), 6)
|
|
|
|
// ── sample comments ─────────────────────────────────────────────
|
|
const POSITIVE_COMMENTS = [
|
|
'Absolutely incredible film. A masterpiece in every sense.',
|
|
'This movie changed my perspective on cinema. Must watch!',
|
|
'The cinematography alone is worth the price of admission.',
|
|
'One of the greatest performances I\'ve ever seen on screen.',
|
|
'Every frame is a painting. Stunning work.',
|
|
'I\'ve seen this at least 5 times and it gets better every watch.',
|
|
'The screenplay is tight, the pacing is perfect.',
|
|
'A landmark achievement in filmmaking.',
|
|
'This deserves every award it got and more.',
|
|
'Rewatched it last night — still holds up beautifully.',
|
|
'Such an important documentary. Everyone should see this.',
|
|
'The storytelling here is on another level.',
|
|
]
|
|
|
|
const MIXED_COMMENTS = [
|
|
'Good but I think it\'s a bit overrated honestly.',
|
|
'Solid film, though the third act drags a little.',
|
|
'Worth watching once for sure, but I wouldn\'t rewatch.',
|
|
'Technically impressive but emotionally I felt nothing.',
|
|
'The hype is a bit much, but it\'s still a decent movie.',
|
|
'Some great moments, but also some really slow stretches.',
|
|
'I can see why people love it, just not my cup of tea.',
|
|
'Better than I expected, worse than the reviews suggest.',
|
|
]
|
|
|
|
const NEGATIVE_COMMENTS = [
|
|
'I really don\'t understand the hype around this one.',
|
|
'Couldn\'t finish it. Way too slow for my taste.',
|
|
'Overrated. There are much better films in this genre.',
|
|
]
|
|
|
|
// ── publishing helper ───────────────────────────────────────────
|
|
async function publishEvent(
|
|
relay: Relay,
|
|
signer: PrivateKeySigner,
|
|
event: { kind: number; content: string; tags: string[][]; created_at: number },
|
|
label: string,
|
|
): Promise<boolean> {
|
|
const signed = await signer.signEvent(event)
|
|
try {
|
|
const res = await relay.publish(signed, { timeout: 5000 })
|
|
if (!res.ok) console.warn(` ⚠ ${label}: ${res.message}`)
|
|
return true
|
|
} catch (err) {
|
|
console.error(` ✗ ${label}:`, err instanceof Error ? err.message : err)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ── seed reactions (kind 17) ────────────────────────────────────
|
|
async function seedReactions(relay: Relay) {
|
|
console.log('\n📊 Seeding reactions (kind 17)...')
|
|
let count = 0
|
|
|
|
// Top content: lots of positive reactions
|
|
for (const item of topContent) {
|
|
const voters = pick(allPersonas, randomInt(5, 10))
|
|
for (const persona of voters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const emoji = Math.random() < 0.9 ? '+' : '-'
|
|
const age = randomInt(1 * ONE_DAY, 30 * ONE_DAY)
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 17,
|
|
content: emoji,
|
|
tags: [
|
|
['i', contentUrl(item.id)],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `reaction ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Mid content: moderate reactions, more mixed
|
|
for (const item of pick(midContent, 5)) {
|
|
const voters = pick(allPersonas, randomInt(2, 5))
|
|
for (const persona of voters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const emoji = Math.random() < 0.6 ? '+' : '-'
|
|
const age = randomInt(2 * ONE_DAY, 60 * ONE_DAY)
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 17,
|
|
content: emoji,
|
|
tags: [
|
|
['i', contentUrl(item.id)],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `reaction ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Trending content: recent reactions
|
|
for (const item of trendingContent) {
|
|
const voters = pick(allPersonas, randomInt(4, 8))
|
|
for (const persona of voters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const age = randomInt(0, ONE_WEEK)
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 17,
|
|
content: '+',
|
|
tags: [
|
|
['i', contentUrl(item.id)],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `trending-reaction ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Tastemaker-specific reactions
|
|
for (const item of tastemakerFaves) {
|
|
const voters = pick(tastemakers, randomInt(2, 5))
|
|
for (const persona of voters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const age = randomInt(0, 14 * ONE_DAY)
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 17,
|
|
content: '+',
|
|
tags: [
|
|
['i', contentUrl(item.id)],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `tastemaker-reaction ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
console.log(` ✓ ${count} reactions seeded`)
|
|
}
|
|
|
|
// ── seed comments (kind 1111) ───────────────────────────────────
|
|
async function seedComments(relay: Relay) {
|
|
console.log('\n💬 Seeding comments (kind 1111)...')
|
|
let count = 0
|
|
|
|
// Top content: several comments
|
|
for (const item of topContent) {
|
|
const url = contentUrl(item.id)
|
|
const commenters = pick(allPersonas, randomInt(2, 5))
|
|
|
|
for (const persona of commenters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const comments =
|
|
Math.random() < 0.7 ? POSITIVE_COMMENTS : MIXED_COMMENTS
|
|
const content = comments[randomInt(0, comments.length - 1)]
|
|
const age = randomInt(1 * ONE_DAY, 30 * ONE_DAY)
|
|
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 1111,
|
|
content,
|
|
tags: [
|
|
['I', url],
|
|
['K', 'web'],
|
|
['i', url],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `comment ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Mid content: occasional comments
|
|
for (const item of pick(midContent, 4)) {
|
|
const url = contentUrl(item.id)
|
|
const commenters = pick(allPersonas, randomInt(1, 2))
|
|
|
|
for (const persona of commenters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const pool = [...MIXED_COMMENTS, ...NEGATIVE_COMMENTS]
|
|
const content = pool[randomInt(0, pool.length - 1)]
|
|
const age = randomInt(3 * ONE_DAY, 45 * ONE_DAY)
|
|
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 1111,
|
|
content,
|
|
tags: [
|
|
['I', url],
|
|
['K', 'web'],
|
|
['i', url],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `comment ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Tastemaker reviews on their faves
|
|
for (const item of tastemakerFaves.slice(0, 4)) {
|
|
const url = contentUrl(item.id)
|
|
const reviewers = pick(tastemakers, randomInt(1, 3))
|
|
|
|
for (const persona of reviewers) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const content = POSITIVE_COMMENTS[randomInt(0, POSITIVE_COMMENTS.length - 1)]
|
|
const age = randomInt(0, 10 * ONE_DAY)
|
|
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 1111,
|
|
content,
|
|
tags: [
|
|
['I', url],
|
|
['K', 'web'],
|
|
['i', url],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `tastemaker-comment ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
// Trending content: recent comments
|
|
for (const item of trendingContent.slice(0, 3)) {
|
|
const url = contentUrl(item.id)
|
|
const commenters = pick(allPersonas, randomInt(2, 4))
|
|
|
|
for (const persona of commenters) {
|
|
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
|
const content = POSITIVE_COMMENTS[randomInt(0, POSITIVE_COMMENTS.length - 1)]
|
|
const age = randomInt(0, 3 * ONE_DAY)
|
|
|
|
const ok = await publishEvent(relay, signer, {
|
|
kind: 1111,
|
|
content,
|
|
tags: [
|
|
['I', url],
|
|
['K', 'web'],
|
|
['i', url],
|
|
['k', 'web'],
|
|
],
|
|
created_at: now - age,
|
|
}, `trending-comment ${persona.name}->${item.title}`)
|
|
if (ok) count++
|
|
}
|
|
}
|
|
|
|
console.log(` ✓ ${count} comments seeded`)
|
|
}
|
|
|
|
// ── main ────────────────────────────────────────────────────────
|
|
async function main() {
|
|
console.log('🎬 Seeding activity data into relay at', RELAY_URL)
|
|
|
|
const relay = new Relay(RELAY_URL)
|
|
|
|
await seedReactions(relay)
|
|
await seedComments(relay)
|
|
|
|
console.log('\n✅ Done! Activity seeded successfully.')
|
|
setTimeout(() => process.exit(0), 1000)
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Fatal:', err)
|
|
process.exit(1)
|
|
})
|