2026-05-20 03:26:54 -07:00
|
|
|
|
import express from "express";
|
|
|
|
|
|
import path from "path";
|
|
|
|
|
|
import axios from "axios";
|
|
|
|
|
|
import { createServer as createViteServer } from "vite";
|
|
|
|
|
|
import * as OneSignal from 'onesignal-node';
|
|
|
|
|
|
|
2026-05-20 04:12:56 -07:00
|
|
|
|
// Import Firebase (Server-side compatible since v9 supports Node environments)
|
|
|
|
|
|
import { db } from "./src/lib/firebase";
|
2026-05-20 07:43:17 -07:00
|
|
|
|
import { doc, getDoc, setDoc, addDoc, collection, runTransaction } from "firebase/firestore";
|
2026-05-20 04:12:56 -07:00
|
|
|
|
|
2026-05-20 03:26:54 -07:00
|
|
|
|
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'));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 04:12:56 -07:00
|
|
|
|
// --- 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-20 07:43:17 -07:00
|
|
|
|
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);
|
2026-05-20 04:12:56 -07:00
|
|
|
|
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) {
|
2026-05-20 07:43:17 -07:00
|
|
|
|
// 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);
|
2026-05-20 04:12:56 -07:00
|
|
|
|
}
|
2026-05-20 07:43:17 -07:00
|
|
|
|
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() });
|
|
|
|
|
|
});
|
2026-05-20 04:12:56 -07:00
|
|
|
|
|
2026-05-20 03:26:54 -07:00
|
|
|
|
app.listen(PORT, "0.0.0.0", () => {
|
|
|
|
|
|
console.log('Server running on http://localhost:' + PORT);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startServer();
|