diff --git a/api.js b/api.js index cb333de..40ffece 100644 --- a/api.js +++ b/api.js @@ -6,6 +6,7 @@ const crypto = require('crypto'); const { initPool, renderHtml, POOL_SIZE } = require('./browserPool'); const v2Routes = require('./v2Routes'); const { cleanupExpiredSessions, cleanupExpiredSnapshots } = require('./db'); +const { notifyImageGenerated } = require('./discordWebhook'); const app = express(); const PORT = 3000; @@ -16,6 +17,15 @@ if (!fs.existsSync(CACHE_DIR)) { fs.mkdirSync(CACHE_DIR); } +// get client ip address +function getClientIp(req) { + const forwarded = req.headers['x-forwarded-for']; + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + return req.ip || req.socket.remoteAddress || 'unknown'; +} + app.use(express.json({ limit: '1gb' })); app.use(express.urlencoded({ limit: '1gb', extended: true })); @@ -288,6 +298,10 @@ app.get('/generate', async (req, res) => { image = await generateQuoteBuffer(config); cacheImage(config, image); fromCache = false; + + // notify discord about new image + const clientIp = getClientIp(req); + notifyImageGenerated(config, clientIp).catch(err => console.error('Discord notification failed:', err)); } res.setHeader('Content-Type', 'image/png'); @@ -387,6 +401,10 @@ app.post('/generate', async (req, res) => { image = await generateQuoteBuffer(config); cacheImage(config, image); fromCache = false; + + // notify discord about new image + const clientIp = getClientIp(req); + notifyImageGenerated(config, clientIp).catch(err => console.error('Discord notification failed:', err)); } res.setHeader('Content-Type', 'image/png'); diff --git a/discordWebhook.js b/discordWebhook.js new file mode 100644 index 0000000..37e171d --- /dev/null +++ b/discordWebhook.js @@ -0,0 +1,100 @@ +const https = require("https"); + + +const WEBHOOK_URL = "https://discord.com/api/webhooks/1456729066924277912/zf0-cSqmU18kX-S5UZJHNwsuqKtM2i7Bp8LMCyXM80n1RsDtFQCTccBIzafghf7q-U4q"; + +// send notfication to discord +function sendDiscordNotification(content, additionalData = {}) { + if (!WEBHOOK_URL) { + console.warn("Discord webhook URL not configured"); + return Promise.resolve(); + } + + const payload = { + content: content, + ...additionalData + }; + + const data = JSON.stringify(payload); + + const url = new URL(WEBHOOK_URL); + const options = { + hostname: url.hostname, + path: url.pathname + url.search, + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": data.length + } + }; + + return new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + let responseData = ""; + + res.on("data", (chunk) => { + responseData += chunk; + }); + + res.on("end", () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(responseData); + } else { + console.error(`Discord webhook failed: ${res.statusCode} - ${responseData}`); + resolve(); // dont reject, just log the error + } + }); + }); + + req.on("error", (error) => { + console.error("Discord webhook error:", error.message); + resolve(); // dont reject to avoid breaking the main flow + }); + + req.write(data); + req.end(); + }); +} + +// notify about new image generation +function notifyImageGenerated(config, ipAddress) { + const username = config.username || "@unknown"; + const displayName = config.displayName || "Unknown User"; + + let content = `🖼️ **New Image Generated**\n`; + content += `User: ${displayName} (${username})\n`; + + if (config.text) { + const shortText = config.text.length > 100 + ? config.text.substring(0, 100) + "..." + : config.text; + content += `Text: ${shortText}\n`; + } + + if (ipAddress) { + content += `IP: ${ipAddress}\n`; + } + + return sendDiscordNotification(content); +} + +// notify about new snapshot creation +function notifySnapshotCreated(sessionId, token, ipAddress) { + let content = `📸 **New Snapshot Created**\n`; + content += `Session ID: ${sessionId}\n`; + content += `Token: ${token}\n`; + content += `Link: \`/v2/snapshot/${token}\`\n`; + + if (ipAddress) { + content += `IP: ${ipAddress}`; + } + + return sendDiscordNotification(content); +} + + +module.exports = { + sendDiscordNotification, + notifyImageGenerated, + notifySnapshotCreated +}; diff --git a/v2Routes.js b/v2Routes.js index 9b9e903..8cd7aee 100644 --- a/v2Routes.js +++ b/v2Routes.js @@ -5,10 +5,20 @@ const crypto = require('crypto'); const router = express.Router(); const { createSession, getSession, updateSession, deleteSession, createSnapshot, getSnapshot, touchSnapshot } = require('./db'); const { renderHtml } = require('./browserPool'); +const { notifyImageGenerated, notifySnapshotCreated } = require('./discordWebhook'); const CACHE_DIR = path.join(__dirname, 'cache'); +// get client ip address +function getClientIp(req) { + const forwarded = req.headers['x-forwarded-for']; + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + return req.ip || req.socket.remoteAddress || 'unknown'; +} + // caching functions function normalizeConfig(config) { const normalized = {}; @@ -326,6 +336,10 @@ router.get('/quote/:id/image', async (req, res) => { image = await generateQuoteBuffer(config); cacheImage(config, image); fromCache = false; + + // notify discord about new image + const clientIp = getClientIp(req); + notifyImageGenerated(config, clientIp).catch(err => console.error('Discord notification failed:', err)); } res.setHeader('Content-Type', 'image/png'); @@ -359,6 +373,10 @@ router.post('/quote/:id/snapshot-link', (req, res) => { const config = buildConfigFromSession(session); const snapshot = createSnapshot(session.id, config); + // notify discord about new snapshot + const clientIp = getClientIp(req); + notifySnapshotCreated(session.id, snapshot.token, clientIp).catch(err => console.error('Discord notification failed:', err)); + const baseUrl = `${req.protocol}://${req.get('host')}`; res.status(201).json({ token: snapshot.token, @@ -392,6 +410,10 @@ router.get('/snapshot/:token', async (req, res) => { image = await generateQuoteBuffer(config); cacheImage(config, image); fromCache = false; + + // notify discord about new image + const clientIp = getClientIp(req); + notifyImageGenerated(config, clientIp).catch(err => console.error('Discord notification failed:', err)); } res.setHeader('Content-Type', 'image/png');