titan-bot / server.js
M-hv1's picture
Upload 2 files
bf2c94a verified
Raw
History Blame Contribute Delete
130 kB
// ==========================================
// 🦅 TITAN NEXUS SERVER v14.1 - TIME-AWARE ROYAL AI EDITION
// 🛡️ SENTINEL MK2 ACTIVE | ⚡ SOCKET.IO RELAY | 🎛️ ADMIN COMMAND CENTER
// 👑 ROYAL IMMUNITY SYSTEM | 🧠 GEMMA 4 AI | 🕰️ TIME-AWARE ENGINE
// 📅 AUTOMATED CONTENT GENERATION & ROYAL REPORTS
// 🧩 ANTI-DUPLICATE RIDDLE SYSTEM (riddle_history collection)
// ==========================================
const admin = require("firebase-admin");
const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const natural = require("natural");
const http = require("http");
const { Server } = require("socket.io");
const os = require("os");
const cron = require("node-cron");
const { GoogleGenerativeAI } = require("@google/generative-ai");
const https = require("https");
const { pipeline } = require("stream");
const { promisify } = require("util");
const streamPipeline = promisify(pipeline);
const initExtensions = require('./server-extensions'); // 🪙 Wallet + new admin endpoints
// 1. CORE CONFIGURATION
const app = express();
app.set('trust proxy', 2);
const PORT = process.env.PORT || 7860;
// ==========================================
// 🔑 ENVIRONMENT VARIABLES — Hugging Face Secrets
// Required secrets in HF Space settings:
// FIREBASE_SERVICE_ACCOUNT → full serviceAccountKey.json content (JSON string)
// GEMINI_API_KEY → Gemini API key
// GROQ_API_KEY → Groq API key
// ADMIN_ID → Firebase UID of the admin user
// ==========================================
// Validate all required secrets on startup
const REQUIRED_ENV = ['FIREBASE_SERVICE_ACCOUNT', 'GEMINI_API_KEY', 'GROQ_API_KEY', 'ADMIN_ID', 'TITAN_GATEWAY_KEY', 'TITAN_RELAY_KEY'];
const missingEnv = REQUIRED_ENV.filter(k => !process.env[k]);
if (missingEnv.length > 0) {
console.error(`\x1b[31m[FATAL] Missing required environment variables: ${missingEnv.join(', ')}\x1b[0m`);
console.error('\x1b[31m[FATAL] Set them in Hugging Face Space → Settings → Repository secrets\x1b[0m');
process.exit(1);
}
// 🔑 SECURITY & AUTHENTICATION
let SERVICE_ACCOUNT;
try {
SERVICE_ACCOUNT = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
} catch (e) {
console.error('\x1b[31m[FATAL] FIREBASE_SERVICE_ACCOUNT is not valid JSON. Paste the full serviceAccountKey.json content.\x1b[0m');
process.exit(1);
}
const ADMIN_ID = process.env.ADMIN_ID;
// 👑 ROYAL IMMUNITY SYSTEM
const ROYAL_FAMILY = [
process.env.ADMIN_ID,
"jcVOBGfUpkVnZOI8CQ0HRelRsed2"
];
// 🧠 GEMINI AI CONFIGURATION
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
const geminiModel = genAI.getGenerativeModel({ model: "gemini-2.5-flash-lite" });
// 📈 GROQ — for news/stocks only
const GROQ_API_KEY = process.env.GROQ_API_KEY;
const GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
const GROQ_MARKET_MODEL = "compound-beta";
// 📧 EMAIL via Resend API (works on HF Spaces — no SMTP blocking)
// 📧 EMAIL via Google Apps Script Mailer Bridge
// HF Secrets required: MAILER_URL, MAILER_TOKEN
const MAILER_URL = process.env.MAILER_URL;
const MAILER_TOKEN = process.env.MAILER_TOKEN;
async function sendTitanEmail(to, subject, htmlBody) {
if (!MAILER_URL) throw new Error('MAILER_URL not set in HF Secrets');
if (!MAILER_TOKEN) throw new Error('MAILER_TOKEN not set in HF Secrets');
const toStr = Array.isArray(to) ? to.join(',') : to;
const payload = JSON.stringify({ token: MAILER_TOKEN, to: toStr, subject, htmlBody });
// Google Apps Script redirects POST to GET — use native https to handle this
return new Promise((resolve, reject) => {
const url = new URL(MAILER_URL);
const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
}
};
function makeRequest(reqOptions, body, redirectCount = 0) {
if (redirectCount > 5) return reject(new Error('Too many redirects'));
const req = https.request(reqOptions, (res) => {
// Handle Google's redirect (302/301)
if ((res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) && res.headers.location) {
const redirectUrl = new URL(res.headers.location);
res.resume(); // drain response
// Follow redirect — for 302 use GET (standard behavior)
const newOpts = {
hostname: redirectUrl.hostname,
path: redirectUrl.pathname + redirectUrl.search,
method: res.statusCode === 307 ? reqOptions.method : 'GET',
headers: { 'Content-Type': 'application/json' }
};
return makeRequest(newOpts, res.statusCode === 307 ? body : null, redirectCount + 1);
}
let d = '';
res.on('data', c => d += c);
res.on('end', () => {
try {
// Try JSON parse first
const parsed = JSON.parse(d);
if (parsed.status !== 'success') reject(new Error(parsed.message || 'Non-success status'));
else resolve(parsed);
} catch {
// If we get HTML but status is 2xx, treat as success (Google sometimes returns HTML on success)
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ status: 'success', note: 'HTML response treated as success' });
} else {
reject(new Error(`Non-JSON response (${res.statusCode}): ${d.slice(0, 100)}`));
}
}
});
});
req.setTimeout(20000, () => { req.destroy(); reject(new Error('Mailer timeout')); });
req.on('error', reject);
if (body) req.write(body);
req.end();
}
makeRequest(options, payload);
});
}
// Server start time for uptime calculation
const SERVER_START_TIME = Date.now();
// 2. FIREBASE INIT
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(SERVICE_ACCOUNT),
databaseURL: `https://${SERVICE_ACCOUNT.project_id}.firebaseio.com`
});
}
const db = admin.firestore();
// 3. MIDDLEWARE (Security first)
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
scriptSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"]
}
},
crossOriginEmbedderPolicy: false,
hsts: { maxAge: 31536000, includeSubDomains: true }
}));
app.use(cors({ origin: true, credentials: true }));
app.use(express.json({ limit: '30mb' }));
app.use(express.urlencoded({ extended: true, limit: '30mb' }));
// 4. HELPER: CONSOLE-ONLY LOGGING (no Firestore writes)
async function logSys(mod, msg, type = 'info') {
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
const colors = {
info: '\x1b[36m',
success: '\x1b[32m',
warning: '\x1b[33m',
error: '\x1b[31m',
system: '\x1b[35m',
cyber: '\x1b[34m',
royal: '\x1b[95m',
ai: '\x1b[96m'
};
const reset = '\x1b[0m';
const color = colors[type] || colors.info;
console.log(`${color}[${timestamp}] [${mod}]${reset} ${msg}`);
}
// 🔒 UID MASKER — hides Firebase UIDs in public logs (shows first 4 + last 4 chars)
function maskUid(uid) {
if (!uid || uid.length < 10) return '****';
return uid.substring(0, 4) + '••••••••••••••••' + uid.substring(uid.length - 4);
}
// 5. SENTIMENT ANALYSIS
async function analyzeSentiment(text) {
try {
const analyzer = new natural.SentimentAnalyzer('English', natural.PorterStemmer, 'afinn');
const tokenizer = new natural.WordTokenizer();
const tokens = tokenizer.tokenize(text);
const sentiment = analyzer.getSentiment(tokens);
return {
score: sentiment,
category: sentiment > 0.5 ? 'positive' : sentiment < -0.5 ? 'negative' : 'neutral',
confidence: Math.abs(sentiment)
};
} catch (e) {
return { score: 0, category: 'neutral', confidence: 0 };
}
}
// 6. SENTINEL MK2 WITH ROYAL IMMUNITY
class SentinelMK2 {
constructor() {
this.bannedPatterns = [
/hack(er|ing)?\s*guide/i,
/crack\s*software/i,
/bypass\s*security/i,
/ddos\s*tool/i,
/virus\s*generator/i,
/credit\s*card\s*generator/i
];
this.suspiciousKeywords = ['password', 'login', 'admin', 'root', 'exploit', 'vulnerability'];
this.userReputation = new Map();
this.reportPenalties = {
'spam': -5,
'harassment': -15,
'fake_identity': -10,
'fake identity': -10
};
}
async analyzeComment(comment) {
const analysis = {
riskLevel: 'low',
flags: [],
action: 'none',
confidence: 0
};
this.bannedPatterns.forEach(pattern => {
if (pattern.test(comment.text)) {
analysis.flags.push('banned_pattern');
analysis.riskLevel = 'critical';
analysis.confidence += 0.7;
}
});
const links = (comment.text.match(/https?:\/\/[^\s]+/g) || []).length;
if (links > 2) {
analysis.flags.push('excessive_links');
analysis.riskLevel = Math.max(analysis.riskLevel, 'high');
analysis.confidence += 0.3;
}
const sentiment = await analyzeSentiment(comment.text);
if (sentiment.category === 'negative' && sentiment.confidence > 0.6) {
analysis.flags.push('negative_sentiment');
analysis.riskLevel = Math.max(analysis.riskLevel, 'medium');
analysis.confidence += 0.2;
}
const userRep = this.userReputation.get(comment.userId) || 0;
if (userRep < -5) {
analysis.flags.push('low_reputation_user');
analysis.riskLevel = 'high';
analysis.confidence += 0.4;
}
if (analysis.riskLevel === 'critical') {
analysis.action = 'delete_ban';
} else if (analysis.riskLevel === 'high') {
analysis.action = 'delete_warn';
} else if (analysis.riskLevel === 'medium') {
analysis.action = 'flag_review';
}
return analysis;
}
async monitorComments() {
logSys('SENTINEL_MK2', '🛡️ Advanced comment monitoring activated', 'system');
db.collectionGroup('comments').onSnapshot(async (snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
const comment = change.doc.data();
const commentId = change.doc.id;
// 👑 ROYAL IMMUNITY CHECK
if (ROYAL_FAMILY.includes(comment.userId)) {
logSys('SENTINEL_MK2', `👑 Royal user ${maskUid(comment.userId)} - Immunity shield active`, 'royal');
return;
}
if (comment.userId === ADMIN_ID) return;
const analysis = await this.analyzeComment(comment);
if (analysis.action !== 'none') {
await this.handleViolation(comment, commentId, analysis);
}
await this.updateUserReputation(comment.userId, analysis);
}
});
}, (error) => {
logSys('SENTINEL_MK2', `⚠️ Listener Error (Backoff/Connection): ${error.message}`, 'error');
});
}
async monitorReports() {
logSys('SENTINEL_MK2', '⚖️ Real-Time Report & Ban System activated', 'system');
db.collection('reports')
.where('status', '==', 'pending')
.onSnapshot(async (snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
const report = change.doc.data();
const reportId = change.doc.id;
// 👑 ROYAL IMMUNITY CHECK
if (ROYAL_FAMILY.includes(report.reportedUserId)) {
logSys('SENTINEL_MK2', `👑 Report rejected - Royal immunity for ${maskUid(report.reportedUserId)}`, 'royal');
await db.collection('reports').doc(reportId).update({
status: 'rejected',
reviewedAt: admin.firestore.FieldValue.serverTimestamp(),
rejection_reason: 'Immunity Shield - Royal Family member',
processedBy: 'SENTINEL_MK2_ROYAL_GUARD'
});
return;
}
await this.processReport(report, reportId);
}
});
}, (error) => {
logSys('SENTINEL_MK2', `⚠️ Report Listener Error (Backoff/Connection): ${error.message}`, 'error');
});
}
async processReport(report, reportId) {
try {
logSys('SENTINEL_MK2', `⚖️ Processing report ${reportId} for user ${maskUid(report.reportedUserId)}`, 'info');
const reportType = (report.reason || report.type || 'spam').toLowerCase();
const penalty = this.reportPenalties[reportType] || -5;
let reputation = this.userReputation.get(report.reportedUserId) || 100;
reputation += penalty;
this.userReputation.set(report.reportedUserId, reputation);
await db.collection('user_reputation').doc(report.reportedUserId).set({
userId: report.reportedUserId,
score: reputation,
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
lastPenalty: { amount: penalty, reason: reportType, reportId }
}, { merge: true });
logSys('SENTINEL_MK2', `📉 User ${maskUid(report.reportedUserId)} reputation: ${reputation} (${penalty} penalty)`, 'warning');
let aiVerdict = `Reputation updated: ${reputation} points`;
let actionsTaken = [];
if (reputation <= 50 && reputation > 0) {
await db.collection('users').doc(report.reportedUserId).set({
isShadowBanned: true,
shadowBanReason: `Low reputation (${reputation}) from report ${reportId}`,
shadowBannedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
aiVerdict = `SHADOW BAN applied. User reputation: ${reputation}`;
actionsTaken.push('shadow_ban');
logSys('SENTINEL_MK2', `👤 User ${maskUid(report.reportedUserId)} shadow banned (reputation: ${reputation})`, 'warning');
}
if (reputation <= 0) {
try {
await admin.auth().revokeRefreshTokens(report.reportedUserId);
await db.collection('users').doc(report.reportedUserId).set({
banned: true,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
banReason: `Automatic ban: Reputation dropped to ${reputation}`,
bannedBy: 'SENTINEL_MK2_AUTOMOD',
reportId
}, { merge: true });
await db.collection('banned_users').doc(report.reportedUserId).set({
userId: report.reportedUserId,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
reason: 'automatic_ban_reputation',
finalReputation: reputation,
reportId,
violations: [reportType]
});
aiVerdict = `PERMABAN applied. Reputation dropped to ${reputation}. Firebase token revoked.`;
actionsTaken.push('permaban', 'token_revoked');
logSys('SENTINEL_MK2', `🚨 PERMABAN user ${maskUid(report.reportedUserId)} (reputation: ${reputation})`, 'error');
} catch (authError) {
logSys('SENTINEL_MK2', `Failed to revoke tokens for ${maskUid(report.reportedUserId)}: ${authError.message}`, 'error');
}
}
await db.collection('reports').doc(reportId).update({
status: 'reviewed',
reviewedAt: admin.firestore.FieldValue.serverTimestamp(),
ai_verdict: aiVerdict,
actions_taken: actionsTaken,
reputation_after: reputation,
penalty_applied: penalty,
processedBy: 'SENTINEL_MK2_AUTOMOD'
});
logSys('SENTINEL_MK2', `✅ Report ${reportId} processed. Verdict: ${aiVerdict}`, 'success');
} catch (error) {
logSys('SENTINEL_MK2', `❌ Failed to process report ${reportId}: ${error.message}`, 'error');
await db.collection('reports').doc(reportId).update({
status: 'error',
error: error.message,
processedAt: admin.firestore.FieldValue.serverTimestamp()
});
}
}
async handleViolation(comment, commentId, analysis) {
const commentRef = db.collection('comments').doc(commentId);
switch (analysis.action) {
case 'delete_ban':
await commentRef.delete();
await db.collection('banned_users').doc(comment.userId).set({
userId: comment.userId,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
reason: 'automatic_ban',
violation: analysis.flags.join(', '),
commentPreview: comment.text.substring(0, 100)
});
logSys('SENTINEL_MK2', `🚨 BANNED user ${maskUid(comment.userId)} for critical violation`, 'error');
break;
case 'delete_warn':
await commentRef.delete();
await db.collection('user_warnings').doc(`${comment.userId}_${Date.now()}`).set({
userId: comment.userId,
warnedAt: admin.firestore.FieldValue.serverTimestamp(),
reason: analysis.flags.join(', '),
commentId,
action: 'comment_deleted'
});
logSys('SENTINEL_MK2', `⚠️ Warned user ${maskUid(comment.userId)}`, 'warning');
break;
case 'flag_review':
await commentRef.update({ flagged: true, flagReason: analysis.flags.join(', ') });
logSys('SENTINEL_MK2', `🚩 Flagged comment for review`, 'warning');
break;
}
await db.collection('security_logs').add({
action: analysis.action,
userId: comment.userId,
commentId,
analysis,
timestamp: admin.firestore.FieldValue.serverTimestamp()
});
}
async updateUserReputation(userId, analysis) {
let reputation = this.userReputation.get(userId) || 0;
switch (analysis.action) {
case 'delete_ban': reputation -= 10; break;
case 'delete_warn': reputation -= 3; break;
case 'flag_review': reputation -= 1; break;
default: reputation += 1;
}
this.userReputation.set(userId, reputation);
if (reputation < -5) {
await db.collection('user_reputation').doc(userId).set({
userId,
score: reputation,
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
}
}
}
// 7. JANITOR MK2 WITH ROYAL IMMUNITY
class JanitorMK2 {
constructor() {
this.messageRetentionDays = 30;
}
async cleanOldMessages() {
const cutoff = new Date(Date.now() - this.messageRetentionDays * 24 * 60 * 60 * 1000);
try {
const snapshot = await db.collection('titan_news')
.where('timestamp', '<', cutoff)
.get();
if (!snapshot.empty) {
const batch = db.batch();
snapshot.docs.forEach(doc => {
const data = doc.data();
// 👑 ROYAL IMMUNITY: Skip deletion if message is from royal family
if (data.userId && ROYAL_FAMILY.includes(data.userId)) {
logSys('JANITOR_MK2', `👑 Preserving royal message from ${maskUid(data.userId)}`, 'royal');
return;
}
batch.delete(doc.ref);
});
await batch.commit();
logSys('JANITOR_MK2', `🧹 Deleted ${snapshot.size} old messages (older than ${this.messageRetentionDays} days)`, 'info');
}
} catch (error) {
logSys('JANITOR_MK2', `❌ Cleanup error: ${error.message}`, 'error');
}
}
}
// 8. 🕰️ TIME-AWARE AI CONTENT GENERATOR (v14.0 Engine)
/**
* Calculates the exact Gregorian date string (long format).
*/
function getGregorianDateString(date) {
return date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
/**
* Calculates the exact Hijri date string using the Umm al-Qura calendar
* via the native Intl API — no external libraries required.
*/
function getHijriDateString(date) {
try {
return new Intl.DateTimeFormat('en-US-u-ca-islamic-umalqura', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(date);
} catch (e) {
// Fallback if the locale is not supported in this Node.js build
return 'Hijri date unavailable';
}
}
/**
* 🧩 DAILY RIDDLE GENERATOR (Lightweight & Interactive)
* v14.1 — Anti-duplicate system: tracks last 30 riddles in Firestore
*/
async function generateRiddleContent() {
try {
logSys('TIME_AWARE_AI', '🧩 Generating daily riddle...', 'ai');
const today = new Date();
const gregorianDate = getGregorianDateString(today);
const hijriDate = getHijriDateString(today);
// ✅ FIX: Fetch last 30 riddles to avoid repetition
let recentRiddles = [];
try {
const recentSnap = await db.collection('riddle_history')
.orderBy('timestamp', 'desc')
.limit(30)
.get();
recentRiddles = recentSnap.docs.map(d => d.data().riddleEn || '');
logSys('TIME_AWARE_AI', `📚 Loaded ${recentRiddles.length} past riddles for dedup check`, 'info');
} catch (e) {
logSys('TIME_AWARE_AI', '⚠️ Could not load riddle history (continuing anyway)', 'warning');
}
const avoidSection = recentRiddles.length > 0
? `\n- DO NOT repeat or closely resemble any of these recent riddles:\n${recentRiddles.map((r, i) => ` ${i + 1}. "${r}"`).join('\n')}`
: '';
const categories = ['nature', 'technology', 'food', 'animals', 'everyday objects', 'science', 'history', 'mathematics', 'human body', 'space'];
const category = categories[Math.floor(Math.random() * categories.length)];
const prompt = `You are Titan Nexus AI. Create ONE original, clever riddle about "${category}".
Rules:
- Must be genuinely clever and creative — not a common or well-known riddle
- Suitable for all ages
- Include the answer clearly at the end after " — answer: "
- Avoid ANY special characters that break JSON (no quotes, apostrophes inside strings)
- Date context: ${gregorianDate} / ${hijriDate}${avoidSection}
Reply with ONLY valid JSON, nothing else:
{"riddle":{"en":"[Write a creative riddle about ${category}] — answer: [the answer]","ar":"[اكتب فزورة مبتكرة عن ${category}] — الإجابة: [الجواب]"}}`.trim();
const result = await geminiModel.generateContent(prompt);
const response = await result.response;
let text = response.text().trim();
// Aggressively clean the response
text = text
.replace(/^```json\s*/i, '')
.replace(/^```\s*/i, '')
.replace(/```\s*$/i, '')
// Normalize Arabic/smart quotes to standard ASCII quotes
.replace(/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g, '"')
.replace(/[\u2018\u2019\u201A\u201B\u2032\u2035]/g, "'")
.trim();
// Find the outermost JSON object
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start === -1 || end === -1) throw new Error(`No JSON object found in: ${text.substring(0, 120)}`);
let jsonStr = text.slice(start, end + 1).replace(/[\x00-\x1F\x7F]/g, ' ');
let content;
try {
content = JSON.parse(jsonStr);
} catch (pe) {
const enM = jsonStr.match(/"en"\s*:\s*"((?:[^"\\]|\\.)*)"/);
const arM = jsonStr.match(/"ar"\s*:\s*"((?:[^"\\]|\\.)*)"/);
if (enM && arM) content = { riddle: { en: enM[1], ar: arM[1] } };
else throw new Error(`JSON parse failed: ${pe.message}`);
}
if (!content.riddle || !content.riddle.en || !content.riddle.ar) {
throw new Error('Invalid riddle format from AI');
}
// ✅ FIX: Save new riddle to history to prevent future duplicates
try {
await db.collection('riddle_history').add({
riddleEn: content.riddle.en,
riddleAr: content.riddle.ar,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
gregorianDate,
hijriDate
});
logSys('TIME_AWARE_AI', `💾 Riddle saved to history`, 'info');
} catch (e) {
logSys('TIME_AWARE_AI', '⚠️ Could not save riddle to history', 'warning');
}
logSys('TIME_AWARE_AI', `✅ Riddle generated successfully.`, 'success');
return content;
} catch (error) {
logSys('TIME_AWARE_AI', `❌ Riddle generation failed: ${error.message}`, 'error');
// ✅ FIX: Fallback pool of 10 varied riddles instead of 1 hardcoded
const fallbackRiddles = [
{ en: "I have cities, but no houses. Mountains, but no trees. Water, but no fish. What am I? (A map)", ar: "لدي مدن بلا منازل، وجبال بلا أشجار، ومياه بلا أسماك. من أنا؟ (الخريطة)" },
{ en: "The more you take, the more you leave behind. What am I? (Footsteps)", ar: "كلما أخذت مني ازددت. ما أنا؟ (الخطوات)" },
{ en: "I speak without a mouth and hear without ears. I have no body, but I come alive with wind. What am I? (An echo)", ar: "أتكلم بلا فم وأسمع بلا أذنين. ما أنا؟ (الصدى)" },
{ en: "I can fly without wings. I can cry without eyes. Wherever I go, darkness flies. What am I? (A cloud)", ar: "أطير بلا أجنحة وأبكي بلا عيون. ما أنا؟ (السحابة)" },
{ en: "I run, but I have no legs. I have a mouth, but I never talk. What am I? (A river)", ar: "أجري ولا أملك قدمين، ولدي فم لكنني لا أتكلم. ما أنا؟ (النهر)" },
{ en: "What has hands but cannot clap? (A clock)", ar: "ما الذي له يدان لكن لا يصفق؟ (الساعة)" },
{ en: "I get shorter as I grow older. What am I? (A candle)", ar: "أصير أقصر كلما كبرت. ما أنا؟ (الشمعة)" },
{ en: "I have a neck but no head, and I wear a cap. What am I? (A bottle)", ar: "لدي عنق بلا رأس وأرتدي غطاءً. ما أنا؟ (الزجاجة)" },
{ en: "What can you catch but not throw? (A cold)", ar: "ماذا يمكنك أن تلتقط لكن لا ترمي؟ (نزلة البرد)" },
{ en: "I have teeth but cannot eat. What am I? (A comb)", ar: "لدي أسنان لكنني لا آكل. ما أنا؟ (المشط)" }
];
const pick = fallbackRiddles[Math.floor(Math.random() * fallbackRiddles.length)];
return { riddle: pick };
}
}
// 9. ROYAL REPORT GENERATOR
async function generateRoyalReport(stats) {
try {
logSys('GEMINI_AI', '👑 Generating royal report...', 'ai');
const prompt = `You are generating a professional daily report for the administrators of Titan Nexus platform.
Server Statistics:
- Uptime: ${stats.uptime}
- Memory Usage: ${stats.memory.percentage}
- Online Users: ${stats.connections.registered}
- Total Connections: ${stats.connections.total}
- Platform: ${stats.platform}
- Sentinel Status: ${stats.sentinel}
Generate a concise, professional summary report.
Respond ONLY with valid JSON — no markdown, no backticks, no extra text.
Required format:
{"en":"English summary (2-3 sentences, professional tone)","ar":"ملخص بالعربية"}
Make it sound like a human executive summary, not a robot report.`;
const result = await geminiModel.generateContent(prompt);
const response = await result.response;
let text = response.text().trim();
text = text
.replace(/```json\s*/gi, '').replace(/```\s*/gi, '')
.replace(/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g, '"')
.replace(/[\u2018\u2019\u201A\u201B\u2032\u2035]/g, "'")
.trim();
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start === -1 || end === -1) throw new Error('No JSON object found in AI response');
let rJson = text.slice(start, end + 1).replace(/[\x00-\x1F\x7F]/g, ' ');
let report;
try { report = JSON.parse(rJson); }
catch (pe) {
const enM = rJson.match(/"en"\s*:\s*"((?:[^"\\]|\\.)*)"/);
const arM = rJson.match(/"ar"\s*:\s*"((?:[^"\\]|\\.)*)"/);
if (enM && arM) report = { en: enM[1], ar: arM[1] };
else throw new Error(`Parse failed: ${pe.message}`);
}
if (!report.en || !report.ar) throw new Error('Missing en/ar fields in report');
logSys('GEMINI_AI', '✅ Royal report generated successfully', 'success');
return report;
} catch (error) {
logSys('GEMINI_AI', `❌ Failed to generate royal report: ${error.message}`, 'error');
return {
en: `Daily Report: Server uptime ${stats.uptime}. ${stats.connections.registered} users online. All systems operational.`,
ar: `تقرير يومي: وقت تشغيل الخادم ${stats.uptime}. ${stats.connections.registered} مستخدم متصل. جميع الأنظمة تعمل.`
};
}
}
// ==========================================
// 📈 MARKET INTELLIGENCE ENGINE v2 (Split Requests)
// Request 1: Gold + FX | Request 2: Stocks + Oil + BTC
// ==========================================
async function groqFetch(userPrompt, maxTokens = 150) {
const body = JSON.stringify({
model: GROQ_MARKET_MODEL,
messages: [{ role: "user", content: userPrompt }],
temperature: 0.1,
max_tokens: maxTokens
});
return new Promise((resolve, reject) => {
const url = new URL(GROQ_API_URL);
const req = https.request({
hostname: url.hostname, path: url.pathname, method: 'POST',
headers: {
'Authorization': `Bearer ${GROQ_API_KEY}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
}, (res) => {
let d = '';
res.on('data', c => d += c);
res.on('end', () => res.statusCode >= 200 && res.statusCode < 300
? resolve(d)
: reject(new Error(`${res.statusCode}: ${d.slice(0, 200)}`)));
});
req.setTimeout(40000, () => { req.destroy(); reject(new Error('Timeout')); });
req.on('error', reject);
req.write(body); req.end();
});
}
function parseJSON(raw) {
const parsed = JSON.parse(raw);
let text = (parsed.choices?.[0]?.message?.content || '')
.replace(/```json\s*/gi, '').replace(/```\s*/gi, '')
.replace(/[\u201C\u201D\u00AB\u00BB]/g, '"')
.replace(/[\x00-\x1F\x7F]/g, ' ').trim();
const s = text.indexOf('{'), e = text.lastIndexOf('}');
if (s === -1 || e === -1) throw new Error('No JSON');
return JSON.parse(text.slice(s, e + 1));
}
async function fetchMarketData() {
logSys('MARKET_AI', '📡 Fetching market data...', 'ai');
const ozToGram = 0.0321507;
let goldUsdOz = 0, usdEgp = 50.9, eurEgp = 55.0, gbpEgp = 64.0, sarEgp = 13.6;
let stocks = {};
// ── Step 1: Exchange rates ──
try {
const raw = await new Promise((resolve, reject) => {
const req = https.request({ hostname: 'open.er-api.com', path: '/v6/latest/USD', method: 'GET' }, (res) => {
let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
});
req.setTimeout(8000, () => { req.destroy(); reject(new Error('Timeout')); });
req.on('error', reject); req.end();
});
const fx = JSON.parse(raw);
if (fx.rates?.EGP) {
usdEgp = fx.rates.EGP;
eurEgp = usdEgp / fx.rates.EUR;
gbpEgp = usdEgp / fx.rates.GBP;
sarEgp = usdEgp / fx.rates.SAR;
logSys('MARKET_AI', `✅ FX: 1 USD = ${usdEgp.toFixed(2)} EGP`, 'success');
}
} catch (e) { logSys('MARKET_AI', `⚠️ FX: ${e.message.slice(0,50)}`, 'warning'); }
// ── Step 2: Gold spot — try multiple free APIs ──
const goldAPIs = [
{ host: 'api.gold-api.com', path: '/price/XAU' },
{ host: 'api.metals.live', path: '/v1/spot/gold' },
{ host: 'data.fixer.io', path: '/api/latest?access_key=free&symbols=XAU,USD' },
];
for (const api of goldAPIs) {
if (goldUsdOz > 0) break;
try {
const raw = await new Promise((resolve, reject) => {
const req = https.request({ hostname: api.host, path: api.path, method: 'GET' }, (res) => {
let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
});
req.setTimeout(7000, () => { req.destroy(); reject(new Error('Timeout')); });
req.on('error', reject); req.end();
});
const g = JSON.parse(raw);
// Handle different API response formats
const candidate = parseFloat(
g.price || g.gold || g.XAU ||
(g.rates?.XAU ? 1 / g.rates.XAU : 0) ||
(Array.isArray(g) ? (g[0]?.price || Object.values(g[0])[0]) : 0)
) || 0;
if (candidate > 1000 && candidate < 10000) { // sanity check: gold is between $1000-$10000/oz
goldUsdOz = candidate;
logSys('MARKET_AI', `✅ Gold spot: $${goldUsdOz.toFixed(2)}/oz (${api.host})`, 'success');
}
} catch (e) { logSys('MARKET_AI', `⚠️ Gold API ${api.host}: ${e.message.slice(0,40)}`, 'warning'); }
}
// ── Step 3: Stocks via Groq llama (no web search = no 413) ──
try {
const body = JSON.stringify({
model: 'llama-3.3-70b-versatile',
messages: [{ role: 'user', content: 'Give approximate current market values: S&P500 index, NASDAQ composite, Dow Jones, EGX30 Egypt stock exchange, Bitcoin USD price, Brent crude oil USD/barrel. Reply JSON only: {"sp":"","nq":"","dj":"","eg":"","bt":"","br":""}' }],
temperature: 0.1, max_tokens: 100
});
const raw = await new Promise((resolve, reject) => {
const url = new URL(GROQ_API_URL);
const req = https.request({
hostname: url.hostname, path: url.pathname, method: 'POST',
headers: { 'Authorization': `Bearer ${GROQ_API_KEY}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
}, (res) => {
let d = ''; res.on('data', c => d += c);
res.on('end', () => res.statusCode < 300 ? resolve(d) : reject(new Error(`${res.statusCode}: ${d.slice(0,100)}`)));
});
req.setTimeout(15000, () => { req.destroy(); reject(new Error('Timeout')); });
req.on('error', reject); req.write(body); req.end();
});
stocks = parseJSON(raw);
logSys('MARKET_AI', `✅ Stocks: SP500=${stocks.sp} BTC=${stocks.bt}`, 'success');
} catch (e) { logSys('MARKET_AI', `⚠️ Stocks: ${e.message.slice(0,80)}`, 'warning'); }
// ── Calculate gold prices ──
// Egypt local market: ~6-8% premium over international price
const localPremium = 1.07;
const g24Egp = goldUsdOz ? Math.round(goldUsdOz * usdEgp * ozToGram * localPremium) : 0;
const g21Egp = g24Egp ? Math.round(g24Egp * 21 / 24) : 0;
const g18Egp = g24Egp ? Math.round(g24Egp * 18 / 24) : 0;
const g21Sar = goldUsdOz ? Math.round(goldUsdOz * 3.75 * ozToGram * 21 / 24) : 0;
const g24Aed = goldUsdOz ? Math.round(goldUsdOz * 3.674 * ozToGram) : 0;
return {
gold: {
egypt_21k: g21Egp ? g21Egp.toString() : 'N/A',
egypt_24k: g24Egp ? g24Egp.toString() : 'N/A',
egypt_18k: g18Egp ? g18Egp.toString() : 'N/A',
global_usd_oz: goldUsdOz ? goldUsdOz.toFixed(2) : 'N/A',
saudi_21k: g21Sar ? g21Sar.toString() : 'N/A',
uae_24k: g24Aed ? g24Aed.toString() : 'N/A',
trend: 'stable', change_pct: 'N/A'
},
markets: {
sp500: { v: stocks.sp || 'N/A', c: 'N/A', t: '➡️' },
nasdaq: { v: stocks.nq || 'N/A', c: 'N/A', t: '➡️' },
dow: { v: stocks.dj || 'N/A', c: 'N/A', t: '➡️' },
egx30: { v: stocks.eg || 'N/A', c: 'N/A', t: '➡️' },
brent: { v: stocks.br || 'N/A', c: 'N/A', t: '➡️' },
btc: { v: stocks.bt || 'N/A', c: 'N/A', t: '➡️' }
},
fx: {
usd_egp: usdEgp.toFixed(2),
eur_egp: eurEgp.toFixed(2),
gbp_egp: gbpEgp.toFixed(2),
sar_egp: sarEgp.toFixed(2)
},
summary: { en: 'Market data updated.', ar: 'تم تحديث بيانات السوق.' }
};
}
// ==========================================
// 🕌 PRAYER TIMES NOTIFICATION (Cairo, Egypt)
// ==========================================
async function broadcastPrayerTimes() {
try {
const today = new Date();
const day = today.getDate(), month = today.getMonth() + 1, year = today.getFullYear();
// Aladhan API - free, no key needed
const prayerData = await new Promise((resolve, reject) => {
const req = https.request({
hostname: 'api.aladhan.com',
path: `/v1/timingsByCity/${day}-${month}-${year}?city=Cairo&country=Egypt&method=5`,
method: 'GET'
}, (res) => {
let d = '';
res.on('data', c => d += c);
res.on('end', () => resolve(JSON.parse(d)));
});
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
req.on('error', reject);
req.end();
});
const t = prayerData.data?.timings;
if (!t) throw new Error('No timings data');
const dateStr = getGregorianDateString(today);
const hijriStr = getHijriDateString(today);
const body = `🕌 مواقيت الصلاة — القاهرة
📅 ${dateStr} | 🌙 ${hijriStr}
━━━━━━━━━━━━━━━━━━━━
🌅 الفجر: ${t.Fajr}
🌄 الشروق: ${t.Sunrise}
☀️ الظهر: ${t.Dhuhr}
🌤️ العصر: ${t.Asr}
🌆 المغرب: ${t.Maghrib}
🌙 العشاء: ${t.Isha}
━━━━━━━━━━━━━━━━━━━━
تقبل الله طاعتكم 🤲`;
await db.collection('notifications').add({
title: `🕌 مواقيت الصلاة — ${dateStr}`,
body, type: 'PRAYER_TIMES', userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(), read: false
});
io.emit('prayer_times', { title: `🕌 مواقيت الصلاة`, body, timings: t });
logSys('PRAYER', '✅ Prayer times broadcast sent', 'success');
} catch (e) {
logSys('PRAYER', `❌ Prayer times failed: ${e.message}`, 'error');
}
}
// ==========================================
// 💡 DAILY QUOTE / MOTIVATION (Gemini)
// ==========================================
async function broadcastDailyQuote() {
try {
const today = new Date();
const dateStr = getGregorianDateString(today);
const hijriStr = getHijriDateString(today);
const hour = today.getHours();
const period = hour < 12 ? 'صباحية' : 'مسائية';
const result = await geminiModel.generateContent(
`اكتب تحية ${period} جميلة وحكمة إسلامية ملهمة.
أجب بـ JSON فقط (بدون أي نص خارجه):
{"g":"[اكتب التحية هنا]","q":"[اكتب الحكمة هنا]","s":"[اكتب المصدر هنا]"}`
);
let text = result.response.text().trim()
.replace(/```json\s*/gi, '').replace(/```\s*/gi, '')
.replace(/[\u201C\u201D\u00AB\u00BB]/g, '"')
.replace(/[\x00-\x1F\x7F]/g, ' ');
const s = text.indexOf('{'), e = text.lastIndexOf('}');
if (s === -1) throw new Error('No JSON');
let q;
try {
q = JSON.parse(text.slice(s, e + 1));
} catch (pe) {
// Regex fallback
const gM = text.match(/"g"\s*:\s*"((?:[^"\\]|\\.)*)"/);
const qM = text.match(/"q"\s*:\s*"((?:[^"\\]|\\.)*)"/);
const sM = text.match(/"s"\s*:\s*"((?:[^"\\]|\\.)*)"/);
q = { g: gM?.[1] || '', q: qM?.[1] || '', s: sM?.[1] || '' };
}
const icon = hour < 12 ? '🌅' : '🌆';
const title = hour < 12 ? '🌅 صباح الخير' : '🌆 مساء الخير';
const body = `${icon} ${q.g || ''}\n\n✨ "${q.q || ''}"\n— ${q.s || ''}\n\n📅 ${dateStr} | 🌙 ${hijriStr}`;
await db.collection('notifications').add({
title, body, type: 'DAILY_QUOTE', userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(), read: false
});
io.emit('daily_quote', { title, body });
logSys('QUOTE', '✅ Daily quote broadcast sent', 'success');
} catch (e) {
logSys('QUOTE', `❌ Daily quote failed: ${e.message}`, 'error');
}
}
// ==========================================
// 📰 ARABIC NEWS SUMMARY (Groq)
// ==========================================
async function broadcastArabicNews() {
try {
const dateStr = getGregorianDateString(new Date());
// Very short prompt to avoid 413
const raw = await groqFetch(
`Top 3 Arab world news today. JSON: {"n":[{"t":"headline","s":"summary"},{"t":"","s":""},{"t":"","s":""}]}`,
250
);
const parsed = parseJSON(raw);
const news = parsed.n || parsed.news || [];
if (!news.length) throw new Error('No news items');
const icons = ['1️⃣','2️⃣','3️⃣'];
const body = `📰 أبرز الأخبار — ${dateStr}\n━━━━━━━━━━━━━━━━━━━━\n` +
news.slice(0, 3).map((n, i) => `${icons[i]} ${n.t || n.title || ''}\n ${n.s || n.summary || ''}`).join('\n\n') +
'\n━━━━━━━━━━━━━━━━━━━━\nTitan Nexus News';
await db.collection('notifications').add({
title: `📰 أخبار اليوم — ${dateStr}`,
body, type: 'NEWS_SUMMARY', userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(), read: false
});
io.emit('news_update', { title: `📰 أخبار اليوم`, body });
logSys('NEWS', '✅ Arabic news broadcast sent', 'success');
} catch (e) {
logSys('NEWS', `❌ News broadcast failed: ${e.message}`, 'error');
}
}
function formatMarketNotification(data, dateStr, hijriStr, timeStr) {
const g = data.gold || {};
const m = data.markets || {};
const c = data.fx || {};
const ti = (t) => t === 'up' ? '📈' : t === 'down' ? '📉' : '➡️';
const mRow = (label, obj) => `${label}: ${obj?.v || 'N/A'}`;
const bodyEn = `
┌─────────────────────────────┐
│ 📈 TITAN NEXUS MARKET REPORT │
└─────────────────────────────┘
📅 ${dateStr}
🌙 ${hijriStr}
${timeStr}
🥇 ══ GOLD PRICES ══
🇪🇬 Egypt 21K › ${g.egypt_21k || 'N/A'} EGP/g
🇪🇬 Egypt 24K › ${g.egypt_24k || 'N/A'} EGP/g
🇪🇬 Egypt 18K › ${g.egypt_18k || 'N/A'} EGP/g
🌍 Global › $${g.global_usd_oz || 'N/A'} USD/oz
🇸🇦 Saudi 21K › ${g.saudi_21k || 'N/A'} SAR/g
🇦🇪 UAE 24K › ${g.uae_24k || 'N/A'} AED/g
📊 ══ GLOBAL MARKETS ══
🇺🇸 S&P 500 › ${m.sp500?.v || 'N/A'}
🇺🇸 NASDAQ › ${m.nasdaq?.v || 'N/A'}
🇺🇸 Dow Jones › ${m.dow?.v || 'N/A'}
🇪🇬 EGX 30 › ${m.egx30?.v || 'N/A'}
🛢️ Brent Oil › ${m.brent?.v || 'N/A'} USD/bbl
₿ Bitcoin › $${m.btc?.v || 'N/A'}
💱 ══ EXCHANGE RATES ══
💵 USD › ${c.usd_egp || 'N/A'} EGP
💶 EUR › ${c.eur_egp || 'N/A'} EGP
💷 GBP › ${c.gbp_egp || 'N/A'} EGP
🇸🇦 SAR › ${c.sar_egp || 'N/A'} EGP
⚡ Titan Nexus Market Intelligence
`.trim();
const bodyAr = `
┌─────────────────────────────┐
│ 📈 تقرير السوق اليومي │
└─────────────────────────────┘
📅 ${dateStr}
🌙 ${hijriStr}
${timeStr}
🥇 ══ أسعار الذهب ══
🇪🇬 مصر عيار 21 › ${g.egypt_21k || 'N/A'} جنيه/جرام
🇪🇬 مصر عيار 24 › ${g.egypt_24k || 'N/A'} جنيه/جرام
🇪🇬 مصر عيار 18 › ${g.egypt_18k || 'N/A'} جنيه/جرام
🌍 عالمي › $${g.global_usd_oz || 'N/A'} دولار/أوقية
🇸🇦 السعودية 21 › ${g.saudi_21k || 'N/A'} ريال/جرام
🇦🇪 الإمارات 24 › ${g.uae_24k || 'N/A'} درهم/جرام
📊 ══ الأسواق العالمية ══
🇺🇸 S&P 500 › ${m.sp500?.v || 'N/A'}
🇺🇸 NASDAQ › ${m.nasdaq?.v || 'N/A'}
🇺🇸 داو جونز › ${m.dow?.v || 'N/A'}
🇪🇬 EGX 30 › ${m.egx30?.v || 'N/A'}
🛢️ برنت › ${m.brent?.v || 'N/A'} دولار/برميل
₿ بيتكوين › $${m.btc?.v || 'N/A'}
💱 ══ أسعار الصرف ══
💵 دولار › ${c.usd_egp || 'N/A'} جنيه
💶 يورو › ${c.eur_egp || 'N/A'} جنيه
💷 إسترليني › ${c.gbp_egp || 'N/A'} جنيه
🇸🇦 ريال › ${c.sar_egp || 'N/A'} جنيه
⚡ Titan Nexus Market Intelligence
`.trim();
return { bodyEn, bodyAr };
}
/**
* Main function: fetch + format + broadcast market notification
*/
async function broadcastMarketUpdate() {
logSys('MARKET_AI', '🌅 Starting morning market broadcast...', 'system');
const today = new Date();
const dateStr = getGregorianDateString(today);
const hijriStr = getHijriDateString(today);
const timeStr = today.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true });
// Retry up to 3 times on failure, respecting 429 retry-after
let data = null;
for (let attempt = 1; attempt <= 3; attempt++) {
data = await fetchMarketData();
if (data) break;
if (attempt < 3) {
// On 429 rate limit, wait longer (30s), otherwise 10s
const waitMs = attempt === 1 ? 30000 : 60000;
logSys('MARKET_AI', `⏳ Waiting ${waitMs/1000}s before retry ${attempt + 1}...`, 'warning');
await new Promise(r => setTimeout(r, waitMs));
}
}
if (!data) {
logSys('MARKET_AI', '❌ All attempts failed. Skipping market broadcast.', 'error');
return;
}
const { bodyEn, bodyAr } = formatMarketNotification(data, dateStr, hijriStr, timeStr);
// Save to Firestore as broadcast notification
await db.collection('notifications').add({
title: `📈 تقرير السوق اليومي — ${dateStr}`,
titleEn: `📈 Daily Market Report — ${dateStr}`,
body: bodyEn,
bodyAr: bodyAr,
type: 'MARKET_REPORT',
userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false,
marketData: data,
meta: {
gregorianDate: dateStr,
hijriDate: hijriStr,
fetchedAt: timeStr,
engine: 'GROQ_COMPOUND_MARKET_v1'
}
});
// Real-time push via Socket.IO to all connected clients
io.emit('market_update', {
title: `📈 Daily Market Report — ${dateStr}`,
bodyEn,
bodyAr,
marketData: data,
timestamp: new Date().toISOString()
});
logSys('MARKET_AI', `✅ Market report broadcast to ${io.sockets.sockets.size} connections`, 'success');
}
// 10. 📅 CRON JOBS (v14.0 — Lightweight Riddle Edition)
function initCronJobs() {
// ⏳ Every 6 hours — Send a riddle only (lightweight, interactive)
cron.schedule('0 */6 * * *', async () => {
logSys('CRON', '⏳ 6-Hour cycle triggered — Generating daily riddle...', 'system');
try {
const content = await generateRiddleContent();
const riddleEn = content.riddle.en;
const riddleAr = content.riddle.ar;
// Save to Firestore notifications collection
await db.collection('notifications').add({
title: `🧩 فزورة Titan Nexus اليومية`,
body: riddleEn,
bodyAr: riddleAr,
type: 'DAILY_RIDDLE',
userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false,
meta: {
gregorianDate: getGregorianDateString(new Date()),
hijriDate: getHijriDateString(new Date()),
engine: 'TIME_AWARE_v14_RIDDLE'
}
});
logSys('CRON', `✅ Daily riddle broadcast sent.`, 'success');
} catch (error) {
logSys('CRON', `❌ 6-Hour riddle broadcast failed: ${error.message}`, 'error');
}
});
// 🌙 Night Royal Report (11:59 PM daily)
cron.schedule('59 23 * * *', async () => {
logSys('CRON', '🌙 Royal report generation initiated', 'system');
try {
const stats = getServerStats();
const report = await generateRoyalReport(stats);
// Send to each royal family member
for (const royalId of ROYAL_FAMILY) {
await db.collection('notifications').add({
title: '👑 Daily Royal Report',
body: report.en,
bodyAr: report.ar,
type: 'ROYAL_REPORT',
userId: royalId,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
stats: stats
});
logSys('CRON', `👑 Royal report sent to ${royalId}`, 'royal');
}
logSys('CRON', '✅ Royal reports sent to all family members', 'success');
} catch (error) {
logSys('CRON', `❌ Royal report failed: ${error.message}`, 'error');
}
});
logSys('CRON', '📅 Automated schedules initialized (6h riddle + 11:59 PM royal report)', 'success');
// 🌅 Morning Market Report — 8:00 AM
cron.schedule('0 8 * * *', async () => {
logSys('CRON', '🌅 Morning market broadcast triggered (08:00)', 'system');
try { await broadcastMarketUpdate(); } catch (e) { logSys('CRON', `❌ Market: ${e.message}`, 'error'); }
});
// 🔔 Afternoon Market Update — 3:00 PM
cron.schedule('0 15 * * *', async () => {
logSys('CRON', '🔔 Afternoon market update triggered (15:00)', 'system');
try { await broadcastMarketUpdate(); } catch (e) { logSys('CRON', `❌ Market: ${e.message}`, 'error'); }
});
// 🕌 Prayer Times — 1:00 AM Cairo time (UTC+2) = 23:00 UTC previous day
// If server runs on UTC: use '0 23 * * *'
// If server runs on Cairo time: use '0 1 * * *'
cron.schedule('0 1 * * *', async () => {
logSys('CRON', '🕌 Prayer times broadcast triggered (01:00 Cairo)', 'system');
try { await broadcastPrayerTimes(); } catch (e) { logSys('CRON', `❌ Prayer: ${e.message}`, 'error'); }
});
// 🌅 Morning Quote — Every day at 7:00 AM
cron.schedule('0 7 * * *', async () => {
logSys('CRON', '💡 Morning quote triggered (07:00)', 'system');
try { await broadcastDailyQuote(); } catch (e) { logSys('CRON', `❌ Quote: ${e.message}`, 'error'); }
});
// 🌆 Evening Quote — Every day at 6:00 PM
cron.schedule('0 18 * * *', async () => {
logSys('CRON', '💡 Evening quote triggered (18:00)', 'system');
try { await broadcastDailyQuote(); } catch (e) { logSys('CRON', `❌ Quote: ${e.message}`, 'error'); }
});
// 📰 Arabic News — Every day at 10:00 AM
cron.schedule('0 10 * * *', async () => {
logSys('CRON', '📰 Arabic news broadcast triggered (10:00)', 'system');
try { await broadcastArabicNews(); } catch (e) { logSys('CRON', `❌ News: ${e.message}`, 'error'); }
});
}
// 🏆 GAME MASTER — Titan Gravity Top Players (Daily at 10:00 PM)
cron.schedule('0 22 * * *', async () => {
logSys('GAME_MASTER', '🏆 Calculating Titan Gravity top players...', 'system');
try {
const snapshot = await db.collection('titan_ghosts')
.orderBy('arcade_points', 'desc')
.limit(3)
.get();
if (snapshot.empty) {
logSys('GAME_MASTER', '⚠️ No players found in rankings.', 'warning');
return;
}
const medals = ['🥇', '🥈', '🥉'];
const titles = ['سيد الجاذبية (المركز الأول)', 'الوصيف (المركز الثاني)', 'المحارب (المركز الثالث)'];
const bonusPoints = [100, 50, 25];
const batch = db.batch();
let rank = 0;
snapshot.forEach(doc => {
const userId = doc.id;
const userData = doc.data();
const currentPoints = userData.arcade_points || 0;
if (currentPoints === 0) return;
const msgEn = `Congratulations Champion! 🥳\nYou achieved rank #${rank + 1} in Titan Gravity with ${currentPoints} points.\nReward: +${bonusPoints[rank]} Bonus Points!`;
const msgAr = `مبروك يا بطل! 🥳\nلقد حققت ${medals[rank]} ${titles[rank]} في لعبة Titan Gravity برصيد ${currentPoints} نقطة.\nالجائزة: +${bonusPoints[rank]} نقطة إضافية!`;
const notifRef = db.collection('notifications').doc();
batch.set(notifRef, {
title: `${medals[rank]} تكريم الأوائل`,
body: msgEn,
bodyAr: msgAr,
type: 'GAME_AWARD',
userId: userId,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false,
rank: rank + 1
});
const userRef = db.collection('titan_ghosts').doc(userId);
batch.update(userRef, {
arcade_points: admin.firestore.FieldValue.increment(bonusPoints[rank])
});
logSys('GAME_MASTER', `🏆 Award sent to Rank #${rank + 1}: ${maskUid(userId)} (${currentPoints} pts)`, 'success');
rank++;
});
await batch.commit();
logSys('GAME_MASTER', '✅ Top 3 players awarded & bonuses added.', 'success');
} catch (error) {
logSys('GAME_MASTER', `❌ Failed to award players: ${error.message}`, 'error');
}
});
// 11. SOCKET.IO REAL-TIME RELAY
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "*", methods: ["GET", "POST"] }
});
const onlineUsers = new Map();
async function sendPushNotification(targetId, payload) {
try {
if (!targetId || !payload) return;
const userDoc = await db.collection('users').doc(targetId).get();
const fcmToken = userDoc.exists ? userDoc.data().fcmToken : null;
if (!fcmToken) {
logSys('PUSH', `No FCM token for user ${targetId}`, 'warning');
return;
}
const message = {
token: fcmToken,
notification: {
title: payload.title || 'New message',
body: payload.body || payload.msg || ''
},
data: payload.data || {}
};
await admin.messaging().send(message);
logSys('PUSH', `📲 Push sent to user ${targetId}`, 'success');
} catch (error) {
logSys('PUSH', `❌ Failed to send push to ${targetId}: ${error.message}`, 'error');
}
}
// 12. HELPER: Get Server Statistics
function getServerStats() {
const uptimeMs = Date.now() - SERVER_START_TIME;
const uptimeHours = Math.floor(uptimeMs / (1000 * 60 * 60));
const uptimeMinutes = Math.floor((uptimeMs % (1000 * 60 * 60)) / (1000 * 60));
const memUsage = process.memoryUsage();
const heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
const heapTotalMB = (memUsage.heapTotal / 1024 / 1024).toFixed(2);
const cpuLoad = os.loadavg();
const totalSocketConnections = io.sockets.sockets.size;
return {
uptime: `${uptimeHours}h ${uptimeMinutes}m`,
uptimeMs,
memory: {
heapUsed: `${heapUsedMB} MB`,
heapTotal: `${heapTotalMB} MB`,
percentage: ((memUsage.heapUsed / memUsage.heapTotal) * 100).toFixed(1) + '%'
},
connections: {
registered: onlineUsers.size,
total: totalSocketConnections
},
cpuLoad: {
'1min': cpuLoad[0].toFixed(2),
'5min': cpuLoad[1].toFixed(2),
'15min': cpuLoad[2].toFixed(2)
},
sentinel: 'Active',
platform: os.platform(),
nodeVersion: process.version
};
}
// 13. FIRESTORE DEAD DROP COMMAND SYSTEM
async function writeBlackboxResponse(message, level = 'SUCCESS', data = null) {
try {
await db.collection('titan_blackbox').add({
type: 'CMD_RES',
message,
level,
data,
ts: admin.firestore.FieldValue.serverTimestamp()
});
logSys('BLACKBOX', `📝 Response written: ${message}`, 'info');
} catch (error) {
logSys('BLACKBOX', `❌ Failed to write response: ${error.message}`, 'error');
}
}
// ==========================================
// 🧹 SWEEPER — Orphaned Data Cleanup Engine
// Paginates all Firebase Auth users, cross-references
// titan_ghosts, then batch-deletes orphaned records
// across 5 collections in safe ≤450-op chunks.
// ==========================================
async function sweepOrphanedData() {
logSys('SWEEPER', '🔍 Starting orphaned data sweep...', 'system');
// ── Step 1: Build the full valid-UID Set from Firebase Auth ──
const validUids = new Set();
let pageToken;
try {
do {
const listResult = await admin.auth().listUsers(1000, pageToken);
listResult.users.forEach(userRecord => validUids.add(userRecord.uid));
pageToken = listResult.pageToken;
} while (pageToken);
logSys('SWEEPER', `✅ Loaded ${validUids.size} valid UIDs from Firebase Auth`, 'info');
} catch (e) {
logSys('SWEEPER', `❌ Failed to list Auth users: ${e.message}`, 'error');
throw new Error(`Auth pagination failed: ${e.message}`);
}
// ── Step 2: Fetch titan_ghosts as the source-of-truth roster ──
let ghostsSnapshot;
try {
ghostsSnapshot = await db.collection('titan_ghosts').get();
logSys('SWEEPER', `📋 Fetched ${ghostsSnapshot.size} docs from titan_ghosts`, 'info');
} catch (e) {
logSys('SWEEPER', `❌ Failed to fetch titan_ghosts: ${e.message}`, 'error');
throw new Error(`Firestore read failed: ${e.message}`);
}
// ── Step 3: Identify orphaned UIDs ──
const orphanedUids = [];
ghostsSnapshot.forEach(doc => {
if (!validUids.has(doc.id)) {
orphanedUids.push(doc.id);
}
});
if (orphanedUids.length === 0) {
logSys('SWEEPER', '✅ No orphaned records found. Database is clean.', 'success');
return {
success: true,
message: 'No orphaned records found. Database is clean.',
orphansFound: 0,
docsDeleted: 0
};
}
logSys('SWEEPER', `⚠️ Found ${orphanedUids.length} orphaned UID(s) — beginning batch delete`, 'warning');
// ── Step 4: Batch-delete across 5 collections with chunk safety ──
// Firestore hard limit: 500 ops/batch. We commit at 450 to stay well clear.
const COLLECTIONS = ['titan_ghosts', 'titan_keys', 'users', 'user_reputation', 'welcome_sent'];
const CHUNK_LIMIT = 450;
let currentBatch = db.batch();
let opsInCurrentBatch = 0;
let totalDocsDeleted = 0;
let batchesCommitted = 0;
try {
for (const uid of orphanedUids) {
for (const collectionName of COLLECTIONS) {
const docRef = db.collection(collectionName).doc(uid);
currentBatch.delete(docRef);
opsInCurrentBatch++;
totalDocsDeleted++;
// Commit and start a fresh batch before hitting the 500-op wall
if (opsInCurrentBatch >= CHUNK_LIMIT) {
await currentBatch.commit();
batchesCommitted++;
logSys('SWEEPER', `📦 Committed batch #${batchesCommitted} (${opsInCurrentBatch} ops)`, 'info');
currentBatch = db.batch();
opsInCurrentBatch = 0;
}
}
}
// Commit any remaining operations in the final partial batch
if (opsInCurrentBatch > 0) {
await currentBatch.commit();
batchesCommitted++;
logSys('SWEEPER', `📦 Committed final batch #${batchesCommitted} (${opsInCurrentBatch} ops)`, 'info');
}
} catch (e) {
logSys('SWEEPER', `❌ Batch delete failed: ${e.message}`, 'error');
throw new Error(`Batch delete failed: ${e.message}`);
}
const report = {
success: true,
message: `Sweep complete. Removed ${orphanedUids.length} orphaned user(s).`,
orphansFound: orphanedUids.length,
docsDeleted: totalDocsDeleted,
batchesCommitted,
collectionsSwept: COLLECTIONS,
orphanedUids: orphanedUids.map(uid => maskUid(uid)) // masked for safe logging
};
logSys('SWEEPER', `✅ Sweep complete — ${orphanedUids.length} orphans, ${totalDocsDeleted} docs deleted across ${batchesCommitted} batch(es)`, 'success');
return report;
}
async function processAdminCommand(docId, commandData) {
try {
const { adminKey, command } = commandData;
if (adminKey !== ADMIN_ID) {
await writeBlackboxResponse('Access denied. Invalid admin credentials.', 'HIGH');
logSys('DEAD_DROP', `❌ Unauthorized access attempt (docId: ${docId})`, 'error');
return;
}
logSys('DEAD_DROP', `🎛️ Processing command: ${command}`, 'system');
const parts = command.trim().split(/\s+/);
const cmd = parts[0].toLowerCase();
const args = parts.slice(1);
switch (cmd) {
case '/stats': {
const stats = getServerStats();
const report = `
╔═══════════════════════════════════════════╗
║ 🎛️ TITAN NEXUS SERVER STATISTICS ║
╚═══════════════════════════════════════════╝
⏱️ UPTIME: ${stats.uptime}
💾 MEMORY USAGE:
• Heap Used: ${stats.memory.heapUsed}
• Heap Total: ${stats.memory.heapTotal}
• Usage: ${stats.memory.percentage}
🔌 CONNECTIONS:
• Registered Users: ${stats.connections.registered}
• Total Sockets: ${stats.connections.total}
⚙️ SYSTEM LOAD (CPU):
• 1 min: ${stats.cpuLoad['1min']}
• 5 min: ${stats.cpuLoad['5min']}
• 15 min: ${stats.cpuLoad['15min']}
🛡️ SECURITY:
• Sentinel MK2: ${stats.sentinel}
👑 ROYAL FAMILY:
• Protected Members: ${ROYAL_FAMILY.length}
🖥️ PLATFORM:
• OS: ${stats.platform}
• Node: ${stats.nodeVersion}
🕰️ TIME-AWARE ENGINE:
• Gregorian: ${getGregorianDateString(new Date())}
• Hijri: ${getHijriDateString(new Date())}
Generated: ${new Date().toISOString()}
`.trim();
await writeBlackboxResponse('Server statistics generated', 'SYSTEM', { report, stats });
logSys('DEAD_DROP', '📊 Stats report generated and written to blackbox', 'success');
break;
}
case '/notify': {
if (args.length < 2) {
await writeBlackboxResponse('Usage: /notify [uid] [message]', 'HIGH');
break;
}
const targetUid = args[0];
const message = args.slice(1).join(' ');
await db.collection('notifications').add({
userId: targetUid,
message,
sentBy: 'ADMIN_SYSTEM',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('admin_notification', {
message,
timestamp: new Date().toISOString()
});
}
await sendPushNotification(targetUid, {
title: 'Admin Notification',
body: message,
data: { type: 'admin_notification' }
});
await writeBlackboxResponse(
`Notification sent to user ${targetUid}`,
'SUCCESS',
{ online: !!targetSocketId, pushSent: true }
);
logSys('DEAD_DROP', `📬 Notification sent to ${maskUid(targetUid)}`, 'success');
break;
}
case '/ban': {
if (args.length < 2) {
await writeBlackboxResponse('Usage: /ban [uid] [reason]', 'HIGH');
break;
}
const targetUid = args[0];
const reason = args.slice(1).join(' ');
// 👑 ROYAL IMMUNITY CHECK
if (ROYAL_FAMILY.includes(targetUid)) {
await writeBlackboxResponse(
`Cannot ban user ${targetUid} - Royal Immunity Shield`,
'HIGH',
{ royalProtection: true }
);
logSys('DEAD_DROP', `👑 Ban attempt blocked - Royal immunity for ${maskUid(targetUid)}`, 'royal');
break;
}
await admin.auth().updateUser(targetUid, { disabled: true });
await admin.auth().revokeRefreshTokens(targetUid);
await db.collection('banned_users').doc(targetUid).set({
userId: targetUid,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
reason,
bannedBy: ADMIN_ID,
method: 'admin_command_deaddrop'
});
await db.collection('users').doc(targetUid).set({
banned: true,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
banReason: reason,
bannedBy: ADMIN_ID
}, { merge: true });
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('force_logout', {
reason: 'Account has been banned',
timestamp: new Date().toISOString()
});
io.sockets.sockets.get(targetSocketId)?.disconnect(true);
onlineUsers.delete(targetUid);
}
await writeBlackboxResponse(
`User ${targetUid} banned successfully`,
'SUCCESS',
{ reason, wasOnline: !!targetSocketId, disconnected: !!targetSocketId }
);
logSys('DEAD_DROP', `🚫 User ${maskUid(targetUid)} banned: ${reason}`, 'warning');
break;
}
case '/clear': {
if (args[0] !== 'logs') {
await writeBlackboxResponse('Usage: /clear logs', 'HIGH');
break;
}
const cutoffDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const snapshot = await db.collection('titan_blackbox')
.where('ts', '<', cutoffDate)
.limit(500)
.get();
if (snapshot.empty) {
await writeBlackboxResponse('No old logs found to clear', 'SYSTEM');
break;
}
const batch = db.batch();
snapshot.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
await writeBlackboxResponse(
`Cleared ${snapshot.size} old log entries`,
'SUCCESS',
{ deleted: snapshot.size, olderThan: '7 days' }
);
logSys('DEAD_DROP', `🗑️ Cleared ${snapshot.size} old logs`, 'info');
break;
}
case '/broadcast': {
if (args.length === 0) {
await writeBlackboxResponse('Usage: /broadcast [message]', 'HIGH');
break;
}
const message = args.join(' ');
await db.collection('notifications').add({
title: '📢 تنبيه عام',
body: message,
bodyAr: message,
type: 'SYSTEM_BROADCAST',
userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
io.emit('receive_direct', {
from: 'SYSTEM',
msg: message,
isSystem: true,
timestamp: new Date().toISOString()
});
await writeBlackboxResponse(
'Broadcast sent to all users & saved to DB',
'SUCCESS',
{ recipients: io.sockets.sockets.size, message }
);
logSys('DEAD_DROP', `📢 Broadcast sent to ${io.sockets.sockets.size} connections`, 'success');
break;
}
case '/sweep-orphans': {
logSys('DEAD_DROP', '🧹 /sweep-orphans triggered via Dead Drop', 'system');
const sweepResult = await sweepOrphanedData();
const sweepReport = `
╔═══════════════════════════════════════════╗
║ 🧹 ORPHAN SWEEP REPORT ║
╚═══════════════════════════════════════════╝
${sweepResult.orphansFound === 0
? '✅ Database is clean — no orphaned records found.'
: `⚠️ Orphaned UIDs found: ${sweepResult.orphansFound}
🗑️ Total docs deleted: ${sweepResult.docsDeleted}
📦 Batches committed: ${sweepResult.batchesCommitted}
📂 Collections swept: ${sweepResult.collectionsSwept.join(', ')}
Orphaned UIDs (masked):
${sweepResult.orphanedUids.map(u => ` • ${u}`).join('\n')}`}
Generated: ${new Date().toISOString()}
`.trim();
await writeBlackboxResponse(sweepResult.message, 'SUCCESS', { ...sweepResult, report: sweepReport });
logSys('DEAD_DROP', `🧹 Sweep report written to blackbox`, 'success');
break;
}
default: {
await writeBlackboxResponse(
`Unknown command: ${cmd}`,
'HIGH',
{ availableCommands: ['/notify', '/ban', '/clear logs', '/broadcast', '/stats', '/sweep-orphans'] }
);
logSys('DEAD_DROP', `❓ Unknown command attempted: ${cmd}`, 'warning');
}
}
} catch (error) {
logSys('DEAD_DROP', `❌ Command error: ${error.message}`, 'error');
await writeBlackboxResponse(
`Command execution failed: ${error.message}`,
'HIGH'
);
}
}
async function initCommandReader() {
logSys('DEAD_DROP', '📡 Firestore Dead Drop Command Reader initialized', 'system');
db.collection('pending_bot_actions')
.where('status', '==', 'pending')
.onSnapshot(async (snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
const docId = change.doc.id;
const data = change.doc.data();
try {
await db.collection('pending_bot_actions').doc(docId).update({
status: 'processing',
processedAt: admin.firestore.FieldValue.serverTimestamp()
});
logSys('DEAD_DROP', `📥 New command received (docId: ${docId})`, 'info');
await processAdminCommand(docId, data);
await db.collection('pending_bot_actions').doc(docId).delete();
logSys('DEAD_DROP', `✅ Command processed and deleted (docId: ${docId})`, 'success');
} catch (error) {
logSys('DEAD_DROP', `❌ Failed to process command ${docId}: ${error.message}`, 'error');
await db.collection('pending_bot_actions').doc(docId).update({
status: 'error',
error: error.message,
errorAt: admin.firestore.FieldValue.serverTimestamp()
});
}
}
});
}, (error) => {
logSys('DEAD_DROP', `⚠️ Listener Error: ${error.message}`, 'error');
});
}
// 14. SOCKET.IO CONNECTION HANDLER
io.on('connection', (socket) => {
logSys('SOCKET', `🔌 New connection: ${socket.id}`, 'cyber');
socket.on('register', (userId) => {
if (!userId) return;
onlineUsers.set(userId, socket.id);
if (ROYAL_FAMILY.includes(userId)) {
logSys('SOCKET', `👑 Royal user ${maskUid(userId)} registered (socket: ${socket.id})`, 'royal');
} else {
logSys('SOCKET', `✅ User ${maskUid(userId)} registered (socket: ${socket.id})`, 'success');
}
});
socket.on('send_direct', async (data) => {
const { to, msg } = data;
if (!to || !msg) return;
const targetSocketId = onlineUsers.get(to);
if (targetSocketId) {
io.to(targetSocketId).emit('receive_direct', {
from: socket.id,
msg,
timestamp: new Date().toISOString()
});
logSys('SOCKET', `📨 Relayed DM to online user ${to}`, 'info');
} else {
logSys('SOCKET', `📲 User ${to} offline, sending push`, 'info');
await sendPushNotification(to, {
title: 'New direct message',
body: typeof msg === 'string' ? msg : JSON.stringify(msg),
data: { sender: socket.id, ...msg }
});
}
});
socket.on('admin_cmd', async (payload) => {
try {
const { adminKey, command } = payload;
if (adminKey !== ADMIN_ID) {
socket.emit('cmd_res', {
success: false,
error: 'Access denied. Invalid admin credentials.',
timestamp: new Date().toISOString()
});
logSys('ADMIN_CMD', `❌ Unauthorized access attempt from socket ${socket.id}`, 'error');
return;
}
logSys('ADMIN_CMD', `🎛️ Command received: ${command}`, 'system');
const parts = command.trim().split(/\s+/);
const cmd = parts[0].toLowerCase();
const args = parts.slice(1);
switch (cmd) {
case '/notify': {
if (args.length < 2) {
socket.emit('cmd_res', {
success: false,
error: 'Usage: /notify [uid] [message]'
});
break;
}
const targetUid = args[0];
const message = args.slice(1).join(' ');
await db.collection('notifications').add({
userId: targetUid,
message,
sentBy: 'ADMIN_SYSTEM',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('admin_notification', {
message,
timestamp: new Date().toISOString()
});
}
await sendPushNotification(targetUid, {
title: 'Admin Notification',
body: message,
data: { type: 'admin_notification' }
});
socket.emit('cmd_res', {
success: true,
message: `Notification sent to user ${targetUid}`,
details: {
online: !!targetSocketId,
pushSent: true
}
});
logSys('ADMIN_CMD', `📬 Notification sent to ${maskUid(targetUid)}`, 'success');
break;
}
case '/ban': {
if (args.length < 2) {
socket.emit('cmd_res', {
success: false,
error: 'Usage: /ban [uid] [reason]'
});
break;
}
const targetUid = args[0];
const reason = args.slice(1).join(' ');
// 👑 ROYAL IMMUNITY CHECK
if (ROYAL_FAMILY.includes(targetUid)) {
socket.emit('cmd_res', {
success: false,
error: `Cannot ban user ${targetUid} - Royal Immunity Shield`,
royalProtection: true
});
logSys('ADMIN_CMD', `👑 Ban attempt blocked - Royal immunity for ${maskUid(targetUid)}`, 'royal');
break;
}
await admin.auth().updateUser(targetUid, { disabled: true });
await admin.auth().revokeRefreshTokens(targetUid);
await db.collection('banned_users').doc(targetUid).set({
userId: targetUid,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
reason,
bannedBy: ADMIN_ID,
method: 'admin_command'
});
await db.collection('users').doc(targetUid).set({
banned: true,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
banReason: reason,
bannedBy: ADMIN_ID
}, { merge: true });
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('force_logout', {
reason: 'Account has been banned',
timestamp: new Date().toISOString()
});
io.sockets.sockets.get(targetSocketId)?.disconnect(true);
onlineUsers.delete(targetUid);
}
socket.emit('cmd_res', {
success: true,
message: `User ${targetUid} banned successfully`,
details: {
reason,
wasOnline: !!targetSocketId,
disconnected: !!targetSocketId
}
});
logSys('ADMIN_CMD', `🚫 User ${maskUid(targetUid)} banned: ${reason}`, 'warning');
break;
}
case '/clear': {
if (args[0] !== 'logs') {
socket.emit('cmd_res', {
success: false,
error: 'Usage: /clear logs'
});
break;
}
const cutoffDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const snapshot = await db.collection('titan_blackbox')
.where('timestamp', '<', cutoffDate)
.limit(500)
.get();
if (snapshot.empty) {
socket.emit('cmd_res', {
success: true,
message: 'No old logs found to clear'
});
break;
}
const batch = db.batch();
snapshot.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
socket.emit('cmd_res', {
success: true,
message: `Cleared ${snapshot.size} old log entries`,
details: {
deleted: snapshot.size,
olderThan: '7 days'
}
});
logSys('ADMIN_CMD', `🗑️ Cleared ${snapshot.size} old logs`, 'info');
break;
}
case '/broadcast': {
if (args.length === 0) {
socket.emit('cmd_res', {
success: false,
error: 'Usage: /broadcast [message]'
});
break;
}
const message = args.join(' ');
io.emit('receive_direct', {
from: 'SYSTEM',
msg: message,
isSystem: true,
timestamp: new Date().toISOString()
});
socket.emit('cmd_res', {
success: true,
message: 'Broadcast sent to all users',
details: {
recipients: io.sockets.sockets.size,
message
}
});
logSys('ADMIN_CMD', `📢 Broadcast sent to ${io.sockets.sockets.size} connections`, 'success');
break;
}
case '/stats': {
const stats = getServerStats();
const report = `
╔═══════════════════════════════════════════╗
║ 🎛️ TITAN NEXUS SERVER STATISTICS ║
╚═══════════════════════════════════════════╝
⏱️ UPTIME: ${stats.uptime}
💾 MEMORY USAGE:
• Heap Used: ${stats.memory.heapUsed}
• Heap Total: ${stats.memory.heapTotal}
• Usage: ${stats.memory.percentage}
🔌 CONNECTIONS:
• Registered Users: ${stats.connections.registered}
• Total Sockets: ${stats.connections.total}
⚙️ SYSTEM LOAD (CPU):
• 1 min: ${stats.cpuLoad['1min']}
• 5 min: ${stats.cpuLoad['5min']}
• 15 min: ${stats.cpuLoad['15min']}
🛡️ SECURITY:
• Sentinel MK2: ${stats.sentinel}
👑 ROYAL FAMILY:
• Protected Members: ${ROYAL_FAMILY.length}
🕰️ TIME-AWARE ENGINE:
• Gregorian: ${getGregorianDateString(new Date())}
• Hijri: ${getHijriDateString(new Date())}
🖥️ PLATFORM:
• OS: ${stats.platform}
• Node: ${stats.nodeVersion}
Generated: ${new Date().toISOString()}
`.trim();
socket.emit('cmd_res', {
success: true,
message: 'Server statistics',
report,
data: stats
});
logSys('ADMIN_CMD', '📊 Stats report generated', 'info');
break;
}
case '/sweep-orphans': {
logSys('ADMIN_CMD', '🧹 /sweep-orphans triggered via Socket.IO', 'system');
const sweepResult = await sweepOrphanedData();
socket.emit('cmd_res', {
success: true,
message: sweepResult.message,
details: sweepResult
});
logSys('ADMIN_CMD', `🧹 Sweep complete — ${sweepResult.orphansFound} orphans, ${sweepResult.docsDeleted} docs deleted`, 'success');
break;
}
default: {
socket.emit('cmd_res', {
success: false,
error: `Unknown command: ${cmd}`,
availableCommands: ['/notify', '/ban', '/clear logs', '/broadcast', '/stats', '/sweep-orphans']
});
logSys('ADMIN_CMD', `❓ Unknown command attempted: ${cmd}`, 'warning');
}
}
} catch (error) {
logSys('ADMIN_CMD', `❌ Command error: ${error.message}`, 'error');
socket.emit('cmd_res', {
success: false,
error: `Command execution failed: ${error.message}`,
timestamp: new Date().toISOString()
});
}
});
socket.on('disconnect', () => {
let disconnectedUserId = null;
for (const [userId, sockId] of onlineUsers.entries()) {
if (sockId === socket.id) {
disconnectedUserId = userId;
onlineUsers.delete(userId);
break;
}
}
if (disconnectedUserId) {
const logType = ROYAL_FAMILY.includes(disconnectedUserId) ? 'royal' : 'warning';
const icon = ROYAL_FAMILY.includes(disconnectedUserId) ? '👑' : '🔴';
logSys('SOCKET', `${icon} User ${maskUid(disconnectedUserId)} disconnected`, logType);
} else {
logSys('SOCKET', `🔴 Socket ${socket.id} disconnected (unregistered)`, 'info');
}
});
});
// 📌 ADMIN DASHBOARD (BUILT-IN)
app.use(express.static('public')); // لخدمة أي ملفات إضافية (اختياري)
app.get('/admin', (req, res) => {
res.sendFile(__dirname + '/admin.html');
});
// 15. HEALTH & STATUS ENDPOINTS
app.get('/', (req, res) => {
const stats = getServerStats();
res.json({
service: 'TITAN NEXUS – Time-Aware Royal AI Edition',
status: 'operational',
version: '14.0',
security: 'Sentinel MK2 active',
realtime: 'Socket.io relay active',
adminCenter: 'Dead Drop Command System active',
ai: 'Gemini AI + Time-Aware Engine active',
royalFamily: ROYAL_FAMILY.length,
onlineUsers: onlineUsers.size,
uptime: stats.uptime,
memory: stats.memory.percentage,
timeAware: {
gregorian: getGregorianDateString(new Date()),
hijri: getHijriDateString(new Date())
}
});
});
app.get('/ping', (req, res) => res.send('pong'));
app.get('/stats', (req, res) => {
const stats = getServerStats();
res.json({
...stats,
timeAware: {
gregorian: getGregorianDateString(new Date()),
hijri: getHijriDateString(new Date())
}
});
});
// 🔍 AUTH DEBUG — checks if a key matches ADMIN_ID (temporary, remove after debugging)
app.post('/admin/check-auth', (req, res) => {
const { adminKey } = req.body;
if (!adminKey) return res.json({ ok: false, reason: 'no_key' });
const match = adminKey === ADMIN_ID;
const adminLen = ADMIN_ID ? ADMIN_ID.length : 0;
const keyLen = adminKey.length;
res.json({
ok: match,
reason: match ? 'matched' : 'mismatch',
keyLength: keyLen,
adminIdLength: adminLen,
adminIdSet: !!ADMIN_ID,
firstFour: ADMIN_ID ? ADMIN_ID.substring(0, 4) : 'NOT_SET'
});
});
// 🆕 ADMIN COMMAND HTTP ENDPOINT (for web dashboard)
app.post('/api/admin/command', async (req, res) => {
try {
const { adminKey, command } = req.body;
if (!adminKey || !command) {
return res.status(400).json({ success: false, error: 'Missing adminKey or command' });
}
if (adminKey !== ADMIN_ID) {
logSys('HTTP_ADMIN', `❌ Auth failed — key_len=${adminKey.length} admin_id_len=${ADMIN_ID ? ADMIN_ID.length : 'UNSET'} admin_id_set=${!!ADMIN_ID}`, 'error');
return res.status(403).json({ success: false, error: 'Invalid admin credentials' });
}
logSys('HTTP_ADMIN', `🎛️ HTTP command received: ${command}`, 'system');
const parts = command.trim().split(/\s+/);
const cmd = parts[0].toLowerCase();
const args = parts.slice(1);
// Reuse same command logic as elsewhere
const executeCommand = async () => {
switch (cmd) {
case '/stats': {
const stats = getServerStats();
return {
success: true,
message: 'Server statistics retrieved',
stats,
report: `
Uptime: ${stats.uptime}
Memory: ${stats.memory.percentage}
Online Users: ${stats.connections.registered}
CPU Load: ${stats.cpuLoad['1min']} / ${stats.cpuLoad['5min']} / ${stats.cpuLoad['15min']}
Platform: ${stats.platform}
`.trim()
};
}
case '/notify': {
if (args.length < 2) throw new Error('Usage: /notify [uid] [message]');
const targetUid = args[0];
const message = args.slice(1).join(' ');
await db.collection('notifications').add({
userId: targetUid,
message,
sentBy: 'ADMIN_SYSTEM',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('admin_notification', {
message,
timestamp: new Date().toISOString()
});
}
await sendPushNotification(targetUid, {
title: 'Admin Notification',
body: message,
data: { type: 'admin_notification' }
});
return {
success: true,
message: `Notification sent to user ${targetUid}`,
details: { online: !!targetSocketId, pushSent: true }
};
}
case '/ban': {
if (args.length < 2) throw new Error('Usage: /ban [uid] [reason]');
const targetUid = args[0];
const reason = args.slice(1).join(' ');
if (ROYAL_FAMILY.includes(targetUid)) {
return {
success: false,
error: `Cannot ban user ${targetUid} - Royal Immunity Shield`,
royalProtection: true
};
}
await admin.auth().updateUser(targetUid, { disabled: true });
await admin.auth().revokeRefreshTokens(targetUid);
await db.collection('banned_users').doc(targetUid).set({
userId: targetUid,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
reason,
bannedBy: ADMIN_ID,
method: 'admin_command_http'
});
await db.collection('users').doc(targetUid).set({
banned: true,
bannedAt: admin.firestore.FieldValue.serverTimestamp(),
banReason: reason,
bannedBy: ADMIN_ID
}, { merge: true });
const targetSocketId = onlineUsers.get(targetUid);
if (targetSocketId) {
io.to(targetSocketId).emit('force_logout', {
reason: 'Account has been banned',
timestamp: new Date().toISOString()
});
io.sockets.sockets.get(targetSocketId)?.disconnect(true);
onlineUsers.delete(targetUid);
}
return {
success: true,
message: `User ${targetUid} banned successfully`,
details: { reason, wasOnline: !!targetSocketId }
};
}
case '/clear': {
if (args[0] !== 'logs') throw new Error('Usage: /clear logs');
const cutoffDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const snapshot = await db.collection('titan_blackbox')
.where('ts', '<', cutoffDate)
.limit(500)
.get();
if (snapshot.empty) {
return { success: true, message: 'No old logs found to clear' };
}
const batch = db.batch();
snapshot.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
return {
success: true,
message: `Cleared ${snapshot.size} old log entries`,
details: { deleted: snapshot.size, olderThan: '7 days' }
};
}
case '/broadcast': {
if (args.length === 0) throw new Error('Usage: /broadcast [message]');
const message = args.join(' ');
await db.collection('notifications').add({
title: '📢 تنبيه عام',
body: message,
bodyAr: message,
type: 'SYSTEM_BROADCAST',
userId: 'BROADCAST',
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
io.emit('receive_direct', {
from: 'SYSTEM',
msg: message,
isSystem: true,
timestamp: new Date().toISOString()
});
return {
success: true,
message: `Broadcast sent to ${io.sockets.sockets.size} connections`,
details: { recipients: io.sockets.sockets.size }
};
}
case '/sweep-orphans': {
logSys('HTTP_ADMIN', '🧹 /sweep-orphans triggered via HTTP', 'system');
const sweepResult = await sweepOrphanedData();
return {
success: true,
message: sweepResult.message,
details: sweepResult
};
}
default:
throw new Error(`Unknown command: ${cmd}. Available: /stats, /notify, /ban, /clear logs, /broadcast, /sweep-orphans`);
}
};
const result = await executeCommand();
res.json(result);
} catch (error) {
logSys('HTTP_ADMIN', `❌ Command error: ${error.message}`, 'error');
res.status(500).json({
success: false,
error: error.message
});
}
});
// ==========================================
// 🎉 WELCOME MANAGER — Event-Driven via Firestore
// Replaces the former POST /auth/welcome HTTP endpoint.
// Listens on the `pending_welcomes` collection. The frontend
// writes a document there on new-user registration; this
// listener fires, processes the welcome, then deletes the doc
// so the collection stays empty (zero ongoing read cost).
// ==========================================
function initWelcomeManager() {
db.collection('pending_welcomes').onSnapshot(snapshot => {
snapshot.docChanges().forEach(async change => {
if (change.type !== 'added') return;
const doc = change.doc;
const uid = doc.id;
const { email, displayName } = doc.data();
if (!uid || !email) {
logSys('WELCOME', `⚠️ Skipping malformed pending_welcomes doc (missing uid/email)`, 'warning');
await doc.ref.delete().catch(() => {});
return;
}
// ── Dedup: skip if welcome already sent ──
try {
const welcomeRef = db.collection('welcome_sent').doc(uid);
const sent = await welcomeRef.get();
if (sent.exists) {
logSys('WELCOME', `⏭️ Welcome already sent for ${maskUid(uid)}, skipping`, 'info');
await doc.ref.delete().catch(() => {});
return;
}
// Mark as sent immediately to prevent any race condition
await welcomeRef.set({ sentAt: admin.firestore.FieldValue.serverTimestamp(), email });
} catch (e) {
logSys('WELCOME', `⚠️ Firestore welcome_sent check failed: ${e.message}`, 'warning');
// Continue — don't block the welcome on a metadata failure
}
const name = displayName || 'عزيزي المستخدم';
const today = new Date();
const dateStr = getGregorianDateString(today);
const hijriStr = getHijriDateString(today);
// ── 1. In-app Notification ──
const notifBody = `مرحباً بك في Titan Connect يا ${name}! 🎉
أنت الآن جزء من مجتمعنا المتميز.
اكتشف ما ينتظرك:
🔐 شات وخزنة مشفرة تماماً
📡 نقل ملفات P2P مباشر بدون خوادم
🎬 سينما مع ترجمة ذكية بالذكاء الاصطناعي
📚 الانغماس — تعلّم الإنجليزي بالقصص
👥 جروبات ومجتمعات حية
نتمنى لك تجربة ممتعة! 🌟`;
try {
await db.collection('notifications').add({
title: '🎉 أهلاً بك في Titan Connect!',
body: notifBody,
type: 'WELCOME',
userId: uid,
timestamp: admin.firestore.FieldValue.serverTimestamp(),
read: false
});
// Real-time push to user's socket if online
io.emit('welcome_notification', { uid, title: '🎉 أهلاً بك!', body: notifBody });
logSys('WELCOME', `✅ In-app notification sent to ${maskUid(uid)}`, 'success');
} catch (e) {
logSys('WELCOME', `⚠️ Notification failed: ${e.message}`, 'warning');
}
// ── 2. Welcome Email ──
if (MAILER_URL && MAILER_TOKEN) {
const htmlBody = `<!DOCTYPE html>
<html dir="rtl"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body style="margin:0;padding:0;background:#0a0e1a;font-family:Arial,Helvetica,sans-serif">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0e1a;padding:24px 10px">
<tr><td align="center">
<table width="540" cellpadding="0" cellspacing="0" style="max-width:540px;width:100%;background:#0f1525;border-radius:16px;overflow:hidden;border:1px solid #1e3a5f">
<tr><td style="height:3px;background:linear-gradient(90deg,#63b3ed,#4fd1c5,#63b3ed)"></td></tr>
<tr><td style="background:#0d1830;padding:28px;text-align:center;border-bottom:1px solid #1e3a5f">
<div style="font-size:40px;margin-bottom:8px">&#x1F451;</div>
<h1 style="color:#63b3ed;margin:0;font-size:22px;letter-spacing:3px;font-weight:bold">TITAN CONNECT</h1>
<p style="color:#4fd1c5;margin:6px 0 0;font-size:11px;letter-spacing:2px">WELCOME TO THE FAMILY</p>
</td></tr>
<tr><td style="padding:28px 32px">
<p style="color:#e2ecff;font-size:15px;line-height:1.9;margin:0 0 20px">السلام عليكم ورحمة الله &#x1F319;<br><br>مرحباً <strong style="color:#63b3ed">${name}</strong>،<br>يسعدنا انضمامك إلى <strong style="color:#63b3ed">Titan Connect</strong> — التطبيق الذي صممناه ليكون أكثر من مجرد تطبيق.</p>
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0b1220;border-radius:10px;padding:18px;margin-bottom:24px">
<tr><td><p style="color:#4fd1c5;font-size:11px;letter-spacing:2px;margin:0 0 14px;font-weight:bold">&#x2728; ما ينتظرك بداخله</p></td></tr>
<tr><td style="padding-bottom:12px"><table cellpadding="0" cellspacing="0"><tr><td style="width:28px;vertical-align:top;padding-top:2px;font-size:16px">&#x1F510;</td><td><p style="color:#e2ecff;font-size:13px;font-weight:bold;margin:0">شات وخزنة مشفرة تماماً</p><p style="color:#5a7090;font-size:12px;margin:2px 0 0">محادثاتك لا يصل إليها أحد — حتى نحن</p></td></tr></table></td></tr>
<tr><td style="padding-bottom:12px"><table cellpadding="0" cellspacing="0"><tr><td style="width:28px;vertical-align:top;padding-top:2px;font-size:16px">&#x1F4E1;</td><td><p style="color:#e2ecff;font-size:13px;font-weight:bold;margin:0">نقل ملفات P2P مباشر</p><p style="color:#5a7090;font-size:12px;margin:2px 0 0">بدون خوادم وسيطة — من جهازك مباشرة</p></td></tr></table></td></tr>
<tr><td style="padding-bottom:12px"><table cellpadding="0" cellspacing="0"><tr><td style="width:28px;vertical-align:top;padding-top:2px;font-size:16px">&#x1F3AC;</td><td><p style="color:#e2ecff;font-size:13px;font-weight:bold;margin:0">سينما + ترجمة بالذكاء الاصطناعي</p><p style="color:#5a7090;font-size:12px;margin:2px 0 0">شاهد وترجم الفيديوهات وأنشئ ملفات ترجمة</p></td></tr></table></td></tr>
<tr><td style="padding-bottom:12px"><table cellpadding="0" cellspacing="0"><tr><td style="width:28px;vertical-align:top;padding-top:2px;font-size:16px">&#x1F4DA;</td><td><p style="color:#e2ecff;font-size:13px;font-weight:bold;margin:0">الانغماس — تعلّم الإنجليزي بالقصص</p><p style="color:#5a7090;font-size:12px;margin:2px 0 0">طريقة تعلم مختلفة وممتعة</p></td></tr></table></td></tr>
<tr><td><table cellpadding="0" cellspacing="0"><tr><td style="width:28px;vertical-align:top;padding-top:2px;font-size:16px">&#x1F465;</td><td><p style="color:#e2ecff;font-size:13px;font-weight:bold;margin:0">جروبات ومجتمعات حية</p><p style="color:#5a7090;font-size:12px;margin:2px 0 0">تواصل في مساحة آمنة ومشفرة</p></td></tr></table></td></tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding-bottom:20px">
<a href="https://titan-connect.vercel.app" style="display:inline-block;background:#63b3ed;color:#000;text-decoration:none;padding:13px 36px;border-radius:30px;font-weight:bold;font-size:14px;letter-spacing:1px">ابدأ رحلتك &#x2190;</a>
</td></tr></table>
<p style="color:#5a7090;font-size:12px;line-height:1.7;margin:0;text-align:center">مع تحياتي — محمود حسن<br>المطور والمؤسس، Titan Connect</p>
</td></tr>
<tr><td style="background:#060a12;padding:16px;text-align:center;border-top:1px solid #1a2a40">
<p style="color:#2a3a50;font-size:11px;margin:0">TITAN NEXUS &middot; ROYAL COMMS SYSTEM</p>
<p style="color:#1e2a38;font-size:10px;margin:4px 0 0">&#x1F4C5; ${dateStr} | &#x1F319; ${hijriStr}</p>
</td></tr>
<tr><td style="height:2px;background:linear-gradient(90deg,#63b3ed,#4fd1c5,#63b3ed)"></td></tr>
</table>
</td></tr>
</table>
</body></html>`;
try {
await sendTitanEmail(email, '🎉 أهلاً بك في Titan Connect!', htmlBody);
logSys('WELCOME', `✅ Welcome email sent to ${email.slice(0,4)}***`, 'success');
} catch (e) {
logSys('WELCOME', `⚠️ Welcome email failed: ${e.message.slice(0,80)}`, 'warning');
}
}
// ── CLEANUP: delete the processed doc so pending_welcomes stays empty ──
try {
await doc.ref.delete();
logSys('WELCOME', `🗑️ Cleaned up pending_welcomes/${maskUid(uid)}`, 'info');
} catch (e) {
logSys('WELCOME', `⚠️ Failed to delete pending_welcomes doc: ${e.message}`, 'warning');
}
});
}, err => {
logSys('WELCOME', `❌ pending_welcomes listener error: ${err.message}`, 'error');
});
logSys('WELCOME', '👂 Welcome Manager listening on pending_welcomes queue', 'success');
}
// 15.5. MANUAL TRIGGER ENDPOINTS
app.post('/admin/market-now', async (req, res) => {
const { adminKey } = req.body;
if (adminKey !== ADMIN_ID) return res.status(403).json({ success: false, error: 'Unauthorized' });
logSys('HTTP_ADMIN', '📈 Manual market broadcast triggered via HTTP', 'system');
broadcastMarketUpdate();
res.json({ success: true, message: 'Market report fetch initiated.' });
});
app.post('/admin/prayer-now', async (req, res) => {
const { adminKey } = req.body;
if (adminKey !== ADMIN_ID) return res.status(403).json({ success: false, error: 'Unauthorized' });
broadcastPrayerTimes();
res.json({ success: true, message: 'Prayer times broadcast initiated.' });
});
app.post('/admin/quote-now', async (req, res) => {
const { adminKey } = req.body;
if (adminKey !== ADMIN_ID) return res.status(403).json({ success: false, error: 'Unauthorized' });
broadcastDailyQuote();
res.json({ success: true, message: 'Daily quote broadcast initiated.' });
});
app.post('/admin/news-now', async (req, res) => {
const { adminKey } = req.body;
if (adminKey !== ADMIN_ID) return res.status(403).json({ success: false, error: 'Unauthorized' });
broadcastArabicNews();
res.json({ success: true, message: 'News broadcast initiated.' });
});
// ==========================================
// 📧 EMAIL SENDING ENDPOINT (Google Apps Script Mailer Bridge)
// HF Secrets: MAILER_URL, MAILER_TOKEN
// ==========================================
app.post('/admin/send-email', async (req, res) => {
const {
adminKey, subject, body, targetEmail,
logoUrl, // optional: URL to image shown at top
buttonText, // optional: CTA button label
buttonUrl, // optional: CTA button URL
pdfUrl, // optional: URL to a PDF (linked as button, not attachment — Apps Script limitation)
accentColor // optional: hex color e.g. "#63b3ed"
} = req.body;
if (adminKey !== ADMIN_ID) return res.status(403).json({ success: false, error: 'Unauthorized' });
if (!subject || !body) return res.status(400).json({ success: false, error: 'subject and body are required' });
if (!MAILER_URL || !MAILER_TOKEN) return res.status(500).json({ success: false, error: 'MAILER_URL and MAILER_TOKEN must be set in HF Secrets' });
try {
let recipients = [];
if (targetEmail) {
recipients = [targetEmail];
} else {
logSys('EMAIL', '📋 Fetching all user emails from Firebase Auth...', 'system');
let pageToken;
do {
const result = await admin.auth().listUsers(1000, pageToken);
result.users.forEach(user => { if (user.email) recipients.push(user.email); });
pageToken = result.pageToken;
} while (pageToken);
}
if (!recipients.length) return res.status(404).json({ success: false, error: 'No email recipients found' });
logSys('EMAIL', `📧 Sending to ${recipients.length} recipients...`, 'system');
const accent = accentColor || '#63b3ed';
const accentDark = '#0f2044';
// ── Professional HTML Email Template ──
const logoSection = logoUrl
? `<tr><td style="padding:0 0 20px;text-align:center"><img src="${logoUrl}" alt="Logo" style="max-width:180px;max-height:80px;object-fit:contain"></td></tr>`
: '';
const ctaButton = (buttonText && buttonUrl)
? `<tr><td style="padding:24px 0 0;text-align:center">
<a href="${buttonUrl}" style="display:inline-block;background:linear-gradient(135deg,${accent},#4fd1c5);color:#000;text-decoration:none;padding:14px 36px;border-radius:30px;font-weight:bold;font-size:15px;letter-spacing:1px">${buttonText} →</a>
</td></tr>`
: `<tr><td style="padding:24px 0 0;text-align:center">
<a href="https://m-hv1-titan-bot.hf.space" style="display:inline-block;background:linear-gradient(135deg,${accent},#4fd1c5);color:#000;text-decoration:none;padding:14px 36px;border-radius:30px;font-weight:bold;font-size:15px;letter-spacing:1px">Open Titan App →</a>
</td></tr>`;
const pdfSection = pdfUrl
? `<tr><td style="padding:16px 0 0;text-align:center">
<a href="${pdfUrl}" style="display:inline-block;background:rgba(99,179,237,.1);color:${accent};text-decoration:none;padding:10px 24px;border-radius:20px;font-size:13px;border:1px solid ${accent}">📎 Download PDF</a>
</td></tr>`
: '';
const htmlBody = `<!DOCTYPE html>
<html dir="auto">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>${subject}</title>
</head>
<body style="margin:0;padding:0;background:#0a0e1a;font-family:Arial,Helvetica,sans-serif">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0e1a;padding:30px 10px">
<tr><td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#0f1525;border-radius:20px;overflow:hidden;border:1px solid #1e3a5f;box-shadow:0 20px 60px rgba(0,0,0,.6)">
<!-- TOP ACCENT BAR -->
<tr><td style="height:4px;background:linear-gradient(90deg,${accent},#4fd1c5,${accent})"></td></tr>
<!-- HEADER -->
<tr><td style="background:linear-gradient(135deg,${accentDark},#0a1628);padding:36px 40px;text-align:center">
${logoSection ? `<table width="100%" cellpadding="0" cellspacing="0">${logoSection}</table>` : ''}
<div style="font-size:42px;margin-bottom:10px;filter:drop-shadow(0 0 20px rgba(246,201,14,.4))">👑</div>
<h1 style="color:${accent};margin:0;font-size:26px;letter-spacing:3px;text-transform:uppercase;font-weight:bold">Titan App</h1>
<p style="color:#4fd1c5;margin:8px 0 0;font-size:13px;letter-spacing:2px;text-transform:uppercase">Royal Notification System</p>
</td></tr>
<!-- DIVIDER -->
<tr><td style="height:1px;background:linear-gradient(90deg,transparent,${accent},transparent)"></td></tr>
<!-- BODY -->
<tr><td style="padding:36px 40px">
<div style="color:#e2ecff;font-size:15px;line-height:1.9;white-space:pre-wrap;background:rgba(99,179,237,.04);border-left:3px solid ${accent};padding:20px 20px 20px 20px;border-radius:0 8px 8px 0;margin-bottom:8px">${body}</div>
<table width="100%" cellpadding="0" cellspacing="0">
${ctaButton}
${pdfSection}
</table>
</td></tr>
<!-- FOOTER -->
<tr><td style="background:#060a12;padding:24px 40px;text-align:center;border-top:1px solid #1a2a40">
<p style="color:#3a5070;font-size:12px;margin:0 0 6px;letter-spacing:1px">TITAN NEXUS · POWERED BY ROYAL AI</p>
<p style="color:#2a3a50;font-size:11px;margin:0">You received this because you are a registered Titan App user</p>
</td></tr>
<!-- BOTTOM ACCENT BAR -->
<tr><td style="height:3px;background:linear-gradient(90deg,${accent},#4fd1c5,${accent})"></td></tr>
</table>
</td></tr>
</table>
</body></html>`;
let sent = 0, failed = 0;
for (let i = 0; i < recipients.length; i++) {
const email = recipients[i];
try {
await sendTitanEmail(email, subject, htmlBody);
sent++;
if (sent % 5 === 0) logSys('EMAIL', `📨 Progress: ${sent}/${recipients.length}`, 'system');
} catch (err) {
logSys('EMAIL', `⚠️ Failed ${email.slice(0,6)}***: ${err.message.slice(0,60)}`, 'warning');
failed++;
}
if (i < recipients.length - 1) await new Promise(r => setTimeout(r, 300));
}
logSys('EMAIL', `✅ Done: ${sent} sent, ${failed} failed, ${recipients.length} total`, 'success');
res.json({ success: true, sent, failed, total: recipients.length });
} catch (error) {
logSys('EMAIL', `❌ Email error: ${error.message}`, 'error');
res.status(500).json({ success: false, error: error.message });
}
});
// ==========================================
// 🔐 AUTHENTICATED GATEWAY PROXY (v14.4)
// Route: /api/gateway-proxy/*
//
// Security model:
// 1. Client sends Firebase ID Token in `Authorization: Bearer <token>`
// 2. We verify the token with Firebase Admin — 403 if invalid/missing
// 3. We forward the request to the real Titan Gateway, injecting the
// server-side TITAN_GATEWAY_KEY so the frontend never sees it.
// 4. The gateway response is streamed back transparently.
//
// Supports:
// • GET requests (query-string forwarded as-is)
// • POST requests with JSON body
// • POST requests with multipart/form-data (file uploads)
// ==========================================
const TITAN_GATEWAY_BASE = 'https://m-hv1-titan-gateway.hf.space';
// ── Relay is now merged into titan-messenger (no longer a standalone Space) ──
// Path prefix changed from root /upload-chunk
// to /relay/upload-chunk
const TITAN_CLOUD_RELAY_BASE = 'https://m-hv1-titan-messenger.hf.space/relay';
// ── Middleware: verify Firebase ID Token ──────────────────────────────────
async function requireFirebaseAuth(req, res, next) {
const authHeader = req.headers['authorization'] || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (!token) {
logSys('PROXY', `❌ Missing Authorization header from ${req.ip}`, 'error');
return res.status(403).json({ error: 'Forbidden: missing Authorization header' });
}
try {
const decoded = await admin.auth().verifyIdToken(token);
req.titanUid = decoded.uid; // available in handler if needed
next();
} catch (err) {
logSys('PROXY', `❌ Invalid Firebase ID Token: ${err.message}`, 'error');
return res.status(403).json({ error: 'Forbidden: invalid or expired token' });
}
}
// ── Helper: forward a request to the real gateway / relay ─────────────────
// Handles JSON, FormData (multipart), and GET.
// Returns a Node http.ClientRequest-compatible response that we can pipe.
function forwardToUpstream(upstreamUrl, req, extraHeaders = {}) {
return new Promise((resolve, reject) => {
const parsed = new URL(upstreamUrl);
const contentType = req.headers['content-type'] || '';
const isMultipart = contentType.includes('multipart/form-data');
const isGet = req.method === 'GET';
// ── For JSON/urlencoded: Express has already consumed the stream via express.json(),
// so req.pipe() sends an empty body -> gateway returns 422.
// Solution: re-serialise from req.body (already parsed by Express).
// ── For multipart (FormData): Express does NOT parse it, stream is intact -> pipe directly.
// ── For GET: no body.
let bodyBuffer = null;
let finalContentType = contentType;
if (!isGet && !isMultipart) {
const bodyStr = JSON.stringify(req.body ?? {});
bodyBuffer = Buffer.from(bodyStr, 'utf8');
finalContentType = 'application/json';
}
const headers = {
...((!isGet && finalContentType) ? { 'Content-Type': finalContentType } : {}),
...(bodyBuffer ? { 'Content-Length': bodyBuffer.byteLength } : {}),
'X-Titan-Key': process.env.TITAN_GATEWAY_KEY,
...extraHeaders,
};
const options = {
hostname: parsed.hostname,
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
path: parsed.pathname + (parsed.search || ''),
method: req.method,
headers,
};
const upstreamReq = https.request(options, (upstreamRes) => {
resolve(upstreamRes);
});
upstreamReq.on('error', reject);
upstreamReq.setTimeout(120000, () => {
upstreamReq.destroy();
reject(new Error('Upstream request timed out'));
});
if (isGet) {
upstreamReq.end();
} else if (isMultipart) {
// Stream raw multipart body directly — Express has not touched it
req.pipe(upstreamReq);
} else {
// Write the re-serialised JSON body
upstreamReq.write(bodyBuffer);
upstreamReq.end();
}
});
}
// ── PROXY: Titan Gateway → /api/gateway-proxy/* ─────────────────────────
// Preserves all original paths, e.g.:
// /api/gateway-proxy/api/v1/translate → https://m-hv1-titan-gateway.hf.space/api/v1/translate
app.use('/api/gateway-proxy', requireFirebaseAuth, async (req, res) => {
// Strip the /api/gateway-proxy prefix to get the real downstream path
const downstreamPath = req.path; // e.g. /api/v1/translate
const queryString = req.url.includes('?')
? req.url.slice(req.url.indexOf('?'))
: '';
const upstreamUrl = `${TITAN_GATEWAY_BASE}${downstreamPath}${queryString}`;
logSys('PROXY', `[${req.titanUid?.slice(0, 8)}…] ${req.method} ${upstreamUrl}`, 'info');
try {
const upstreamRes = await forwardToUpstream(upstreamUrl, req);
// Mirror status + headers (minus hop-by-hop headers)
res.status(upstreamRes.statusCode);
const hopByHop = new Set(['connection', 'keep-alive', 'transfer-encoding', 'trailer',
'upgrade', 'proxy-authorization', 'proxy-authenticate', 'te']);
Object.entries(upstreamRes.headers).forEach(([k, v]) => {
if (!hopByHop.has(k.toLowerCase())) res.setHeader(k, v);
});
// Stream response body back to client
upstreamRes.pipe(res);
} catch (err) {
logSys('PROXY', `❌ Gateway proxy error: ${err.message}`, 'error');
if (!res.headersSent) {
res.status(502).json({ error: `Gateway proxy failed: ${err.message}` });
}
}
});
// ── PROXY: Titan Cloud Relay → /api/relay-proxy/* ──────────────────────
// Handles upload-chunk / download-chunk for the media relay.
// Relay is now merged into titan-messenger under the /relay prefix:
// /api/relay-proxy/upload-chunk → https://m-hv1-titan-messenger.hf.space/relay/upload-chunk
// /api/relay-proxy/download-chunk → https://m-hv1-titan-messenger.hf.space/relay/download-chunk
app.use('/api/relay-proxy', requireFirebaseAuth, async (req, res) => {
const downstreamPath = req.path;
const queryString = req.url.includes('?')
? req.url.slice(req.url.indexOf('?'))
: '';
const upstreamUrl = `${TITAN_CLOUD_RELAY_BASE}${downstreamPath}${queryString}`;
logSys('PROXY', `[RELAY][${req.titanUid?.slice(0, 8)}…] ${req.method} ${upstreamUrl}`, 'info');
try {
const upstreamRes = await forwardToUpstream(upstreamUrl, req, {
'x-titan-type' : req.headers['x-titan-type'] || '',
// Forward the user's Firebase token — cloudRelay.js verifyies it
'Authorization' : req.headers['authorization'] || '',
// The cloud-relay server uses TITAN_API_KEY (TITAN_RELAY_KEY in Nexus env)
'X-Titan-Key' : process.env.TITAN_RELAY_KEY || process.env.TITAN_GATEWAY_KEY,
});
res.status(upstreamRes.statusCode);
const hopByHop = new Set(['connection', 'keep-alive', 'transfer-encoding', 'trailer',
'upgrade', 'proxy-authorization', 'proxy-authenticate', 'te']);
Object.entries(upstreamRes.headers).forEach(([k, v]) => {
if (!hopByHop.has(k.toLowerCase())) res.setHeader(k, v);
});
upstreamRes.pipe(res);
} catch (err) {
logSys('PROXY', `❌ Relay proxy error: ${err.message}`, 'error');
if (!res.headersSent) {
res.status(502).json({ error: `Relay proxy failed: ${err.message}` });
}
}
});
// 16. BOOT SEQUENCE
server.listen(PORT, async () => {
console.log(`\n\x1b[36m╔═══════════════════════════════════════════════════════════════════════╗\x1b[0m`);
console.log(`\x1b[36m║ 🛡️ TITAN NEXUS v14.4 - ROYAL COMMS EDITION ║\x1b[0m`);
console.log(`\x1b[36m║ ⚡ Socket.IO | Sentinel MK2 | Janitor MK2 | Email Engine ║\x1b[0m`);
console.log(`\x1b[36m║ 📡 Dead Drop | 🧠 Gemini 3 | Free Gold APIs | Groq Stocks ║\x1b[0m`);
console.log(`\x1b[36m║ 🕌 Prayer | 💡 Quote | 📰 News | 📈 Market | 📧 Email ║\x1b[0m`);
console.log(`\x1b[36m║ 🔐 Auth Gateway Proxy | Relay Proxy | Firebase ID Token Guard ║\x1b[0m`);
console.log(`\x1b[36m║ 👑 Royal Immunity | 🚀 Port: ${PORT} ║\x1b[0m`);
console.log(`\x1b[36m╚═══════════════════════════════════════════════════════════════════════╝\x1b[0m\n`);
// Log current date context
logSys('CORE', `🗓️ Gregorian: ${getGregorianDateString(new Date())}`, 'ai');
logSys('CORE', `🌙 Hijri: ${getHijriDateString(new Date())}`, 'ai');
// Activate Sentinel
const sentinel = new SentinelMK2();
sentinel.monitorComments();
sentinel.monitorReports();
logSys('CORE', '🛡️ Sentinel MK2 armed (Royal Immunity active)', 'success');
// Activate Janitor
const janitor = new JanitorMK2();
setInterval(() => janitor.cleanOldMessages(), 60 * 60 * 1000);
logSys('CORE', '🧹 Janitor MK2 scheduled: hourly (Royal preservation enabled)', 'success');
// Activate Dead Drop Command Reader
initCommandReader();
logSys('CORE', '📡 Firestore Dead Drop Command Reader activated', 'success');
// Activate Event-Driven Welcome Manager (replaces POST /auth/welcome)
initWelcomeManager();
logSys('CORE', '🎉 Welcome Manager activated (pending_welcomes queue listener)', 'success');
// Initialize AI Cron Jobs (6h riddle broadcasts + 11:59 PM royal reports)
initCronJobs();
logSys('CORE', '🧠 Time-Aware AI automation initialized (Riddle-only mode)', 'success');
logSys('CORE', '📅 Scheduled: Every 6h riddle, 8 AM & 3 PM market report, 10 PM game awards, 11:59 PM royal reports', 'success');
logSys('CORE', '📈 Market Intelligence: Groq Compound (web search) — Gold EGP/SAR/AED/USD + Global Indices', 'ai');
// Warm-up reputation cache
try {
const repSnapshot = await db.collection('user_reputation').limit(100).get();
repSnapshot.docs.forEach(doc => {
const data = doc.data();
sentinel.userReputation.set(data.userId, data.score || 100);
});
logSys('CORE', `📊 Loaded ${repSnapshot.size} user reputations`, 'info');
} catch (e) {
logSys('CORE', '⚠️ Could not preload reputations', 'warning');
}
logSys('CORE', `👑 Royal Family protection active for ${ROYAL_FAMILY.length} members`, 'royal');
logSys('CORE', '🎛️ Admin Command Center (Socket + Dead Drop + HTTP) initialized', 'success');
// 🪙 Extensions: Wallet/Coins + new admin commands + cleanup crons
initExtensions(app, io, db, admin, { ADMIN_ID, ROYAL_FAMILY, onlineUsers, logSys, maskUid, getServerStats });
logSys('CORE', '✅ Nexus v14.2 operational. Market Intelligence Edition ready.', 'success');
});