Enhance content management and user interaction features
- Introduced a new content source toggle in the profile and app header to switch between IndeeHub and TopDoc films. - Updated the content fetching logic to dynamically load content based on the selected source. - Enhanced the seeding process to include a combined catalog of IndeeHub and TopDoc films, ensuring diverse content availability. - Improved user interaction by preventing duplicate reactions and ensuring a smoother voting experience across comments and content. - Added support for Amber login (NIP-55) for Android users, integrating it into the existing authentication flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,7 +21,7 @@ const RELAY_URL = process.env.RELAY_URL || 'ws://localhost:7777'
|
||||
const ORIGIN = process.env.ORIGIN || 'http://localhost:5174'
|
||||
|
||||
// ── Content catalog (matching src/data/indeeHubFilms.ts) ──────────
|
||||
const CONTENT = [
|
||||
const INDEEHUB_CONTENT = [
|
||||
{ id: 'god-bless-bitcoin', title: 'God Bless Bitcoin' },
|
||||
{ id: 'thethingswecarry', title: 'The Things We Carry' },
|
||||
{ id: 'duel', title: 'Duel' },
|
||||
@@ -46,6 +46,36 @@ const CONTENT = [
|
||||
{ id: '34f042bd-23d6-40f4-9707-4b3bb62fdd58', title: 'Little Billy' },
|
||||
]
|
||||
|
||||
// ── TopDocumentaryFilms catalog (matching src/data/topDocFilms.ts) ──
|
||||
const TOPDOC_CONTENT = [
|
||||
{ id: 'tdf-god-bless-bitcoin', title: 'God Bless Bitcoin' },
|
||||
{ id: 'tdf-bitcoin-end-of-money', title: 'Bitcoin: The End of Money as We Know It' },
|
||||
{ id: 'tdf-bitcoin-beyond-bubble', title: 'Bitcoin: Beyond the Bubble' },
|
||||
{ id: 'tdf-bitcoin-gospel', title: 'The Bitcoin Gospel' },
|
||||
{ id: 'tdf-bitcoin-psyop', title: 'The Bitcoin Psyop' },
|
||||
{ id: 'tdf-missing-cryptoqueen', title: 'The Missing Cryptoqueen' },
|
||||
{ id: 'tdf-billion-dollar-scam', title: 'The Billion Dollar Scam' },
|
||||
{ id: 'tdf-money-banking-fed', title: 'Money, Banking, and The Federal Reserve' },
|
||||
{ id: 'tdf-american-dream', title: 'The American Dream' },
|
||||
{ id: 'tdf-century-enslavement', title: 'Century of Enslavement' },
|
||||
{ id: 'tdf-money-power-wall-street', title: 'Money, Power and Wall Street' },
|
||||
{ id: 'tdf-gold-6000-year', title: "Gold: Man's 6000 Year Obsession" },
|
||||
{ id: 'tdf-debtasized', title: 'Debtasized' },
|
||||
{ id: 'tdf-crash-next-crisis', title: 'Crash: Are We Ready?' },
|
||||
{ id: 'tdf-pension-gamble', title: 'The Pension Gamble' },
|
||||
{ id: 'tdf-why-americans-poor', title: 'Why Americans Feel So Poor?' },
|
||||
{ id: 'tdf-chain-reaction', title: 'Chain Reaction' },
|
||||
{ id: 'tdf-usa-on-brink', title: 'USA on the Brink' },
|
||||
{ id: 'tdf-big-four', title: 'The Big Four' },
|
||||
{ id: 'tdf-congo-millionaires', title: 'Congo: Millionaires of Chaos' },
|
||||
{ id: 'tdf-economics-of', title: 'The Economics Of' },
|
||||
{ id: 'tdf-big-business-food', title: 'Big Business: Food Empires' },
|
||||
{ id: 'tdf-so-long-superstores', title: 'So Long, Superstores?' },
|
||||
]
|
||||
|
||||
// Combined catalog — seeder covers both sources
|
||||
const CONTENT = [...INDEEHUB_CONTENT, ...TOPDOC_CONTENT]
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────
|
||||
type Persona = { name: string; nsec: string; pubkey: string }
|
||||
|
||||
@@ -74,10 +104,23 @@ 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)
|
||||
// Draw from BOTH catalogs to ensure TopDoc films get activity
|
||||
const topContent = [
|
||||
...INDEEHUB_CONTENT.slice(0, 5),
|
||||
...TOPDOC_CONTENT.slice(0, 5),
|
||||
]
|
||||
const midContent = [
|
||||
...INDEEHUB_CONTENT.slice(5, 11),
|
||||
...TOPDOC_CONTENT.slice(5, 11),
|
||||
]
|
||||
const trendingContent = [
|
||||
...pick(INDEEHUB_CONTENT.slice(0, 8), 3),
|
||||
...pick(TOPDOC_CONTENT.slice(0, 8), 3),
|
||||
]
|
||||
const tastemakerFaves = [
|
||||
...pick(INDEEHUB_CONTENT.slice(0, 8), 3),
|
||||
...pick(TOPDOC_CONTENT.slice(0, 8), 3),
|
||||
]
|
||||
|
||||
// ── sample comments ─────────────────────────────────────────────
|
||||
const POSITIVE_COMMENTS = [
|
||||
@@ -93,6 +136,12 @@ const POSITIVE_COMMENTS = [
|
||||
'Rewatched it last night — still holds up beautifully.',
|
||||
'Such an important documentary. Everyone should see this.',
|
||||
'The storytelling here is on another level.',
|
||||
'Required viewing. This opened my eyes to things I never considered.',
|
||||
'The depth of research in this is incredible. Really well done.',
|
||||
'Finally a documentary that treats the subject seriously.',
|
||||
'Shared this with my whole family. Everyone needs to see this.',
|
||||
'This is the kind of content that changes how you see the world.',
|
||||
'Brilliant piece of journalism. Respect to the filmmakers.',
|
||||
]
|
||||
|
||||
const MIXED_COMMENTS = [
|
||||
@@ -104,12 +153,44 @@ const MIXED_COMMENTS = [
|
||||
'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.',
|
||||
'Interesting topic but the production quality could be better.',
|
||||
'They barely scratched the surface on this topic. Wanted more depth.',
|
||||
'Decent intro to the topic but experts won\'t learn anything new.',
|
||||
]
|
||||
|
||||
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.',
|
||||
'Felt more like a sales pitch than a documentary.',
|
||||
]
|
||||
|
||||
// Documentary-specific comments for TopDoc films
|
||||
const DOC_POSITIVE_COMMENTS = [
|
||||
'One of the best Bitcoin documentaries out there. Really explains the fundamentals.',
|
||||
'Everyone who thinks they understand money should watch this.',
|
||||
'This completely changed how I think about the financial system.',
|
||||
'Incredible deep dive into a topic most people don\'t understand.',
|
||||
'The interviews in this are absolutely fascinating.',
|
||||
'Been orange-pilled for years but this doc still taught me new things.',
|
||||
'Sent this to my dad and now he finally gets it.',
|
||||
'The production quality for a documentary like this is outstanding.',
|
||||
'This should be required viewing in economics classes.',
|
||||
'The parallels they draw to historical events are eye-opening.',
|
||||
'Watched this with my skeptical friends. They were impressed.',
|
||||
'Finally a balanced take on cryptocurrency. Well researched.',
|
||||
'This doc captures the movement perfectly. A time capsule for future generations.',
|
||||
'The personal stories in this really humanize what can feel like a dry topic.',
|
||||
'Phenomenal. I\'ve recommended this to at least 20 people.',
|
||||
]
|
||||
|
||||
const DOC_MIXED_COMMENTS = [
|
||||
'Good overview but a bit surface-level for people already in the space.',
|
||||
'Some parts felt dated already given how fast things move in crypto.',
|
||||
'Wish they had interviewed more diverse perspectives.',
|
||||
'Interesting but could have been 30 minutes shorter.',
|
||||
'The first half is excellent, second half loses steam.',
|
||||
'Fair attempt but misses some key nuances about the technology.',
|
||||
]
|
||||
|
||||
// ── publishing helper ───────────────────────────────────────────
|
||||
@@ -213,9 +294,55 @@ async function seedReactions(relay: Relay) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ensure EVERY TopDoc film has at least some reactions ───────
|
||||
const alreadyReacted = new Set([
|
||||
...topContent.map(c => c.id),
|
||||
...midContent.map(c => c.id),
|
||||
...trendingContent.map(c => c.id),
|
||||
...tastemakerFaves.map(c => c.id),
|
||||
])
|
||||
const unreactedTopDoc = TOPDOC_CONTENT.filter(c => !alreadyReacted.has(c.id))
|
||||
|
||||
for (const item of unreactedTopDoc) {
|
||||
const voters = pick(allPersonas, randomInt(3, 7))
|
||||
for (const persona of voters) {
|
||||
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
||||
const emoji = Math.random() < 0.75 ? '+' : '-'
|
||||
const age = randomInt(1 * ONE_DAY, 35 * ONE_DAY)
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
kind: 17,
|
||||
content: emoji,
|
||||
tags: [
|
||||
['i', contentUrl(item.id)],
|
||||
['k', 'web'],
|
||||
],
|
||||
created_at: now - age,
|
||||
}, `catch-all-reaction ${persona.name}->${item.title}`)
|
||||
if (ok) count++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✓ ${count} reactions seeded`)
|
||||
}
|
||||
|
||||
// ── comment picker (uses doc-specific comments for TopDoc films) ─
|
||||
function pickComment(itemId: string, sentiment: 'positive' | 'mixed' | 'negative'): string {
|
||||
const isTopDoc = itemId.startsWith('tdf-')
|
||||
if (sentiment === 'positive') {
|
||||
const pool = isTopDoc
|
||||
? [...DOC_POSITIVE_COMMENTS, ...POSITIVE_COMMENTS]
|
||||
: POSITIVE_COMMENTS
|
||||
return pool[randomInt(0, pool.length - 1)]
|
||||
}
|
||||
if (sentiment === 'mixed') {
|
||||
const pool = isTopDoc
|
||||
? [...DOC_MIXED_COMMENTS, ...MIXED_COMMENTS]
|
||||
: MIXED_COMMENTS
|
||||
return pool[randomInt(0, pool.length - 1)]
|
||||
}
|
||||
return NEGATIVE_COMMENTS[randomInt(0, NEGATIVE_COMMENTS.length - 1)]
|
||||
}
|
||||
|
||||
// ── seed comments (kind 1111) ───────────────────────────────────
|
||||
async function seedComments(relay: Relay) {
|
||||
console.log('\n💬 Seeding comments (kind 1111)...')
|
||||
@@ -224,13 +351,12 @@ async function seedComments(relay: Relay) {
|
||||
// Top content: several comments
|
||||
for (const item of topContent) {
|
||||
const url = contentUrl(item.id)
|
||||
const commenters = pick(allPersonas, randomInt(2, 5))
|
||||
const commenters = pick(allPersonas, randomInt(3, 6))
|
||||
|
||||
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 sentiment = Math.random() < 0.7 ? 'positive' : 'mixed'
|
||||
const content = pickComment(item.id, sentiment)
|
||||
const age = randomInt(1 * ONE_DAY, 30 * ONE_DAY)
|
||||
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
@@ -249,14 +375,15 @@ async function seedComments(relay: Relay) {
|
||||
}
|
||||
|
||||
// Mid content: occasional comments
|
||||
for (const item of pick(midContent, 4)) {
|
||||
for (const item of pick(midContent, 8)) {
|
||||
const url = contentUrl(item.id)
|
||||
const commenters = pick(allPersonas, randomInt(1, 2))
|
||||
const commenters = pick(allPersonas, randomInt(1, 3))
|
||||
|
||||
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 r = Math.random()
|
||||
const sentiment = r < 0.4 ? 'positive' : r < 0.8 ? 'mixed' : 'negative'
|
||||
const content = pickComment(item.id, sentiment)
|
||||
const age = randomInt(3 * ONE_DAY, 45 * ONE_DAY)
|
||||
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
@@ -275,13 +402,13 @@ async function seedComments(relay: Relay) {
|
||||
}
|
||||
|
||||
// Tastemaker reviews on their faves
|
||||
for (const item of tastemakerFaves.slice(0, 4)) {
|
||||
for (const item of tastemakerFaves) {
|
||||
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 content = pickComment(item.id, 'positive')
|
||||
const age = randomInt(0, 10 * ONE_DAY)
|
||||
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
@@ -300,13 +427,13 @@ async function seedComments(relay: Relay) {
|
||||
}
|
||||
|
||||
// Trending content: recent comments
|
||||
for (const item of trendingContent.slice(0, 3)) {
|
||||
for (const item of trendingContent) {
|
||||
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 content = pickComment(item.id, 'positive')
|
||||
const age = randomInt(0, 3 * ONE_DAY)
|
||||
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
@@ -324,6 +451,42 @@ async function seedComments(relay: Relay) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ensure EVERY TopDoc film has at least some comments ────────
|
||||
// This catches any TopDoc films not already in top/mid/trending subsets
|
||||
const alreadyCommented = new Set([
|
||||
...topContent.map(c => c.id),
|
||||
...midContent.map(c => c.id),
|
||||
...trendingContent.map(c => c.id),
|
||||
...tastemakerFaves.map(c => c.id),
|
||||
])
|
||||
const uncommentedTopDoc = TOPDOC_CONTENT.filter(c => !alreadyCommented.has(c.id))
|
||||
|
||||
for (const item of uncommentedTopDoc) {
|
||||
const url = contentUrl(item.id)
|
||||
const commenters = pick(allPersonas, randomInt(2, 4))
|
||||
|
||||
for (const persona of commenters) {
|
||||
const signer = PrivateKeySigner.fromKey(persona.nsec)
|
||||
const r = Math.random()
|
||||
const sentiment = r < 0.6 ? 'positive' : r < 0.9 ? 'mixed' : 'negative'
|
||||
const content = pickComment(item.id, sentiment)
|
||||
const age = randomInt(2 * ONE_DAY, 40 * ONE_DAY)
|
||||
|
||||
const ok = await publishEvent(relay, signer, {
|
||||
kind: 1111,
|
||||
content,
|
||||
tags: [
|
||||
['I', url],
|
||||
['K', 'web'],
|
||||
['i', url],
|
||||
['k', 'web'],
|
||||
],
|
||||
created_at: now - age,
|
||||
}, `catch-all-comment ${persona.name}->${item.title}`)
|
||||
if (ok) count++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✓ ${count} comments seeded`)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user