Skip to content

Commit 9bb516f

Browse files
authored
Merge branch 'stackernews:master' into issue_787
2 parents 8250c2f + e269a8e commit 9bb516f

File tree

3 files changed

+120
-8
lines changed

3 files changed

+120
-8
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,5 @@ docker/lnbits/data
6464
!docker/lndk/tls-*.pem
6565

6666
# nostr link extract
67-
scripts/nostr-link-extract.config.json
67+
scripts/nostr-link-extract.config.json
68+
scripts/nostr-links.db

awards.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,6 @@ ed-kung,pr,#1926,#1927,easy,,,,100k,[email protected],???
184184
ed-kung,issue,#1926,#1927,easy,,,,10k,[email protected],???
185185
ed-kung,issue,#1913,#1890,good-first-issue,,,,2k,[email protected],???
186186
Scroogey-SN,pr,#1930,#1167,good-first-issue,,,,20k,[email protected],???
187-
itsrealfake,issue,#1930,#1167,good-first-issue,,,,2k,???,???
187+
itsrealfake,issue,#1930,#1167,good-first-issue,,,,2k,[email protected],???
188188
Scroogey-SN,pr,#1948,#1849,medium,urgent,,,750k,[email protected],???
189189
felipebueno,issue,#1947,#1945,good-first-issue,,,,2k,[email protected],???

scripts/nostr-link-extract.js

100644100755
Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require('child_process')
4+
module.paths.push(execSync('npm config get prefix').toString().trim() + '/lib/node_modules')
15
const WebSocket = require('ws') // You might need to install this: npm install ws
26
const { nip19 } = require('nostr-tools') // Keep this for formatting
37
const fs = require('fs')
48
const path = require('path')
9+
const sqlite3 = require('sqlite3').verbose() // Add this at the top with other requires
510

611
// ANSI color codes
712
const colors = {
@@ -39,6 +44,91 @@ const colors = {
3944
}
4045
}
4146

