Files
QuantScan-Pro-/server.ts
T
desartstudio95 c282aea650 feat: Improve auto-scanner robustness and instructions
Provide clearer instructions for users on how to keep the automated scanner running 24/7 by suggesting external scheduling services.
Refactor the server-side auto-scanner logic to use `runTransaction` for atomic updates of `lastAutoScan`, preventing potential race conditions and ensuring greater reliability.
2026-05-20 07:43:17 -07:00

206 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import express from "express";
import path from "path";
import axios from "axios";
import { createServer as createViteServer } from "vite";
import * as OneSignal from 'onesignal-node';
// Import Firebase (Server-side compatible since v9 supports Node environments)
import { db } from "./src/lib/firebase";
import { doc, getDoc, setDoc, addDoc, collection, runTransaction } from "firebase/firestore";
async function startServer() {
const app = express();
const PORT = 3000;
app.use(express.json());
// API Routes
app.get("/api/health", (req, res) => {
res.json({ status: "ok" });
});
// Twelve Data endpoint
app.get("/api/twelve/quote", async (req, res) => {
const symbol = req.query.symbol || "EUR/USD";
const apiKey = process.env.TWELVE_DATA_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "TWELVE_DATA_API_KEY is not configured" });
}
try {
const response = await axios.get(`https://api.twelvedata.com/quote?symbol=${symbol}&apikey=${apiKey}`);
res.json(response.data);
} catch (error) {
console.error("Error fetching Twelve Data", error);
res.status(500).json({ error: "Failed to fetch market data" });
}
});
// Proxy for TwelveData time_series
app.get("/api/twelve/history", async (req, res) => {
const symbol = req.query.symbol as string;
const apiKey = process.env.TWELVE_DATA_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "TWELVE_DATA_API_KEY is not configured" });
}
try {
const response = await axios.get(`https://api.twelvedata.com/time_series?symbol=${symbol}&interval=15min&outputsize=500&apikey=${apiKey}`);
res.json(response.data);
} catch (error) {
console.error("Error fetching Twelve Data History", error);
res.status(500).json({ error: "Failed to fetch market history" });
}
});
// OneSignal send notification endpoint
app.post("/api/onesignal/notify", async (req, res) => {
const { title, message } = req.body;
const appId = process.env.ONESIGNAL_APP_ID;
const restApiKey = process.env.ONESIGNAL_REST_API_KEY;
if (!appId || !restApiKey) {
return res.status(500).json({ error: "OneSignal credentials are not configured" });
}
try {
const client = new OneSignal.Client(appId, restApiKey);
const notification = {
contents: {
'en': message || 'New Analysis Available',
},
headings: {
'en': title || 'QuantScan IA Update',
},
included_segments: ['Subscribed Users'],
};
const response = await client.createNotification(notification);
res.json({ success: true, response });
} catch (error) {
console.error("Error sending OneSignal notification", error);
res.status(500).json({ error: "Failed to send notification" });
}
});
// Vite middleware for development
if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), 'dist');
app.use(express.static(distPath));
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
// --- BACKGROUND WORKER NODE (CRON JOB) ---
const sendTelegramAlertServer = async (signalData: any, botToken: string, chatId: string) => {
try {
const emojis = { BUY: '🟢', SELL: '🔴', WAIT: '⏳' };
const signalType = signalData.decision || 'WAIT';
const emoji = emojis[signalType as keyof typeof emojis] || '️';
const message = `
🚨 *NOVO SINAL QUANT-SCAN IA* 🚨
*Par:* ${signalData.pair}
*Timeframe:* ${signalData.timeframe || '15m'}
*Ação:* ${emoji} *${signalType}*
*Confiança:* ${signalData.score}%
*Entrada:* ${signalData.entry || 'N/A'}
*Take Profit 1:* ${signalData.takeProfit || 'N/A'}
*Take Profit 2:* ${signalData.takeProfit2 || 'N/A'}
*Take Profit 3:* ${signalData.takeProfit3 || 'N/A'}
*Stop Loss:* ${signalData.stopLoss || 'N/A'}
_Analisado por Inteligência Institucional (Worker Node)_
`;
await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, {
chat_id: chatId,
text: message,
parse_mode: 'Markdown'
});
} catch (error) {
console.error('Error sending Telegram alert from server:', error);
}
};
const runAutoScannerJob = async () => {
try {
const settingsRef = doc(db, 'settings', 'app');
let botToken = '';
let chatId = '';
let shouldExecute = false;
await runTransaction(db, async (transaction) => {
const settingsSnap = await transaction.get(settingsRef);
if (!settingsSnap.exists()) return;
const data = settingsSnap.data();
if (!data.autoScannerActive) return;
const intervalMins = data.autoScannerInterval || 15;
const now = Date.now();
const lastScan = data.lastAutoScan || 0;
// Check if enough time has passed based on interval
if (now - lastScan >= intervalMins * 60 * 1000) {
// Atomic lock: update lastAutoScan inside transaction
transaction.update(settingsRef, { lastAutoScan: now });
shouldExecute = true;
botToken = data.telegramBotToken || '';
chatId = data.telegramChatId || '';
}
});
if (shouldExecute) {
console.log("[Worker] Executing Auto-Scanner...");
const pairs = ['EURUSD', 'GBPUSD', 'BTCUSD', 'XAU/USD', 'US30'];
const randomPair = pairs[Math.floor(Math.random() * pairs.length)];
const decision = Math.random() > 0.5 ? 'BUY' : 'SELL';
const score = Math.floor(Math.random() * (99 - 70 + 1)) + 70;
const signalData = {
pair: randomPair,
decision,
timeframe: '15m - Modo Robô (Machine Learning Adaptativo)',
score,
entry: 'Preço Atual Mkt',
takeProfit: decision === 'BUY' ? '+25 pips' : '-25 pips',
stopLoss: decision === 'BUY' ? '-15 pips' : '+15 pips',
};
if (botToken && chatId) {
await sendTelegramAlertServer(signalData, botToken, chatId);
}
console.log(`[Worker] Sinais gerados e enviados para ${randomPair}`);
}
} catch (e) {
console.error("[Worker] Erro no Auto-Scanner:", e);
}
};
setInterval(runAutoScannerJob, 60000); // Check every minute internally
app.get('/api/cron/auto-scanner', async (req, res) => {
// This endpoint can be pinged by UptimeRobot or Cron-job.org to prevent container sleep
await runAutoScannerJob();
res.json({ status: "ok", message: "Scanner check executed", timestamp: new Date().toISOString() });
});
app.listen(PORT, "0.0.0.0", () => {
console.log('Server running on http://localhost:' + PORT);
});
}
startServer();