So I've done it using chokdir as you suggested, with a great lot of help from GPT4o
Anyways, in case this is helpful for anyone, the code is below.
This watches a folder structure for files automatically uploaded from the NextCloud app on an Android phone and then pushes the file out to a Telegram bot, in this case any pictures or videos taken on the same phone. It now changes the watched folder depending on the date as the Nextcloud app will alter the upload path for the Year and Month (likely also a good idea for organising photos)
This completes faster than the built in Watch node.
You'll need an Inject node to create the watcher, prob a good idea to make it run on start and then routinely at midnight or something. (maybe an overkill)
// Import required modules from the global context
const chokidar = global.get('chokidar');
const fs = global.get('fs');
const path = global.get('path');
// Constants
const CHAT_ID = "-123456789";
const WATCHER_ID = "uniqueWatcherId";
const DEBOUNCE_RATE_SECONDS = 1; // 1 second debounce rate
const TELEGRAM_RATE_LIMIT_MS = DEBOUNCE_RATE_SECONDS * 1000; // Convert to milliseconds
// Get current year and month
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
// Set the path to monitor using the current year and month
const pathToWatch = `/pictures/${year}/${month}`;
// Function to get the list of existing files
function getExistingFiles(dirPath) {
try {
return fs.readdirSync(dirPath).map(file => path.join(dirPath, file));
} catch (err) {
node.error(`Error reading directory: ${err.message}`);
return [];
}
}
// Check if a watcher already exists with the specific ID
let existingWatcher = global.get(WATCHER_ID);
if (existingWatcher) {
// Close the existing watcher
existingWatcher.close();
// Remove the existing watcher from the global context
global.set(WATCHER_ID, null);
}
// Get the list of existing files
const existingFiles = getExistingFiles(pathToWatch);
global.set('existingFiles', existingFiles);
// Initialize watcher
const watcher = chokidar.watch(path.join(pathToWatch, '*.{jpg,mp4}'), {
persistent: true,
ignoreInitial: true // Ignore initial add events
});
// Function to determine the file type
function getFileType(filePath) {
if (filePath.endsWith('.jpg')) {
return 'document';
} else if (filePath.endsWith('.mp4')) {
return 'video';
} else {
return 'unknown';
}
}
// Debounce mechanism to avoid rapid multiple events and respect Telegram rate limits
let debounceTimeout;
function debounceEvent(callback, delay = TELEGRAM_RATE_LIMIT_MS) {
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(callback, delay);
}
// Define event listeners
watcher
.on('add', filePath => {
debounceEvent(() => {
const existingFiles = global.get('existingFiles');
if (!existingFiles.includes(filePath)) {
let msg = {};
msg.payload = {
content: filePath,
chatId: CHAT_ID,
type: getFileType(filePath)
};
node.send(msg);
}
});
})
.on('change', filePath => {
debounceEvent(() => {
let msg = {};
msg.payload = {
content: filePath,
chatId: CHAT_ID,
type: getFileType(filePath)
};
node.send(msg);
});
})
.on('unlink', filePath => {
debounceEvent(() => {
let msg = {};
msg.payload = {
content: filePath,
chatId: CHAT_ID,
type: getFileType(filePath)
};
node.send(msg);
});
})
.on('error', error => {
node.error(`Watcher error: ${error.message}`);
});
// Save watcher to global context with the unique ID to prevent multiple watchers
global.set(WATCHER_ID, watcher);
return null;
This needs to be included into NodeRed's settings.js and then restart NodeRed
functionGlobalContext: {
chokidar: require('chokidar'),
fs: require('fs'),
path: require('path')
},