47+
// Add these new database utility functions after the color definitions but before the config
48+
const db = {
49+
connection: null,
50+
51+
async init () {
52+
return new Promise((resolve, reject) => {
53+
const dbPath = path.join(__dirname, 'nostr-links.db')
54+
this.connection = new sqlite3.Database(dbPath, (err) => {
55+
if (err) {
56+
logger.error(`Error opening database: ${err.message}`)
57+
reject(err)
58+
return
59+
}
60+
61+
this.connection.run(`
62+
CREATE TABLE IF NOT EXISTS notes (
63+
id TEXT PRIMARY KEY,
64+
pubkey TEXT,
65+
content TEXT,
66+
created_at INTEGER,
67+
metadata TEXT,
68+
processed_at INTEGER
69+
)
70+
`, (err) => {
71+
if (err) {
72+
logger.error(`Error creating table: ${err.message}`)
73+
reject(err)
74+
return
75+
}
76+
resolve()
77+
})
78+
})
79+
})
80+
},
81+
82+
async getLatestNoteTimestamp () {
83+
return new Promise((resolve, reject) => {
84+
this.connection.get(
85+
'SELECT MAX(created_at) as latest FROM notes',
86+
(err, row) => {
87+
if (err) {
88+
reject(err)
89+
return
90+
}
91+
resolve(row?.latest || 0)
92+
}
93+
)
94+
})
95+
},
96+
97+
async saveNote (note) {
98+
return new Promise((resolve, reject) => {
99+
const metadata = note.userMetadata ? JSON.stringify(note.userMetadata) : null
100+
this.connection.run(
101+
`INSERT OR IGNORE INTO notes (id, pubkey, content, created_at, metadata, processed_at)
102+
VALUES (?, ?, ?, ?, ?, ?)`,
103+
[note.id, note.pubkey, note.content, note.created_at, metadata, Math.floor(Date.now() / 1000)],
104+
(err) => {
105+
if (err) {
106+
reject(err)
107+
return
108+
}
109+
resolve()
110+
}
111+
)
112+
})
113+
},
114+
115+
async close () {
116+
return new Promise((resolve, reject) => {
117+
if (this.connection) {
118+
this.connection.close((err) => {
119+
if (err) {
120+
reject(err)
121+
return
122+
}
123+
resolve()
124+
})
125+
} else {
126+
resolve()
127+
}
128+
})
129+
}
130+
}
131+
42132
// Default configuration
43133
let config = {
44134
userPubkeys: [],
@@ -236,9 +326,16 @@ async function fetchEvents (relayUrls, filter, timeoutMs = 10000) {
236326
* @returns {Promise<Array>} - Array of note objects containing external links within the time interval
237327
*/
238328
async function getNotesWithLinks (userPubkeys, timeIntervalHours, relayUrls, ignorePubkeys = []) {
329+
// Get the latest stored note timestamp
330+
const latestStoredTimestamp = await db.getLatestNoteTimestamp()
331+
239332
// Calculate the cutoff time in seconds (Nostr uses UNIX timestamp)
240333
const now = Math.floor(Date.now() / 1000)
241-
const cutoffTime = now - (timeIntervalHours * 60 * 60)
334+
// Use the later of: configured time interval or latest stored note
335+
const configuredCutoff = now - (timeIntervalHours * 60 * 60)
336+
const cutoffTime = Math.max(configuredCutoff, latestStoredTimestamp)
337+
338+
logger.debug(`Using cutoff time: ${new Date(cutoffTime * 1000).toISOString()}`)
242339

243340
const allNotesWithLinks = []
244341
const allFollowedPubkeys = new Set() // To collect all followed pubkeys
@@ -395,6 +492,11 @@ async function getNotesWithLinks (userPubkeys, timeIntervalHours, relayUrls, ign
395492
logger.progress(`Completed processing all ${totalBatches} batches`)
396493
}
397494

495+
// After processing notes and before returning, save them to the database
496+
for (const note of allNotesWithLinks) {
497+
await db.saveNote(note)
498+
}
499+
398500
return allNotesWithLinks
399501
}
400502

@@ -405,9 +507,12 @@ async function getNotesWithLinks (userPubkeys, timeIntervalHours, relayUrls, ign
405507
* @returns {String} - Formatted string with note information
406508
*/
407509
function formatNoteOutput (notes) {
510+
// Sort notes by timestamp (newest first)
511+
const sortedNotes = [...notes].sort((a, b) => b.created_at - a.created_at)
512+
408513
const output = []
409514

410-
for (const note of notes) {
515+
for (const note of sortedNotes) {
411516
// Get note ID as npub
412517
const noteId = nip19.noteEncode(note.id)
413518
const pubkey = nip19.npubEncode(note.pubkey)
@@ -500,12 +605,15 @@ function normalizeToHexPubkey (key) {
500605
* Main function to execute the script
501606
*/
502607
async function main () {
503-
// Load configuration from file
504-
const configPath = path.join(__dirname, 'nostr-link-extract.config.json')
505-
logger.info(`Loading configuration from ${configPath}`)
506-
config = loadConfig(configPath)
608+
// Initialize database
609+
await db.init()
507610

508611
try {
612+
// Load configuration from file
613+
const configPath = path.join(__dirname, 'nostr-link-extract.config.json')
614+
logger.info(`Loading configuration from ${configPath}`)
615+
config = loadConfig(configPath)
616+
509617
logger.info(`Starting Nostr link extraction (time interval: ${config.timeIntervalHours} hours)`)
510618

511619
// Convert any npub format keys to hex
@@ -536,6 +644,9 @@ async function main () {
536644
}
537645
} catch (error) {
538646
logger.error(`${error}`)
647+
} finally {
648+
// Close database connection
649+
await db.close()
539650
}
540651
}
541652

0 commit comments

Comments
 (0)