Add upload email notifications with Seafile direct-upload watcher; v2.0.6
- Portal uploads send one email immediately (host, episode, passage, description, filenames) via Resend to NUGGETS_NOTIFY_EMAIL - Seafile watcher polls /Episode Upload every 15 min for direct uploads and sends a notification for any new file not already seen via portal - Files uploaded through the portal are marked seen so the watcher never sends a duplicate notification - Credentials kept out of git: SEAFILE_TOKEN, SEAFILE_NUGGETS_REPO_ID, NUGGETS_NOTIFY_EMAIL now sourced from docker .env file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a03813914
commit
1e396644a9
7 files changed
+177
-5
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { sendNuggetsUploadNotification } from './email.js'
|
||||
import {
|
||||
SEAFILE_URL,
|
||||
SEAFILE_TOKEN,
|
||||
SEAFILE_NUGGETS_REPO_ID,
|
||||
SEAFILE_EPISODE_UPLOAD_DIR,
|
||||
SEAFILE_EPISODE_INFO_DIR,
|
||||
SEAFILE_POLL_INTERVAL_MS,
|
||||
NUGGETS_SEAFILE_SEEN_FILE,
|
||||
NUGGETS_NOTIFY_EMAIL,
|
||||
} from './config.js'
|
||||
|
||||
async function loadSeen() {
|
||||
try {
|
||||
return JSON.parse(await readFile(NUGGETS_SEAFILE_SEEN_FILE, 'utf8'))
|
||||
} catch {
|
||||
return { files: [] }
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSeen(data) {
|
||||
await writeFile(NUGGETS_SEAFILE_SEEN_FILE, JSON.stringify(data, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
// Called by the upload route after a successful portal upload so the poller
|
||||
// doesn't also fire a notification for the same file.
|
||||
export async function markSeafileFileSeen(filename) {
|
||||
const data = await loadSeen()
|
||||
if (!data.files.includes(filename)) {
|
||||
data.files.push(filename)
|
||||
await saveSeen(data)
|
||||
}
|
||||
}
|
||||
|
||||
async function listSeafileDir(dir) {
|
||||
if (!SEAFILE_URL || !SEAFILE_TOKEN || !SEAFILE_NUGGETS_REPO_ID) return []
|
||||
try {
|
||||
const url = `${SEAFILE_URL}/api/v2.1/repos/${SEAFILE_NUGGETS_REPO_ID}/dir/?p=${encodeURIComponent(dir)}`
|
||||
const res = await fetch(url, {
|
||||
headers: { Authorization: `Token ${SEAFILE_TOKEN}` },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
})
|
||||
if (!res.ok) return []
|
||||
const data = await res.json()
|
||||
return (data.dirent ?? []).filter(e => e.type === 'file').map(e => e.name)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function poll() {
|
||||
if (!SEAFILE_URL || !SEAFILE_TOKEN || !SEAFILE_NUGGETS_REPO_ID) return
|
||||
|
||||
const [uploadFiles, seen] = await Promise.all([
|
||||
listSeafileDir(SEAFILE_EPISODE_UPLOAD_DIR),
|
||||
loadSeen(),
|
||||
])
|
||||
|
||||
const seenSet = new Set(seen.files)
|
||||
const newFiles = uploadFiles.filter(f => !seenSet.has(f))
|
||||
|
||||
if (newFiles.length === 0) return
|
||||
|
||||
for (const filename of newFiles) {
|
||||
console.log(`[nuggets-watcher] new direct upload detected: ${filename}`)
|
||||
await sendNuggetsUploadNotification({
|
||||
to: NUGGETS_NOTIFY_EMAIL,
|
||||
source: 'direct',
|
||||
filename,
|
||||
})
|
||||
seenSet.add(filename)
|
||||
}
|
||||
|
||||
seen.files = [...seenSet]
|
||||
await saveSeen(seen)
|
||||
}
|
||||
|
||||
export function startNuggetsSeafileWatcher() {
|
||||
if (!SEAFILE_URL || !SEAFILE_TOKEN || !SEAFILE_NUGGETS_REPO_ID) {
|
||||
console.log('[nuggets-watcher] Seafile not configured — watcher disabled.')
|
||||
return
|
||||
}
|
||||
// Initial poll after 2 min (let the server settle), then every 15 min
|
||||
setTimeout(() => {
|
||||
poll().catch(err => console.error('[nuggets-watcher] poll error:', err))
|
||||
setInterval(() => {
|
||||
poll().catch(err => console.error('[nuggets-watcher] poll error:', err))
|
||||
}, SEAFILE_POLL_INTERVAL_MS)
|
||||
}, 2 * 60 * 1000)
|
||||
|
||||
console.log(`[nuggets-watcher] started — polling every ${SEAFILE_POLL_INTERVAL_MS / 60000} min`)
|
||||
}
|
||||
Reference in new issue
Block a user