From aec12651782dc34ce9106b3bc070d0979aa62d17 Mon Sep 17 00:00:00 2001 From: Greirson Lee-Thorp Date: Thu, 30 Jan 2025 20:52:28 -0800 Subject: [PATCH] Add notifications support via Apprise - Added Apprise integration for flexible notifications - Added notification environment variables - Added notification logging - Updated documentation with setup instructions - Added Python and Apprise to Dockerfile --- .env.example | 6 +++++- Dockerfile | 11 +++++++++++ README.md | 12 ++++++++++++ docker-compose.yml | 4 +++- package.json | 1 + server.js | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index acc51db..bbbe88d 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,8 @@ PORT=3000 # The port the server will listen on MAX_FILE_SIZE=1024 # Maximum file size in MB (default: 1024 MB / 1 GB) # Security -DUMBDROP_PIN= # Optional 4-digit PIN protection (leave empty to disable) \ No newline at end of file +DUMBDROP_PIN= # Optional 4-digit PIN protection (leave empty to disable) + +# Notifications +APPRISE_URL= # Apprise URL for notifications (leave empty to disable) +APPRISE_MESSAGE= # Custom message for notifications (default: "File uploaded: {filename}") \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e63a95b..e8f609f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,16 @@ FROM node:18-alpine +# Install python and create virtual environment +RUN apk add --no-cache python3 py3-pip && \ + python3 -m venv /opt/venv + +# Activate virtual environment and install apprise +RUN . /opt/venv/bin/activate && \ + pip install --no-cache-dir apprise + +# Add virtual environment to PATH +ENV PATH="/opt/venv/bin:$PATH" + WORKDIR /app COPY package*.json ./ diff --git a/README.md b/README.md index 3b894b7..5e15e18 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ No auth (unless you want it now!), no storage, no nothing. Just a simple file up - Configurable file size limits - Drag and Drop Directory Support (Maintains file structure in upload) - Optional PIN protection (4-10 digits) with secure validation +- Configurable notifications via Apprise +- Custom notification messages with filename templating ## Environment Variables @@ -26,6 +28,8 @@ No auth (unless you want it now!), no storage, no nothing. Just a simple file up | PORT | Server port | 3000 | No | | MAX_FILE_SIZE| Maximum file size in MB | 1024 | No | | DUMBDROP_PIN | PIN protection (4-10 digits) | None | No | +| APPRISE_URL | Apprise URL for notifications | None | No | +| APPRISE_MESSAGE| Notification message template | "File uploaded: {filename}" | No | ## Security Features @@ -35,6 +39,12 @@ No auth (unless you want it now!), no storage, no nothing. Just a simple file up - Secure PIN validation middleware - No PIN storage in browser (memory only) +## Notification Support +- Integration with [Apprise](https://github.com/caronc/apprise?tab=readme-ov-file#supported-notifications) for flexible notifications +- Support for all Apprise notification services +- Customizable notification messages with filename templating +- Optional - disabled if no APPRISE_URL is set + # Future Features - Camera Upload for Mobile - Enhanced Progress Features (upload speed display, time remaining estimation) @@ -87,6 +97,8 @@ services: - $(pwd)/local_uploads:/app/uploads environment: - DUMBDROP_PIN=123456 + # - APPRISE_URL= # i.e. tgram://bottoken/ChatID + # - APPRISE_MESSAGE= image: abite3/dumbdrop:latest ``` diff --git a/docker-compose.yml b/docker-compose.yml index fe8d148..8708168 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,9 @@ services: ports: - 3000:3000 volumes: - - $(pwd)/local_uploads:/app/uploads + - ./local_uploads:/app/uploads environment: - DUMBDROP_PIN=123456 + - APPRISE_URL= # i.e. tgram://bottoken/ChatID + - APPRISE_MESSAGE= # dont add quotes here image: abite3/dumbdrop:latest \ No newline at end of file diff --git a/package.json b/package.json index f11425e..715c697 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "description": "A simple file upload application", "dependencies": { + "apprise": "^1.0.0", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.0.3", diff --git a/server.js b/server.js index ffa60e9..67ea6d4 100644 --- a/server.js +++ b/server.js @@ -5,12 +5,17 @@ const cors = require('cors'); const fs = require('fs'); const crypto = require('crypto'); const cookieParser = require('cookie-parser'); +const { exec } = require('child_process'); +const util = require('util'); +const execAsync = util.promisify(exec); require('dotenv').config(); const app = express(); const port = process.env.PORT || 3000; const uploadDir = './uploads'; // Local development const maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '1024') * 1024 * 1024; // Convert MB to bytes +const APPRISE_URL = process.env.APPRISE_URL; +const APPRISE_MESSAGE = process.env.APPRISE_MESSAGE || 'File uploaded: {filename}'; // Brute force protection setup const loginAttempts = new Map(); // Stores IP addresses and their attempt counts @@ -291,6 +296,9 @@ app.post('/upload/chunk/:uploadId', express.raw({ upload.writeStream.end(); uploads.delete(uploadId); log.success(`Upload completed: ${upload.filename}`); + + // Add notification here + sendNotification(upload.filename); } } catch (err) { log.error(`Chunk upload failed: ${err.message}`); @@ -325,6 +333,15 @@ app.listen(port, () => { log.info(`Server running at http://localhost:${port}`); log.info(`Upload directory: ${uploadDir}`); + // Add Apprise configuration logging + if (APPRISE_URL) { + log.info(`Apprise notifications enabled`); + log.info(`Apprise URL: ${APPRISE_URL}`); + log.info(`Apprise message template: ${APPRISE_MESSAGE}`); + } else { + log.info('Apprise notifications disabled - no URL configured'); + } + // List directory contents try { const files = fs.readdirSync(uploadDir); @@ -336,3 +353,18 @@ app.listen(port, () => { log.error(`Failed to list directory contents: ${err.message}`); } }); + +// Add this helper function after other helper functions +async function sendNotification(filename) { + if (!APPRISE_URL) return; + + try { + const message = APPRISE_MESSAGE.replace('{filename}', filename); + + // Execute apprise command + await execAsync(`apprise "${APPRISE_URL}" -b "${message}"`); + log.info(`Notification sent for: ${filename}`); + } catch (err) { + log.error(`Failed to send notification: ${err.message}`); + } +}