BOT2 / bot-team.js
abdulader's picture
Update bot-team.js
d2c4ec3 verified
Raw
History Blame Contribute Delete
177 kB
'use strict';
try { require('dotenv').config(); } catch (_) {}
/**
* ╔══════════════════════════════════════════════════════════════════════════╗
* Sayed Bot Team — v17.0 | النسخة الذكية الكاملة
* ╠══════════════════════════════════════════════════════════════════════════╣
* v17.0 — تحسينات الاستقرار الكاملة:
*
* SETUP-01: معالج إعداد ويب — يطلب IP, Port, الإصدار, عدد البوتات
* PLUG-01: كشف البلاجن الكاملة عند منح OP + تحليل كل بلاجن
* PERF-01: مراقبة أداء السيرفر + تحسين تلقائي عند الـ lag
* DASH-01: لوحة تحكم كاملة RTL عربي — live update بدون reload
* VER-01: دعم كل الإصدارات + fallback تلقائي عند version mismatch
* ANTI-01: حماية ضد قطع Aternos — 9 طبقات حماية
* ANTI-02: تشغيل Aternos تلقائياً عند DeepSleep + healthz check
* MEM-01: إدارة ذاكرة محسّنة + حفظ البيانات تلقائياً
* POS-01: إحداثيات البوتات في الداشبورد
* CHAT-01: إرسال رسائل في السيرفر من الداشبورد
* ╚══════════════════════════════════════════════════════════════════════════╝
*/
process.setMaxListeners(0);
process.on('uncaughtException', err => {
const c = err.code || err.message || '';
if (/EPIPE|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EHOSTUNREACH/.test(c)) return;
console.error('[SYS] ❌ uncaughtException:', err.message);
});
process.on('unhandledRejection', reason => {
const m = String(reason?.message || reason || '');
if (/EPIPE|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EHOSTUNREACH/.test(m)) return;
console.error('[SYS] ❌ unhandledRejection:', m);
});
const fs = require('fs');
const path = require('path');
const http = require('http');
const net = require('net');
const dns = require('dns');
const { spawn } = require('child_process');
const express = require('express');
const mineflayer = require('mineflayer');
const { pathfinder, Movements, goals } = require('mineflayer-pathfinder');
// ════════════════════════════════════════════════════════════════════════════
// CONFIG: يُحمَّل من ملف config.json أو متغيرات البيئة
// ════════════════════════════════════════════════════════════════════════════
const CONFIG_PATH = path.join(__dirname, 'config.json');
let CFG = loadConfig();
function loadConfig() {
let file = {};
try {
if (fs.existsSync(CONFIG_PATH)) file = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
} catch (_) {}
return {
host: file.host || process.env.MC_HOST || 'A3BO3D-XlvV.aternos.me'
port: parseInt(file.port || process.env.MC_PORT || '12476', 10),
version: file.version || process.env.MC_VERSION || 'auto',
botCount: parseInt(file.botCount || process.env.BOT_COUNT || '4', 10),
bot1Name: file.bot1Name || process.env.BOT1_NAME || 'Alex_miner26',
bot2Name: file.bot2Name || process.env.BOT2_NAME || 'Steve_builder',
bot3Name: file.bot3Name || process.env.BOT3_NAME || 'NightExplorer',
bot4Name: file.bot4Name || process.env.BOT4_NAME || 'RedstoneKing',
staggerMin: parseInt(file.staggerMin || process.env.BOT_STAGGER_MIN || '120', 10) * 1000,
staggerMax: parseInt(file.staggerMax || process.env.BOT_STAGGER_MAX || '240', 10) * 1000,
dashPort: parseInt(file.dashPort || process.env.PORT || '7860', 10),
isConfigured: !!(file.host || process.env.MC_HOST),
};
}
function saveConfig(data) {
const updated = { ...CFG, ...data, isConfigured: true };
fs.writeFileSync(CONFIG_PATH, JSON.stringify(updated, null, 2));
CFG = updated;
return CFG;
}
// ════════════════════════════════════════════════════════════════════════════
// DATA PERSISTENCE — MEM-01 + حفظ البيانات بين عمليات التشغيل
// ════════════════════════════════════════════════════════════════════════════
const DATA_PATH = path.join(__dirname, 'data.json');
function saveData() {
try {
fs.writeFileSync(DATA_PATH, JSON.stringify({
plugins: detectedPlugins,
perfHistory: performanceData.history.slice(-20),
players: [...playerRegistry.entries()],
savedAt: tsNow(),
}, null, 2));
} catch (_) {}
}
function loadData() {
try {
if (!fs.existsSync(DATA_PATH)) return;
const d = JSON.parse(fs.readFileSync(DATA_PATH, 'utf8'));
if (Array.isArray(d.plugins) && d.plugins.length) {
detectedPlugins.push(...d.plugins);
sysLog('💾', `محُمِّل ${d.plugins.length} بلاجن من الحفظ السابق`);
}
if (Array.isArray(d.perfHistory) && d.perfHistory.length) {
performanceData.history.push(...d.perfHistory);
}
if (Array.isArray(d.players) && d.players.length) {
d.players.forEach(([name, data]) => {
playerRegistry.set(name, { ...data, joinedAt: null, lastChat: null });
});
sysLog('💾', `محُمِّل ${d.players.length} لاعب من السجل السابق`);
}
} catch (_) {}
}
// ════════════════════════════════════════════════════════════════════════════
// ATERNOS AUTO-TRIGGER — ANTI-02
// ════════════════════════════════════════════════════════════════════════════
let aternosRunning = false;
function triggerAternos() {
if (aternosRunning) return;
aternosRunning = true;
sysLog('🔔', 'ANTI-02: السيرفر لا يرد — تنبيه بتشغيل Aternos...');
addEvent('info', '🔔 السيرفر متوقف — افتح aternos.org وشغّل السيرفر يدوياً');
addEvent('info', '🔗 aternos.org → My Servers → Start');
// لا نستخدم Puppeteer (لا يعمل على HuggingFace — لا Chrome)
// بدلاً منه: نبث رابط مباشر في الداشبورد ونعيد المحاولة تلقائياً
setTimeout(() => {
aternosRunning = false;
sysLog('🔄', 'ANTI-02: إعادة محاولة الاتصال...');
}, 3 * 60000); // بعد 3 دقائق نعيد المحاولة تلقائياً
}
// ════════════════════════════════════════════════════════════════════════════
// VERSIONS — الإصدارات المدعومة بالكامل
// ════════════════════════════════════════════════════════════════════════════
const SUPPORTED_VERSIONS = [
'1.21.6','1.21.5','1.21.4','1.21.3','1.21.2','1.21.0',
'1.20.0','1.19.0','1.18.0','1.17.0','1.16.0',
'1.15.0','1.14.0','1.13.0','1.12.0','1.11.0','1.10.0',
'1.9.0','1.8.0','1.7.2','1.6.1','1.5.0','1.4.2','1.3.1',
'1.2.1','1.1.0','1.0.0',
'26.2.0','26.1.0',
];
const PROTOCOL_MAP = {
770:'1.21.5', 769:'1.21.4', 768:'1.21.3', 767:'1.21.2', 766:'1.21.0',
765:'1.20.4', 764:'1.20.2', 763:'1.20.1', 762:'1.20.0',
761:'1.19.4', 760:'1.19.3', 759:'1.19.2', 758:'1.19.1', 757:'1.19.0',
756:'1.18.2', 755:'1.18.1', 748:'1.18.0',
753:'1.17.0',
754:'1.16.5', 736:'1.16.0',
578:'1.15.0', 498:'1.14.0', 393:'1.13.0', 340:'1.12.0',
316:'1.11.0', 210:'1.10.0', 110:'1.9.0',
47:'1.8.0', 4:'1.7.2', 39:'1.6.1', 61:'1.5.0',
};
const VERSION_VERSION_MAP = {
'26.1.0': '1.21.4',
'26.2.0': '1.21.5',
'1.21.6': '1.21.5',
'1.21.0': '1.21',
'1.20.0': '1.20',
'1.19.0': '1.19',
'1.18.0': '1.18',
'1.17.0': '1.17',
'1.16.0': '1.16.5',
'1.15.0': '1.15.2',
'1.14.0': '1.14.4',
'1.13.0': '1.13.2',
'1.12.0': '1.12.2',
'1.11.0': '1.11.2',
'1.10.0': '1.10.2',
'1.9.0': '1.9.4',
'1.8.0': '1.8.9',
};
function resolveVersion(v) {
if (!v || v === 'auto') return null;
if (VERSION_VERSION_MAP[v]) return VERSION_VERSION_MAP[v];
return v;
}
let autoVersion = null;
let fallbackVersionList = buildVersionFallbacks();
function buildVersionFallbacks() {
try {
const mcProto = require('minecraft-protocol');
const raw = mcProto.supportedVersions ||
(mcProto.versions && (mcProto.versions.pc || mcProto.versions)) || [];
const list = Array.isArray(raw) ? raw : Object.values(raw);
const versions = list
.map(v => typeof v === 'string' ? v : v.minecraftVersion || v.version || '')
.filter(v => { const p = v.split('.'); return parseInt(p[0]) === 1 && parseInt(p[1]) >= 7; });
if (versions.length > 0) {
versions.sort((a, b) => {
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
for (let i = 0; i < 3; i++) { const d = (pb[i]||0)-(pa[i]||0); if (d) return d; }
return 0;
});
return [...new Set(versions)];
}
} catch (_) {}
return ['1.21.4','1.21.3','1.21.2','1.21.1','1.21','1.20.6','1.20.4','1.20.2',
'1.20.1','1.20','1.19.4','1.19.2','1.19','1.18.2','1.18','1.17.1',
'1.16.5','1.15.2','1.14.4','1.12.2','1.9.4','1.8.9'];
}
async function detectServerVersion(host, port) {
return new Promise(resolve => {
try {
const mcProto = require('minecraft-protocol');
const timer = setTimeout(() => resolve(null), 8000);
mcProto.ping({ host, port, closeTimeout: 6000 }, (err, data) => {
clearTimeout(timer);
if (err || !data) { resolve(null); return; }
try {
const raw = String(data.version?.name || data.version || '');
const protocol = data.version?.protocol || null;
const m = raw.match(/(\d+\.\d+(?:\.\d+)?)/);
let ver = m ? m[1] : null;
if (!ver && protocol) {
ver = PROTOCOL_MAP[protocol] || null;
}
if (ver && !fallbackVersionList.includes(ver)) {
ver = fallbackVersionList[0];
}
resolve(ver);
} catch (_) { resolve(null); }
});
} catch (_) { resolve(null); }
});
}
// ════════════════════════════════════════════════════════════════════════════
// PLUGIN DATABASE — قاعدة بيانات البلاجن المعروفة
// ════════════════════════════════════════════════════════════════════════════
const PLUGIN_DB = {
// أداء
'clearlag': { cat:'performance', desc:'يحذف الـ entities الزائدة لتقليل الـ lag', good:true },
'spark': { cat:'performance', desc:'أداة مراقبة أداء متقدمة — تحليل TPS وRAM', good:true },
'laggremover': { cat:'performance', desc:'يزيل entities ونواتج البناء لتحسين الأداء', good:true },
'farmcontrol': { cat:'performance', desc:'يتحكم في حجم المزارع لمنع الـ lag', good:true },
'entitytracker': { cat:'performance', desc:'يتتبع ويحذف الـ entities الزائدة', good:true },
'tpsbenchmark': { cat:'performance', desc:'يقيس وعرض معدل TPS الحالي', good:true },
'chunky': { cat:'performance', desc:'يولّد الـ chunks مسبقاً لمنع الـ lag', good:true },
'fastchunkpregenerator': { cat:'performance', desc:'توليد chunks سريع', good:true },
'entityclearer': { cat:'performance', desc:'ينظف الـ entities تلقائياً', good:true },
'performanceplus': { cat:'performance', desc:'تحسين عام للأداء', good:true },
// حماية
'worldguard': { cat:'protection', desc:'حماية المناطق من التخريب والتعديل', good:true },
'essentialsx': { cat:'utility', desc:'أوامر أساسية: /tpa, /spawn, /home, AFK', good:true },
'essentials': { cat:'utility', desc:'أوامر أساسية للسيرفر', good:true },
'anticheat': { cat:'security', desc:'كشف الغش والـ hacks', good:true },
'coreprotect': { cat:'protection', desc:'تسجيل التعديلات وإمكانية التراجع', good:true },
'griefprevention': { cat:'protection', desc:'منع الـ grief وحماية الأراضي', good:true },
// إدارة
'luckperms': { cat:'admin', desc:'إدارة الصلاحيات المتقدمة', good:true },
'vaultapi': { cat:'admin', desc:'API للاقتصاد والصلاحيات', good:true },
'vault': { cat:'admin', desc:'API للاقتصاد والصلاحيات', good:true },
'multiverse-core': { cat:'worlds', desc:'إدارة عوالم متعددة', good:true },
'multiverse': { cat:'worlds', desc:'إدارة عوالم متعددة', good:true },
'dynmap': { cat:'map', desc:'خريطة ديناميكية للسيرفر على المتصفح', good:true },
'bluemap': { cat:'map', desc:'خريطة ثلاثية الأبعاد على المتصفح', good:true },
// لاعبين
'skinsrestorer': { cat:'cosmetic', desc:'استعادة الـ skins في السيرفرات offline', good:true },
'tab': { cat:'ui', desc:'تخصيص قائمة اللاعبين (TAB)', good:true },
'placeholderapi': { cat:'utility', desc:'API للـ placeholders للبلاجن الأخرى', good:true },
'papi': { cat:'utility', desc:'PlaceholderAPI', good:true },
'chatmanager': { cat:'chat', desc:'إدارة وتنسيق الشات', good:true },
'discordsrv': { cat:'integration', desc:'ربط السيرفر بـ Discord', good:true },
// AFK
'antialfkplus': { cat:'afk', desc:'منع طرد AFK في Aternos', good:true },
'antialfk': { cat:'afk', desc:'منع طرد AFK', good:true },
'noafkkick': { cat:'afk', desc:'منع طرد AFK', good:true },
'afkplus': { cat:'afk', desc:'نظام AFK محسّن', good:true },
'antiafc': { cat:'afk', desc:'منع kick AFK', good:true },
// اقتصاد
'essentialsxeco': { cat:'economy', desc:'نظام اقتصاد مدمج مع EssentialsX', good:true },
'shopguiplus': { cat:'economy', desc:'متجر GUI سهل الاستخدام', good:true },
'aureliumskills': { cat:'skills', desc:'نظام مهارات للاعبين', good:true },
'mmocore': { cat:'rpg', desc:'نظام RPG متقدم', good:true },
};
function analyzePlugin(name) {
const key = name.toLowerCase().replace(/[\s\-_]/g, '');
for (const [k, v] of Object.entries(PLUGIN_DB)) {
if (key.includes(k.replace(/[\s\-_]/g, ''))) return { name, ...v };
}
const lk = key;
if (/anticheat|nocheat|aac|matrix|spartan|grim/.test(lk))
return { name, cat:'security', desc:'مكافحة الغش', good:true };
if (/economy|econ|money|coin|token/.test(lk))
return { name, cat:'economy', desc:'نظام اقتصاد', good:true };
if (/chat|message|msg|talk/.test(lk))
return { name, cat:'chat', desc:'إدارة الشات', good:true };
if (/protect|guard|safe|region/.test(lk))
return { name, cat:'protection', desc:'حماية وأمان', good:true };
if (/shop|market|sell|buy|trade/.test(lk))
return { name, cat:'economy', desc:'نظام تجاري', good:true };
if (/skill|level|xp|exp|rpg/.test(lk))
return { name, cat:'rpg', desc:'نظام مهارات/مستويات', good:true };
if (/clear|clean|remov|delet/.test(lk))
return { name, cat:'performance', desc:'تنظيف الـ entities/drops', good:true };
if (/world|dimension|teleport|warp/.test(lk))
return { name, cat:'worlds', desc:'إدارة عوالم أو نقاط teleport', good:true };
return { name, cat:'misc', desc:'بلاجن متنوعة', good:false };
}
// ════════════════════════════════════════════════════════════════════════════
// GLOBAL STATE
// ════════════════════════════════════════════════════════════════════════════
const STATE = Object.freeze({
IDLE:'IDLE', WALKING:'WALKING', DIGGING:'DIGGING', SLEEPING:'SLEEPING',
EATING:'EATING', FOLLOWING:'FOLLOWING', FARMING:'FARMING',
COMMAND:'COMMAND', SCANNING:'SCANNING',
});
const sleep = ms => new Promise(r => setTimeout(r, Math.max(0, ms)));
const randInt = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a;
const randFloat = (a, b) => Math.random() * (b - a) + a;
const pick = arr => arr[Math.floor(Math.random() * arr.length)];
const clamp = (v, mn, mx) => Math.max(mn, Math.min(mx, v));
const tsNow = () => new Date().toISOString().replace('T',' ').slice(0,19);
const sysLog = (e, m) => console.log(`[${tsNow()}] [SYS] ${e} ${m}`);
const botLog = (id, e, m) => console.log(`[${tsNow()}] [B${id+1}] ${e} ${m}`);
// سجل الأحداث للـ dashboard
const EVENT_LOG = [];
function addEvent(type, msg) {
EVENT_LOG.unshift({ time: tsNow(), type, msg });
if (EVENT_LOG.length > 100) EVENT_LOG.length = 100;
}
// بيانات البلاجن
let detectedPlugins = [];
let performanceData = { tps: null, lag: false, lastCheck: null, history: [], ecoMode: false, ecoCount: 0 };
let serverInfo = { motd: '', players: 0, maxPlayers: 0, version: '' };
// تحميل البيانات المحفوظة من التشغيل السابق (بعد تهيئة المتغيرات)
setTimeout(loadData, 0);
// ════════════════════════════════════════════════════════════════════════════
// BOT ROLES — نظام أدوار الفريق المتكامل
// ════════════════════════════════════════════════════════════════════════════
const BOT_ROLES = [
{ id:'commander', label:'🎖️ القائد', color:'#f59e0b', desc:'يوجّه الفريق ويدير السيرفر تلقائياً' },
{ id:'guardian', label:'🛡️ الحارس', color:'#3b82f6', desc:'يراقب اللاعبين ويكشف AFK والغش' },
{ id:'farmer', label:'🌾 المزارع', color:'#10b981', desc:'يدير الموارد والأكل للجميع' },
{ id:'builder', label:'🔨 البنّاء', color:'#8b5cf6', desc:'يعمر ويحسّن السيرفر' },
{ id:'diplomat', label:'💬 الدبلوماسي', color:'#06b6d4', desc:'يرحّب باللاعبين ويتواصل معهم' },
];
// ════════════════════════════════════════════════════════════════════════════
// TASK MAP — توزيع المهام ديناميكياً حسب عدد البوتات المتصلة
// المهام: guardian = مراقبة AFK + طرد الخاملين
// diplomat = ترحيب + تشجيع اللاعبين
// commander = تقارير دورية + إعلانات
// farmer = تذكير بالأكل
// optimizer = تحسين أداء السيرفر
// ════════════════════════════════════════════════════════════════════════════
const TASK_MAP = {
1: [['guardian','diplomat','commander','farmer','optimizer']],
2: [['guardian','commander'], ['diplomat','farmer','optimizer']],
3: [['guardian','optimizer'], ['commander','farmer'], ['diplomat']],
4: [['commander'], ['guardian'], ['farmer'], ['diplomat']],
};
// ════════════════════════════════════════════════════════════════════════════
// PLAYER REGISTRY — سجل اللاعبين (ROLE-01)
// ════════════════════════════════════════════════════════════════════════════
const playerRegistry = new Map(); // username → { firstSeen, lastSeen, visits, joinedAt, lastChat }
const _chatRateMap = new Map(); // slotId → lastSendTime (rate limit للـ /api/send-chat)
// Shared team state — حالة الفريق المشتركة
const teamState = { lastReport: null, eventPending: false };
// ════════════════════════════════════════════════════════════════════════════
// DANGER BLOCKS & FOOD
// ════════════════════════════════════════════════════════════════════════════
const DANGER_BLOCKS = new Set([
'lava','flowing_lava','fire','soul_fire','cactus','magma_block',
'sweet_berry_bush','wither_rose','powder_snow',
]);
const FOOD_NAMES = [
'bread','cooked_beef','cooked_chicken','cooked_pork_chop','cooked_salmon',
'cooked_mutton','cooked_rabbit','apple','golden_apple','carrot','baked_potato',
'melon_slice','cookie','dried_kelp','beef','pork_chop','chicken','salmon',
'rabbit','mutton','pumpkin_pie','sweet_berries',
];
const DIGGABLE = [
'stone','cobblestone','dirt','grass_block','sand','gravel','netherrack',
'oak_log','spruce_log','birch_log','jungle_log','acacia_log','dark_oak_log',
'coal_ore','iron_ore','gold_ore','diamond_ore','deepslate','clay','soul_sand',
'andesite','diorite','granite',
];
// ════════════════════════════════════════════════════════════════════════════
// CHAT DATA
// ════════════════════════════════════════════════════════════════════════════
const GENERAL_CHAT = [
'hey','yo','lol','gg','bruh','fr?','nice','wow','يلا','ماشي','اوكي','هههه',
'back','lmao','rip','bro fr','سبحان الله','omg','real??','اه والله',
'anyone here?','مين اونلاين؟','fr fr','bro 😭','lag rn tbh',
'night time already??','morning guys!','يلا نلعب','inventory full 😭',
'صباح الـ grind!!','gn everyone','who else online?',
];
const SMART_REPLIES = {
greet: ['hey!','yo!','هاي!','ايه الأخبار','wassup bro','مرحبا!','ahlan!'],
how: ['good lol','fine والله','alright','chilling','not bad tbh','تمام بحمد الله'],
bot: ['no lol im human','أنا مش bot 😂','bruh im not a bot','what?? no lol','لأ يعني 😭'],
afk: ['nah im here','مش afk','just busy bro','im good','still here tbh'],
bye: ['bye!','later bro','cya','مع السلامة','yala bye','peace ✌️'],
lag: ['yeah bad rn','same lol','الـ lag terrible','fr ping rough'],
diamond: ['y-58 يا جماعة','cave mining works','strip mine الطريقة'],
food: ['same lol جعان','need food too','farming rn','wheat soon yalla'],
night: ['scary night lol','الليل bad','mobs everywhere careful'],
death: ['F 💀','rip lol 😭','oof bro','كيف مت؟؟'],
yes: ['اه','yep','sure','facts','true'],
no: ['nah','لأ','idk','prob not'],
};
const B2B_GENERIC = [
'fr??','nice lol','ماشي','اوكي bro','هههه','seriously??','lmaooo',
'real??','bro 😭','lol yep','همممم','wait what','اه والله','fr fr',
'اه كده؟','omg','هههه اه','يسطا!','dang bro','no way!!',
];
// ════════════════════════════════════════════════════════════════════════════
// TEAM CONFIG — يُبنى من CFG عند بدء البوتات
// ════════════════════════════════════════════════════════════════════════════
function buildTeamCfg() {
const names = [CFG.bot1Name, CFG.bot2Name, CFG.bot3Name, CFG.bot4Name];
const count = Math.min(CFG.botCount, 4);
// تقسيم ذكي: لو بوتان فقط → بوت نشط + بوت مراقبة خفيف
const lightMode = count <= 2; // وضع خفيف للبوت الثاني عند 1-2 بوتات
const all = [
{
id:0, label:'حفّار', emoji:'⛏️', personality:'miner',
username: names[0],
sessionMinH:4, sessionMaxH:8, breakMinM:20, breakMaxM:60,
chatStyle:[
'بحفر deep rn','found iron!!','y-58 يا جماعة','الكهف ده ضخم جداً',
'diamonds?? maybe!!','lava everywhere يا رب 😭','coal كتير هنا',
'out of torches rip','creeper blew up my stuff 💀','cave sound scary lol',
'ancient debris maybe??','full inventory already 😭','found spawner 👀',
'skeleton almost got me لول','بحفر branch mine dude',
],
oreTargets:['coal_ore','iron_ore','copper_ore','gold_ore','diamond_ore',
'deepslate_iron_ore','deepslate_coal_ore','deepslate_copper_ore',
'deepslate_gold_ore','deepslate_diamond_ore','redstone_ore','lapis_ore'],
topicWords:['mine','dig','iron','coal','diamond','cave','ore','y-58','deepslate'],
},
{
id:1, label:'بنّاء', emoji:'🏗️', personality:'builder',
username: names[1],
sessionMinH:5, sessionMaxH:9, breakMinM:25, breakMaxM:70,
chatStyle:[
'بباني بيتي شوية شوية 🏗️','need more wood lol','anyone got stone؟',
'ran out of glass 😭','working on my farm','almost done with the roof!!',
'adding a second floor يلا','this design coming together 🔥','بعمل storage room',
'صممت حاجة حلوة النهارده','building underwater base 😱',
],
oreTargets:['oak_log','birch_log','spruce_log','jungle_log','acacia_log',
'dark_oak_log','stone','cobblestone','sand','gravel'],
topicWords:['build','wood','stone','house','base','farm','glass','craft','design'],
},
{
id:2, label:'مستكشف', emoji:'🗺️', personality:'explorer',
username: names[2],
sessionMinH:5, sessionMaxH:10, breakMinM:30, breakMaxM:90,
chatStyle:[
'بفتش على الخريطة','found a village!! 👀','this biome is SICK bro',
'found desert pyramid!!','cherry blossom biome gorgeous',
'ancient city down below scary 😱','warden almost got me omg!!',
'exploring the nether rn 🔥','this mesa biome is wild bro',
'beautiful sunset من هنا 🌅','found swamp village!! rare',
],
oreTargets:['chest','barrel','crafting_table','furnace','lectern','bookshelf'],
topicWords:['explore','village','biome','temple','coords','far','map','nether','end'],
},
{
id:3, label:'مهندس ريدستون', emoji:'🔴', personality:'redstone',
username: names[3],
sessionMinH:4, sessionMaxH:9, breakMinM:20, breakMaxM:60,
chatStyle:[
'redstone circuit almost done 🔴','making auto farm rn!!','this piston door is tricky',
'bro my flying machine broke 💀','comparator logic is wild','TNT duper working!!',
'zero-tick farm patched ugh','need more redstone ore','my sorter is finally sorting',
'observer chain going brr','made an auto smelter 🔥','slime block shenanigans',
'flush piston door complete!!','lag machine? nah just redstone 😂',
],
oreTargets:['redstone_ore','deepslate_redstone_ore','observer','piston',
'sticky_piston','redstone_torch','lever','comparator'],
topicWords:['redstone','piston','farm','auto','circuit','comparator','observer','machine'],
},
];
const result = all.slice(0, count);
// تقسيم ذكي للمهام: بوت واحد أو اثنان → الثاني يصبح مراقب خفيف
if (lightMode && result.length >= 2) {
result[1] = { ...result[1], lightBot: true,
sessionMinH: 10, sessionMaxH: 14, breakMinM: 5, breakMaxM: 10,
chatStyle: ['مراقب السيرفر 👀','كل شيء تمام 👍','السيرفر مستقر','هنا للمراقبة 🛡️'] };
}
return result;
}
// ════════════════════════════════════════════════════════════════════════════
// TASK MANAGER
// ════════════════════════════════════════════════════════════════════════════
class TaskManager {
constructor() { this._task=null; this._pri=0; this._state=STATE.IDLE; this._timer=null; }
tryAcquire(name, pri=0, tms=20000) {
if (this._task && pri<=this._pri) return false;
if (this._timer) { clearTimeout(this._timer); this._timer=null; }
this._task=name; this._pri=pri;
const n=name.toLowerCase();
if (n.includes('dig')) this._state=STATE.DIGGING;
else if (n.includes('walk')) this._state=STATE.WALKING;
else if (n.includes('sleep')) this._state=STATE.SLEEPING;
else if (n.includes('follow')) this._state=STATE.FOLLOWING;
else if (n.includes('farm')) this._state=STATE.FARMING;
else if (n.includes('cmd')) this._state=STATE.COMMAND;
else if (n.includes('eat')) this._state=STATE.EATING;
else if (n.includes('scan')) this._state=STATE.SCANNING;
else this._state=STATE.WALKING;
this._timer=setTimeout(()=>{ if(this._task===name){ this._task=null; this._pri=0; this._state=STATE.IDLE; this._timer=null; }}, tms);
return true;
}
release(name) { if(this._task===name){ if(this._timer){ clearTimeout(this._timer); this._timer=null; } this._task=null; this._pri=0; this._state=STATE.IDLE; } }
reset() { if(this._timer){ clearTimeout(this._timer); this._timer=null; } this._task=null; this._pri=0; this._state=STATE.IDLE; }
get busy() { return !!this._task; }
get state() { return this._state; }
}
// ════════════════════════════════════════════════════════════════════════════
// HELPERS
// ════════════════════════════════════════════════════════════════════════════
async function humanSleep(mn, mx) {
if (mn>=mx) { await sleep(mn); return; }
const r=Math.random(), range=mx-mn;
let ms = r<0.70 ? mn+Math.random()*range*0.50
: r<0.95 ? mn+range*0.50+Math.random()*range*0.375
: mn+range*0.875+Math.random()*range*0.125;
ms=Math.round(ms);
const chunks=Math.floor(ms/2000); let elapsed=0;
for (let i=0; i<chunks; i++) { await sleep(2000); elapsed+=2000; }
await sleep(Math.max(0,ms-elapsed));
}
async function typeMsg(bot, msg) {
if (!bot||!msg) return;
await sleep(randInt(35,90)*msg.length+randInt(80,300));
try { bot.chat(msg); } catch(_){}
}
function isChunkLoaded(bot, pos) {
if (!bot||!pos) return false;
try { const b=bot.blockAt(pos); return b!==null && b.type!==undefined; }
catch(_) { return false; }
}
async function waitForChunk(bot, pos, maxWait=6000) {
const t=Date.now();
while (Date.now()-t<maxWait) { if (isChunkLoaded(bot,pos)) return true; await sleep(400); }
return false;
}
async function smoothLook(bot, pos, steps=7, delay=55) {
if (!bot?.entity) return;
try {
const ep=bot.entity.position;
const dx=(pos.x||0)-ep.x, dy=((pos.y||ep.y)-ep.y)-1.62, dz=(pos.z||0)-ep.z;
const dist=Math.sqrt(dx*dx+dz*dz);
const ty=-Math.atan2(dx,dz), tp=-Math.atan2(dy,dist);
const sy=bot.entity.yaw, sp=bot.entity.pitch;
for (let i=1; i<=steps; i++) {
const t=i/steps, e=t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2;
let yd=ty-sy;
while(yd>Math.PI) yd-=2*Math.PI;
while(yd<-Math.PI) yd+=2*Math.PI;
await bot.look(sy+yd*e, clamp(sp+(tp-sp)*e,-Math.PI/2,Math.PI/2), false);
await sleep(Math.max(20,delay+randInt(-12,12)));
}
} catch(_){}
}
function dangerNear(bot) {
if (!bot?.entity) return false;
const pos=bot.entity.position;
for (let dx=-2; dx<=2; dx++)
for (let dy=-1; dy<=2; dy++)
for (let dz=-2; dz<=2; dz++) {
try { const b=bot.blockAt(pos.offset(dx,dy,dz)); if (b&&DANGER_BLOCKS.has(b.name)) return true; }
catch(_){}
}
return false;
}
function scanEnv(bot) {
const r={hasOre:false,hasWood:false,hasCrops:false,hasDanger:false,
hasWater:false,hasPlayers:false,playerCount:0,isUnderground:false,isNight:false};
if (!bot?.entity) return r;
try {
const pos=bot.entity.position;
r.isNight=(bot.time?.timeOfDay??0)>12000;
r.isUnderground=pos.y<60;
const nearby=Object.values(bot.players||{}).filter(p=>p.entity&&p.username!==bot.username);
r.hasPlayers=nearby.length>0; r.playerCount=nearby.length;
for (const n of ['coal_ore','iron_ore','gold_ore','diamond_ore','deepslate_iron_ore']) {
try { const b=bot.findBlock({matching:bl=>bl.name===n,maxDistance:8}); if(b){r.hasOre=true;break;} } catch(_){}
}
for (const n of ['oak_log','birch_log','spruce_log','jungle_log']) {
try { const b=bot.findBlock({matching:bl=>bl.name===n,maxDistance:6}); if(b){r.hasWood=true;break;} } catch(_){}
}
r.hasDanger=dangerNear(bot);
} catch(_){}
return r;
}
// ════════════════════════════════════════════════════════════════════════════
// TEAM CHAT LOCK
// ════════════════════════════════════════════════════════════════════════════
const teamChat = {
locked:false, speaker:null, lastMsgTime:0,
lock(id) { this.locked=true; this.speaker=id; this.lastMsgTime=Date.now(); setTimeout(()=>{ this.locked=false; },2500); },
tryLock(id) { if (this.locked&&this.speaker!==id) return false; this.lock(id); return true; },
canChat() { return Date.now()-this.lastMsgTime>1500; },
};
// ════════════════════════════════════════════════════════════════════════════
// BOT SLOT — كل بوت له حالته المعزولة
// ════════════════════════════════════════════════════════════════════════════
class BotSlot {
constructor(cfg) {
this.cfg = cfg;
this.bot = null;
this.connecting = false;
this.spawnTime = null;
this.lastAct = null;
this.reconnDelay = 15000;
this.gen = 0;
this.sessCount = 0;
this.totalUptime = 0;
this.avgPing = null;
this.chatOff = false;
this.deepSleep = false;
this.connRefused = 0;
this.tasks = new TaskManager();
this.cmdQueue = [];
this.followTarget = null;
this.envCache = {};
this.kickHistory = [];
this.pingHist = [];
this.isOp = false;
this.eating = false;
this.sleeping = false;
this._reconnTimer = null;
this._sessTimer = null;
this._keepTimer = null;
this._watchTimer = null;
this._lastVer = null;
this._deepSleepStart = null;
this.assignedTasks = []; // يُحدَّث ديناميكياً من TeamCoordinator.redistributeTasks()
this.role = BOT_ROLES[cfg.id % BOT_ROLES.length];
this.opActionsRun = false;
this._stealthNames = [
`${cfg.username}_x`,`Player${randInt(1000,9999)}`,`Craft${randInt(100,999)}`,
];
this._stealthIdx = 0;
}
log(e, m) { botLog(this.cfg.id, e, m); }
mark() { this.lastAct=Date.now(); }
alive(b,g) { return this.bot===b && this.gen===g && !!b?.entity; }
clearCtrl() {
for (const s of ['forward','back','left','right','jump','sprint','sneak'])
try { this.bot?.setControlState(s,false); } catch(_){}
this.tasks.reset();
}
getUser() { return this.cfg.username; }
// ── Plugin Detection (PLUG-01) — يعمل مع OP وبدونه ────────────────────────
async scanPlugins(bot, force=false) {
if (force) this.isOp = true;
this.log('🔍','فحص البلاجن...');
addEvent('plugin','🔍 بدأ فحص البلاجن على السيرفر');
if (this.isOp) {
// مع OP: نستخدم الأوامر المباشرة
const commands = ['/plugins','/pl','/pluginlist'];
for (const cmd of commands) {
try { bot.chat(cmd); await sleep(3000); break; } catch(_) {}
}
} else {
// بدون OP: نطلب المعلومات من الـ Server Brand + version command
try { bot.chat('/version'); await sleep(2000); } catch(_){}
try { bot.chat('/help'); await sleep(2000); } catch(_){}
// نستمع للردود في parsePluginsFromChat تلقائياً
this.log('🔍','فحص سلبي بدون OP (نستمع لردود الشات)');
}
}
parsePluginsFromChat(msg) {
const lo = msg.toLowerCase();
const cleaned = msg.replace(/\u00a7./g,'').replace(/[§❌✅]/g,'');
// كشف مباشر من قائمة البلاجن
const isPluginList = lo.includes('plugin') || lo.includes('pl:') || lo.includes('pl ') || lo.includes('/version') || lo.includes('running');
if (isPluginList) {
const parts = cleaned.split(/[\,\s]+/);
const found = [];
for (const part of parts) {
const clean = part.trim().replace(/[\[\]()]/g,'');
if (clean.length < 2 || clean.length > 40) continue;
if (/^\d+$/.test(clean)) continue;
if (['plugins','plugin','list','pl','enabled','disabled','total','server','running','version','bukkit','spigot','paper'].includes(clean.toLowerCase())) continue;
found.push(clean);
}
if (found.length > 0) {
const analyzed = found.map(n => analyzePlugin(n));
const newPlugins = analyzed.filter(p => !detectedPlugins.some(e => e.name.toLowerCase()===p.name.toLowerCase()));
if (newPlugins.length > 0) {
detectedPlugins.push(...newPlugins);
addEvent('plugin',`✅ اكتُشف ${newPlugins.length} بلاجن جديدة`);
this.log('🔌',`اكتُشف ${detectedPlugins.length} بلاجن إجمالاً`);
this.analyzePerformancePlugins();
}
}
}
// كشف سلبي: بلاجن معروفة تظهر في رسائل الشات العادية
const PASSIVE_PLUGINS = [
['essentials','EssentialsX'],['worldguard','WorldGuard'],['worldedit','WorldEdit'],
['luckperms','LuckPerms'],['vault','Vault'],['clearlag','ClearLag'],
['coreprotect','CoreProtect'],['dynmap','Dynmap'],['griefprevention','GriefPrevention'],
['shopguiplus','ShopGUI+'],['mcmmo','mcMMO'],['aureliumskills','AureliumSkills'],
['discordsrv','DiscordSRV'],['geyser','Geyser'],['floodgate','Floodgate'],
['spark','spark'],['lagg','NoLagg'],['anticheat','AntiCheat'],
];
for (const [key, name] of PASSIVE_PLUGINS) {
if (lo.includes(key) && !detectedPlugins.some(p=>p.name.toLowerCase()===name.toLowerCase())) {
const analyzed = analyzePlugin(name);
detectedPlugins.push(analyzed);
addEvent('plugin',`🔎 كُشف سلبياً: ${name}`);
}
}
}
analyzePerformancePlugins() {
const perf = detectedPlugins.filter(p => p.cat==='performance' || p.cat==='afk');
if (perf.length > 0) {
addEvent('perf',`🚀 بلاجن أداء مكتشفة: ${perf.map(p=>p.name).join(', ')}`);
this.log('🚀',`بلاجن أداء: ${perf.map(p=>p.name).join(', ')}`);
}
const hasAntiAfk = detectedPlugins.some(p=>p.cat==='afk');
if (hasAntiAfk) {
addEvent('info','✅ بلاجن anti-AFK موجودة — البوتات محمية من الـ kick');
}
}
// ── Performance Monitor (PERF-01) ────────────────────────────────────────
async monitorPerformance(bot, gen) {
while (this.alive(bot, gen)) {
try {
await sleep(60000);
if (!this.alive(bot, gen)) continue;
performanceData.lastCheck = tsNow();
// اقرأ TPS — مع OP بأمر /tps، وبدون OP نستمع للشات
if (this.isOp) {
try { bot.chat('/tps'); } catch(_){}
await sleep(2000);
}
// مراقبة أداء البوت نفسه (ping) — تعمل مع/بدون OP
const ping = this.avgPing || 0;
if (ping > 500 || performanceData.lag) {
this.log('⚠️','اكتُشف lag عالي (ping: ' + ping + 'ms)');
addEvent('perf',`⚠️ Ping عالي: ${ping}ms — السيرفر يعاني lag`);
// لو عندنا OP: نطبق التحسينات التلقائية
if (this.isOp) await this.optimizePerformance(bot);
// بدون OP: البوت ينتظر ويخفف نشاطه (Eco Mode مفعّل من parseTpsFromChat)
}
} catch(_) { await sleep(30000); }
}
}
async optimizePerformance(bot) {
if (!this.isOp) return;
const cmds = [];
// بناءً على البلاجن المكتشفة
const hasClearLag = detectedPlugins.some(p=>p.name.toLowerCase().includes('clearlag'));
const hasSpark = detectedPlugins.some(p=>p.name.toLowerCase().includes('spark'));
const hasLagg = detectedPlugins.some(p=>p.name.toLowerCase().includes('lagg'));
if (hasClearLag) cmds.push('/lagg clear');
if (hasLagg) cmds.push('/lagg clear');
if (hasSpark) cmds.push('/spark gc');
// أوامر عامة
cmds.push('/minecraft:kill @e[type=item]');
for (const cmd of cmds) {
try {
bot.chat(cmd);
await sleep(1500);
addEvent('perf',`⚡ تم تنفيذ: ${cmd}`);
} catch(_) {}
}
}
parseTpsFromChat(msg) {
const m = msg.match(/tps[:\s]+([0-9.]+)/i) ||
msg.match(/([0-9.]+)\s*tps/i) ||
msg.match(/TPS[^0-9]*([0-9.]+)/);
if (m) {
const tps = parseFloat(m[1]);
if (!isNaN(tps) && tps >= 0 && tps <= 22) {
performanceData.tps = tps;
performanceData.lag = tps < 15;
performanceData.history.push({ time: tsNow(), tps });
if (performanceData.history.length > 30) performanceData.history.shift();
// ── ECO MODE: تخفيف ذكي عند انخفاض TPS ──────────────────────────────
if (tps < 14) {
performanceData.ecoCount = Math.min(5, (performanceData.ecoCount || 0) + 1);
if (!performanceData.ecoMode && performanceData.ecoCount >= 2) {
performanceData.ecoMode = true;
sysLog('🌿','ECO MODE مفعّل — تخفيف نشاط البوتات لتقليل الضغط على السيرفر');
addEvent('perf',`🌿 ECO MODE: TPS=${tps.toFixed(1)} — البوتات تعمل بحد أدنى`);
}
addEvent('perf',`⚠️ TPS منخفض: ${tps.toFixed(1)} — السيرفر يعاني من lag`);
} else if (tps >= 18) {
performanceData.ecoCount = Math.max(0, (performanceData.ecoCount || 0) - 1);
if (performanceData.ecoMode && performanceData.ecoCount === 0) {
performanceData.ecoMode = false;
sysLog('✅','ECO MODE منتهي — TPS عاد لطبيعي، البوتات تعمل بكامل طاقتها');
addEvent('perf',`✅ ECO MODE منتهي: TPS=${tps.toFixed(1)} — عودة للنشاط الكامل`);
}
}
}
}
}
// ── Kick Analyzer ──────────────────────────────────────────────────────
analyzeKick(raw) {
let text=raw||'';
try { const p=JSON.parse(raw); text=p.text||p.translate||raw; } catch(_){}
const lo=text.toLowerCase();
const isBot = /\bbot\b|automated|captcha|verification|suspicious|\bdetected\b|\bhuman\b/.test(lo);
const isBan = /\bbanned\b|ip.?ban|suspended/.test(lo);
const isStop = /server.*stop|shutdown|restart|closing/.test(lo);
const isFull = /server is full|no slot/.test(lo);
const isSess = /logged in from|already connected|duplicate/.test(lo);
const isVer = /outdated|version|protocol|incompatible/.test(lo);
let delay = null;
if (isBan) delay = 8*60000+randInt(0,120000);
if (isStop) delay = randInt(25000,45000);
if (isFull) delay = randInt(90000,180000);
if (isSess) delay = randInt(5*60000,8*60000);
return { text, isBot, isBan, isStop, isFull, isSess, isVer, delay };
}
// ── Reconnect ──────────────────────────────────────────────────────────
scheduleReconnect(delay) {
if (this.deepSleep) return; // FIX-2: لا إعادة اتصال في السبات
if (this._reconnTimer) { clearTimeout(this._reconnTimer); this._reconnTimer=null; }
const d = delay ?? Math.min(this.reconnDelay, 120000);
this.reconnDelay = Math.min(this.reconnDelay * 1.5, 120000);
this.log('⏳',`إعادة الاتصال خلال ${Math.round(d/1000)}ث`);
this._reconnTimer = setTimeout(() => {
this._reconnTimer=null;
if (!this.deepSleep && !this.bot && !this.connecting) this.connect();
}, d);
}
// ── CONNECT ────────────────────────────────────────────────────────────
async connect() {
if (this.deepSleep) return;
if (this.bot || this.connecting) return;
// FIX-WATCH: تنظيف watchTimer القديم قبل اتصال جديد
if (this._watchTimer) { clearInterval(this._watchTimer); this._watchTimer = null; }
this.connecting=true;
this.log('🔗',`اتصال → ${CFG.host}:${CFG.port}`);
addEvent('connect',`🔗 ${this.cfg.username} يحاول الاتصال`);
let ver = resolveVersion(CFG.version) || autoVersion || fallbackVersionList[0];
if (!ver || ver==='auto') {
ver = await detectServerVersion(CFG.host, CFG.port) || fallbackVersionList[0];
}
if (!autoVersion) autoVersion=ver;
this._lastVer = ver;
this.log('🎮',`الإصدار: ${ver}`);
this.gen++;
const g=this.gen;
let bot;
try {
bot = mineflayer.createBot({
host: CFG.host, port: CFG.port, username: this.getUser(),
version: ver, disableChatSigning: true,
checkTimeoutInterval: 60000, hideErrors: true,
auth: 'offline',
});
} catch (e) {
this.connecting=false;
this.log('❌',`فشل الإنشاء: ${e.message}`);
this.scheduleReconnect(30000);
return;
}
this.bot=bot;
this.connecting=false;
// ANTI-01: حماية من الـ timeout
const connTimer = setTimeout(() => {
if (!bot.entity) {
this.log('⏱️','timeout اتصال');
try { bot.quit(); } catch(_){}
}
}, 45000);
bot.on('login', () => {
clearTimeout(connTimer);
this.reconnDelay=15000;
this.connRefused=0;
this.spawnTime=Date.now();
this.sessCount++;
this.isOp=false;
this.log('✅',`متصل | جلسة #${this.sessCount} | إصدار: ${ver}`);
addEvent('connect',`✅ ${this.cfg.username} اتصل بنجاح (إصدار ${ver})`);
serverInfo.version=ver;
try { bot.loadPlugin(pathfinder); bot.pathfinder.setMovements(new Movements(bot)); } catch(_){}
// بدء الحلقات
this._startLoops(bot, g);
// جلسة تلقائية
const sessLen = randInt(
this.cfg.sessionMinH * 3600000,
this.cfg.sessionMaxH * 3600000
);
this._sessTimer = setTimeout(() => {
this.log('💤',`انتهت الجلسة — استراحة`);
addEvent('session',`💤 ${this.cfg.username} يأخذ استراحة`);
this.totalUptime += Math.round((Date.now()-(this.spawnTime||Date.now()))/1000);
this.spawnTime=null;
try { bot.quit(); } catch(_){}
const breakLen = randInt(this.cfg.breakMinM, this.cfg.breakMaxM)*60000;
this._reconnTimer = setTimeout(()=>{ this._reconnTimer=null; this.connect(); }, breakLen);
}, sessLen);
});
bot.on('spawn', () => {
this.log('🌍','spawn');
this.mark();
this.startKeepAlive(bot, g);
// بعد spawn بـ 10 ثواني: فحص البلاجن لو OP
setTimeout(() => this.scanPlugins(bot), 10000);
});
bot.on('chat', (username, message) => {
if (username === bot.username) return;
this.handleChat(username, message, bot, g);
this.parsePluginsFromChat(message);
this.parseTpsFromChat(message);
// معلومات السيرفر
const lo = message.toLowerCase();
const isOpMsg =
lo.includes('you are now an operator') ||
lo.includes('granted op') ||
lo.includes('op level') ||
lo.includes('made') && lo.includes('server operator') ||
lo.includes('you have been granted op') ||
lo.includes('granted operator') ||
(username==='' && /\bop\b/.test(lo) && !/\bshop\b|\bdrop\b|\btop\b|\bcrop\b/.test(lo));
if (isOpMsg) {
this.isOp=true;
this.log('👑','حصل على OP!');
addEvent('op',`👑 ${this.cfg.username} حصل على OP — بدء فحص البلاجن`);
setTimeout(() => this.scanPlugins(bot), 2000);
setTimeout(() => this.doOPActions(bot), 6000); // OP-OPT: تحسينات السيرفر التلقائية
}
// رسائل النظام — كشف TPS
if (username==='') {
this.parseTpsFromChat(message);
}
});
// FIX-3: كشف OP من كل مصادر الرسائل الممكنة
const detectOpText = (raw) => {
if (this.isOp) return; // بالفعل عنده OP
const tlo = raw.toLowerCase().replace(/[§\u00a7]./g,'').replace(/[""]/g,'"');
const isOp =
tlo.includes('you are now an operator') ||
tlo.includes('granted op') ||
tlo.includes('you have been granted op') ||
tlo.includes('made') && tlo.includes('server operator') ||
tlo.includes('granted operator') ||
(tlo.includes('op') && tlo.includes('now') && (tlo.includes('you') || tlo.includes('are')));
if (isOp) {
this.isOp=true;
this.log('👑','كُشف OP!');
addEvent('op',`👑 ${this.cfg.username} حصل على OP — جاري فحص البلاجن والإعداد`);
setTimeout(()=>this.scanPlugins(bot),2000);
setTimeout(()=>this.doOPActions(bot),5000); // FIX: أضف doOPActions هنا أيضاً
}
};
bot.on('message', (jsonMsg) => {
try {
// طريقة 1: toString() العادية
const t1 = jsonMsg.toString();
detectOpText(t1);
this.parsePluginsFromChat(t1.toLowerCase());
this.parseTpsFromChat(t1);
// طريقة 2: text مباشر من الـ JSON
try {
const j = jsonMsg.json || jsonMsg;
if (j && j.text) detectOpText(j.text);
if (j && j.translate) detectOpText(j.translate);
// استخراج extra
if (j && Array.isArray(j.extra)) {
j.extra.forEach(ex => { if(ex && ex.text) detectOpText(ex.text); });
}
} catch(_){}
} catch(_){}
});
// FIX-3: Raw packet listener — يمسك أي صيغة من Aternos
try {
bot._client.on('chat', (packet) => {
try {
const raw = typeof packet.message==='string'
? packet.message
: JSON.stringify(packet.message||'');
detectOpText(raw);
// حاول extract نص من JSON مباشرة
try {
const obj = JSON.parse(raw);
if (obj.text) detectOpText(obj.text);
if (obj.translate) detectOpText(obj.translate);
} catch(_){}
} catch(_){}
});
} catch(_){}
bot.on('kicked', (reason) => {
clearTimeout(connTimer);
const k = this.analyzeKick(reason);
this.log('🚫',`طُرد: ${k.text.slice(0,80)}`);
addEvent('kick',`🚫 ${this.cfg.username} طُرد: ${k.text.slice(0,60)}`);
this.kickHistory.unshift({ time: tsNow(), reason: k.text.slice(0,80) });
if (this.kickHistory.length>5) this.kickHistory.length=5;
// VER-02: إذا كان السبب version mismatch جرّب الإصدار التالي في القائمة
if (k.isVer) {
const curIdx = fallbackVersionList.indexOf(this._lastVer || '');
const nextVer = curIdx >= 0 ? fallbackVersionList[curIdx + 1] : null;
if (nextVer) {
this.log('🔄',`إصدار غير متوافق — محاولة: ${nextVer}`);
addEvent('connect',`🔄 إصدار بديل: ${nextVer}`);
autoVersion = nextVer;
}
}
this._cleanup(g);
const delay = k.delay ?? this.reconnDelay;
this.scheduleReconnect(delay);
});
bot.on('end', (reason) => {
clearTimeout(connTimer);
this.log('🔌',`انقطع: ${reason||'unknown'}`);
addEvent('disconnect',`🔌 ${this.cfg.username} انقطع: ${reason||'unknown'}`);
this._cleanup(g);
if (!this._reconnTimer) this.scheduleReconnect();
});
// ROLE-01: سجل اللاعبين — تتبع الدخول والخروج
bot.on('playerJoined', (player) => {
if (player.username === bot.username) return;
if (coord?.slots.some(s => s.cfg.username === player.username)) return;
const now = tsNow();
const existing = playerRegistry.get(player.username);
if (!existing) {
playerRegistry.set(player.username, {
firstSeen: now, lastSeen: now, visits: 1,
joinedAt: Date.now(), lastChat: null,
});
addEvent('info', `👋 لاعب جديد دخل: ${player.username}`);
// الدبلوماسي يرحّب
// SEC: تصفية اسم اللاعب قبل وضعه في رسالة الشات
const safeUser = player.username.replace(/[^\x20-\x7E\u0600-\u06FF]/g,'').slice(0,16);
const dip = coord?.slots.find(s => s.role?.id === 'diplomat' && s.bot);
const welcomeBot = dip || (this.role?.id === 'diplomat' ? this : null);
if (welcomeBot?.bot) {
setTimeout(() => {
try { welcomeBot.bot?.chat(`أهلاً يا ${safeUser}! 👋 مرحباً بك في السيرفر!`); } catch(_){}
}, randInt(3000, 8000));
}
} else {
existing.lastSeen = now;
existing.visits++;
existing.joinedAt = Date.now();
existing.lastChat = null;
const safeUser = player.username.replace(/[^\x20-\x7E\u0600-\u06FF]/g,'').slice(0,16);
if (existing.visits > 1) addEvent('info', `🎮 ${player.username} عاد للسيرفر (زيارة #${existing.visits})`);
const dip = coord?.slots.find(s => s.role?.id === 'diplomat' && s.bot);
if (dip?.bot && existing.visits > 1) {
setTimeout(() => {
try { dip.bot?.chat(`يا هلا يا ${safeUser}! 🎮 زيارة رقم ${existing.visits}`); } catch(_){}
}, randInt(4000, 9000));
}
}
serverInfo.players = Object.keys(bot.players || {}).length;
});
bot.on('playerLeft', (player) => {
if (player.username === bot.username) return;
const reg = playerRegistry.get(player.username);
if (reg) { reg.lastSeen = tsNow(); reg.joinedAt = null; }
serverInfo.players = Math.max(0, (serverInfo.players || 1) - 1);
});
bot.on('error', err => {
const c = err.code||err.message||'';
if (/ECONNREFUSED|EHOSTUNREACH|ETIMEDOUT/.test(c)) {
this.connRefused++;
if (this.connRefused >= 3) {
this.deepSleep=true;
this._deepSleepStart = Date.now();
const d=randInt(5*60000,12*60000);
this.log('😴',`DeepSleep ${Math.round(d/60000)} دقيقة`);
addEvent('sleep',`😴 ${this.cfg.username} DeepSleep — السيرفر غير متاح`);
// ANTI-02: تشغيل Aternos تلقائياً عند السبات العميق
if (!aternosRunning) triggerAternos();
setTimeout(()=>{
this.deepSleep=false;
this._deepSleepStart=null;
this.connRefused=0;
this.scheduleReconnect(5000);
}, d);
return;
}
}
this.log('❌',`خطأ: ${err.message}`);
});
// Ping monitor — ANTI-01
bot.on('physicsTick', () => {
const p = bot.player?.ping;
if (p!=null) {
this.pingHist.push(p);
if (this.pingHist.length>10) this.pingHist.shift();
this.avgPing = Math.round(this.pingHist.reduce((a,b)=>a+b,0)/this.pingHist.length);
}
this.mark();
});
// ANTI-01: مراقبة الـ ticks — إذا توقف الـ bot نعيد الاتصال
this._watchTimer = setInterval(() => {
if (!this.bot || this.deepSleep) return;
const idle = Date.now() - (this.lastAct || Date.now());
if (idle > 5*60000 && this.bot?.entity) {
this.log('🔄','watchdog: لا نشاط → إعادة تشغيل keep-alive');
this.mark();
try { bot.swingArm(); } catch(_){}
}
}, 60000);
}
_cleanup(gen) {
if (this._sessTimer) { clearTimeout(this._sessTimer); this._sessTimer=null; }
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer=null; }
if (this._watchTimer) { clearInterval(this._watchTimer); this._watchTimer=null; }
if (this.gen===gen) {
this.totalUptime += Math.round((Date.now()-(this.spawnTime||Date.now()))/1000);
this.spawnTime=null; this.bot=null; this.isOp=false;
this.eating=false; // FIX: reset eating lock on disconnect
this.opActionsRun=false; // FIX: allow doOPActions on next OP grant
this.tasks.reset(); this.cmdQueue=[];
}
}
_startLoops(bot, g) {
this._runLoop('personality', ()=>this.personalityLoop(bot,g));
this._runLoop('chat', ()=>this.chatLoop(bot,g));
this._runLoop('hunger', ()=>this.hungerLoop(bot,g));
this._runLoop('sleep', ()=>this.sleepLoop(bot,g));
this._runLoop('cmd', ()=>this.cmdLoop(bot,g));
this._runLoop('follow', ()=>this.followLoop(bot,g));
this._runLoop('perf', ()=>this.monitorPerformance(bot,g));
this._runLoop('role', ()=>this.roleLoop(bot,g));
}
_runLoop(name, fn) {
fn().catch(e => {
if (!/EPIPE|ETIMEDOUT|ECONNRESET/.test(e.message||''))
this.log('⚠️',`Loop ${name} crashed: ${e.message}`);
});
}
// ── Real Player Activity Loop — FIX-1 ────────────────────────────────
// بدل الـ AFK micro-movements، البوت بيعمل أنشطة حقيقية زي لاعب فعلي
startKeepAlive(bot, gen) {
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer=null; }
const self = this;
async function realPlayerActivity() {
if (!self.bot || self.gen!==gen || !bot.entity || self.tasks.busy) return;
self.mark();
const r = Math.random();
// 1) مشي حقيقي لمسافة معقولة (8-25 block) ثم إكمال طريق
if (r < 0.30 && bot.pathfinder) {
if (!self.tasks.tryAcquire('keep_walk',0,25000)) return;
try {
const pos = bot.entity.position;
const angle = Math.random() * Math.PI * 2;
const dist = randInt(20, 60);
const tx = pos.x + Math.cos(angle)*dist;
const tz = pos.z + Math.sin(angle)*dist;
bot.pathfinder.setGoal(new goals.GoalNear(tx, pos.y, tz, 2), true);
// امشي 5-12 ثانية ثم وقّف
await sleep(randInt(5000, 12000));
try { bot.pathfinder.setGoal(null); } catch(_){}
// انتظر 2-5 ثانية في المكان (لاعب حقيقي بيوقف ويبص)
await sleep(randInt(2000, 5000));
self.clearCtrl();
// ارجع لنقطة قريبة من البداية
if (self.alive(bot,gen) && !self.tasks.busy) {
bot.pathfinder.setGoal(new goals.GoalNear(pos.x, pos.y, pos.z, 3), true);
await sleep(randInt(5000, 10000));
try { bot.pathfinder.setGoal(null); } catch(_){}
self.clearCtrl();
}
} catch(_){ self.clearCtrl(); }
finally { self.tasks.release('keep_walk'); }
}
// 2) حفر وجمع — يضرب التراب/الحجر أمامه
else if (r < 0.52 && bot.entity) {
if (!self.tasks.tryAcquire('keep_mine',0,18000)) return;
try {
const yaw = bot.entity.yaw;
// اتجه لمكان ثم "يحفر" (swing arm متواصل)
await bot.look(yaw + randFloat(-0.5,0.5), randFloat(-0.6,-0.1), false);
await sleep(randInt(500,1200));
const swings = randInt(5, 15);
for (let i=0; i<swings && self.alive(bot,gen); i++) {
try { bot.swingArm(); } catch(_){}
// بعد كل ضربة ينظر لنفس المكان بتذبذب طبيعي
await bot.look(yaw + randFloat(-0.3,0.3), randFloat(-0.7,-0.1), false);
await sleep(randInt(350, 650)); // وقت طبيعي بين الضربات
}
// خطوتين للأمام بعد "الحفر"
bot.setControlState('forward', true);
await sleep(randInt(400, 900));
bot.setControlState('forward', false);
self.clearCtrl();
await sleep(randInt(1500, 3000)); // استراحة بعد الحفر
} catch(_){ self.clearCtrl(); }
finally { self.tasks.release('keep_mine'); }
}
// 3) نظر طبيعي للبيئة — يبص حوالين كأنه بيستكشف
else if (r < 0.72) {
const startYaw = bot.entity?.yaw || 0;
const steps = randInt(4, 9);
for (let i=0; i<steps && self.alive(bot,gen); i++) {
try {
const newYaw = startYaw + randFloat(-Math.PI*0.8, Math.PI*0.8);
const newPitch = randFloat(-0.7, 0.3);
await bot.look(newYaw, newPitch, false);
await sleep(randInt(600, 1800)); // وقت طبيعي بين النظرات
} catch(_){ break; }
}
// بعد الاستكشاف خطوتين
if (self.alive(bot,gen) && !self.tasks.busy && Math.random()<0.4) {
try{
const dir = pick(['forward','back','left','right']);
bot.setControlState(dir, true);
await sleep(randInt(600, 1400));
bot.setControlState(dir, false);
self.clearCtrl();
} catch(_){ self.clearCtrl(); }
}
}
// 4) يرتب inventory — يغيّر الـ hotbar زي لاعب بيفكر في السلاح
else if (r < 0.86) {
const slots = randInt(3, 7);
for (let i=0; i<slots && self.alive(bot,gen); i++) {
try { bot.setQuickBarSlot(randInt(0,8)); } catch(_){}
await sleep(randInt(400, 1200));
if (Math.random() < 0.5) {
try { bot.swingArm(); } catch(_){}
await sleep(randInt(200, 500));
}
}
// ينظر لأسفل زي مفتش في صندوق أو crafting
try { await bot.look(bot.entity?.yaw||0, 0.8, false); } catch(_){}
await sleep(randInt(800, 2000));
}
// 5) نقر ورجوع — Crouch وامشي زي حذر
else {
if (!self.tasks.tryAcquire('keep_sneak',0,8000)) return;
try {
bot.setControlState('sneak', true);
const dir = pick(['forward','left','right']);
bot.setControlState(dir, true);
await sleep(randInt(1500, 4000));
bot.setControlState(dir, false);
bot.setControlState('sneak', false);
self.clearCtrl();
await sleep(randInt(1000, 2500));
// jump واحد طبيعي
bot.setControlState('jump', true);
await sleep(250);
bot.setControlState('jump', false);
self.clearCtrl();
} catch(_){ self.clearCtrl(); }
finally { self.tasks.release('keep_sneak'); }
}
}
// شغّل النشاط — ECO MODE: 120-180ث | عادي: 50-100ث | lightBot: 150-220ث
const interval = this.cfg.lightBot ? randInt(150000, 220000)
: performanceData.ecoMode ? randInt(120000, 180000)
: randInt(50000, 100000);
this._keepTimer = setInterval(() => {
// في ECO MODE أو lightBot: نتجنب المشي والحفر (أثقل الأنشطة)
if (performanceData.ecoMode || this.cfg.lightBot) {
if (!self.bot || !self.bot.entity) return;
self.mark();
// فقط نظرة عشوائية لإثبات الحياة
const yaw = self.bot.entity?.yaw || 0;
self.bot.look(yaw + randFloat(-1, 1), randFloat(-0.3, 0.2), false).catch(_=>{});
if (Math.random() < 0.3) { try { self.bot.swingArm(); } catch(_){} }
} else {
realPlayerActivity().catch(_=>{});
}
}, interval);
}
// ── Personality Loop ──────────────────────────────────────────────────
async personalityLoop(bot, gen) {
await humanSleep(randInt(5000,20000), randInt(20000,40000));
while (this.alive(bot, gen)) {
try {
// ECO MODE أو lightBot: فترات أطول بكثير
const minW = (performanceData.ecoMode || this.cfg.lightBot) ? 300000 : 90000;
const maxW = (performanceData.ecoMode || this.cfg.lightBot) ? 600000 : 360000;
await humanSleep(minW, maxW);
if (!this.alive(bot,gen)||!bot.entity) continue;
if (this.tasks.busy) { await sleep(15000); continue; }
// ECO MODE: نتخطى الحركات الثقيلة (pathfinder) كلياً
if (performanceData.ecoMode || this.cfg.lightBot) {
try { await bot.look(bot.entity.yaw+randFloat(-0.5,0.5), randFloat(-0.3,0.2), false); } catch(_){}
await sleep(randInt(2000,5000));
continue;
}
if (!this.tasks.tryAcquire('personality',0,60000)) continue;
try {
this.mark();
const cfg=this.cfg;
const env=scanEnv(bot);
this.envCache=env;
// حركة عشوائية
if (!dangerNear(bot)&&bot.pathfinder) {
const pos=bot.entity.position;
const tx=pos.x+randFloat(-60,60), ty=pos.y, tz=pos.z+randFloat(-60,60);
try {
bot.pathfinder.setGoal(new goals.GoalNear(tx,ty,tz,3),true);
await sleep(randInt(3000,8000));
try { bot.pathfinder.setGoal(null); } catch(_){}
} catch(_){}
}
// نظرة عشوائية
const sy=bot.entity?.yaw||0;
await bot.look(sy+randFloat(-2,2),randFloat(-0.5,0.5),false);
await sleep(randInt(500,1500));
// حفر (للحفّار)
if (cfg.personality==='miner'&&env.hasOre&&!dangerNear(bot)) {
for (const name of cfg.oreTargets) {
try {
const b=bot.findBlock({matching:bl=>bl.name===name,maxDistance:6});
if (b&&isChunkLoaded(bot,b.position)) {
await smoothLook(bot,b.position.offset(0.5,0.5,0.5),7,55);
await sleep(randInt(500,1000));
await bot.dig(b,true);
await sleep(randInt(300,700));
break;
}
} catch(_){}
}
}
} finally { this.clearCtrl(); this.tasks.release('personality'); }
} catch(_) { this.clearCtrl(); this.tasks.release('personality'); await sleep(5*60000); }
}
}
// ── Chat Loop ─────────────────────────────────────────────────────────
async chatLoop(bot, gen) {
await humanSleep(randInt(60000,180000), randInt(180000,360000));
while (this.alive(bot,gen)) {
try {
// ECO MODE: شات أقل كثيراً (البوت يصمت معظم الوقت)
const minW = performanceData.ecoMode ? 15*60000 : this.cfg.lightBot ? 10*60000 : 4*60000;
const maxW = performanceData.ecoMode ? 30*60000 : this.cfg.lightBot ? 20*60000 : 18*60000;
await humanSleep(minW, maxW);
if (!this.alive(bot,gen)||this.chatOff||this.deepSleep) continue;
if (!teamChat.tryLock(this.cfg.id)) continue;
this.mark();
const msg = pick([...this.cfg.chatStyle,...GENERAL_CHAT.slice(0,15)]);
await typeMsg(bot, msg);
addEvent('chat',`💬 ${bot.username}: ${msg}`);
} catch(_) { await sleep(3*60000); }
}
}
// ── Hunger Loop ───────────────────────────────────────────────────────
async hungerLoop(bot, gen) {
await sleep(randInt(15000, 25000));
while (this.alive(bot, gen)) {
try {
await sleep(randInt(8000, 15000));
if (!this.alive(bot, gen) || !bot.entity) continue;
const food = typeof bot.food === 'number' ? bot.food : 20;
// ── حالة طوارئ: جوع حاد + OP → أعطِ نفسك طعاماً ──────────────────
if (food < 8 && this.isOp && !this.eating) {
try {
bot.chat('/give @s cooked_beef 32');
addEvent('info', `🥩 ${this.cfg.username} أعطى نفسه أكل (جوع حاد)`);
await sleep(2500);
} catch(_) {}
}
// ── أكل عادي من الـ inventory ───────────────────────────────────────
if (food < 14 && !this.eating) {
this.eating = true;
try {
const item = bot.inventory?.items().find(i =>
FOOD_NAMES.some(n => i.name.includes(n))
);
if (item) {
await bot.equip(item, 'hand');
await sleep(300);
await bot.consume();
this.mark();
} else if (this.isOp) {
// لا أكل في الـ inventory + عنده OP → أعطِ نفسه
bot.chat('/give @s bread 32');
await sleep(2500);
// حاول أكل بعد الاستلام
const fresh = bot.inventory?.items().find(i =>
FOOD_NAMES.some(n => i.name.includes(n))
);
if (fresh) {
await bot.equip(fresh, 'hand');
await sleep(300);
await bot.consume();
this.mark();
}
}
} catch(_) {
// فشل الأكل — reset وحاول بعدين
} finally {
this.eating = false;
}
}
} catch(_) { await sleep(15000); }
}
}
// ── Sleep Loop ────────────────────────────────────────────────────────
async sleepLoop(bot, gen) {
while (this.alive(bot,gen)) {
try {
await sleep(randInt(25000,40000));
if (!this.alive(bot,gen)||!bot.entity) continue;
let tod=0; try { tod=bot.time?.timeOfDay??0; } catch(_){}
if (tod>12600&&tod<23000&&!this.sleeping) {
this.mark();
let bed=null;
try { bed=bot.findBlock({matching:bl=>bl.name.includes('_bed'),maxDistance:10}); } catch(_){}
if (bed&&isChunkLoaded(bot,bed.position)) {
this.sleeping=true;
if (this.tasks.tryAcquire('sleep',1,65000)) {
try {
if (bot.pathfinder&&!dangerNear(bot)) {
bot.pathfinder.setGoal(new goals.GoalNear(bed.position.x,bed.position.y,bed.position.z,1),true);
await sleep(3000); try{bot.pathfinder.setGoal(null);}catch(_){}
}
await smoothLook(bot,bed.position.offset(0.5,0.5,0.5),6,60);
await sleep(randInt(300,600));
await bot.sleep(bed);
await sleep(randInt(15000,45000));
try{await bot.wake();}catch(_){}
this.sleeping=false;
} catch(_){this.sleeping=false;}
finally{this.clearCtrl();this.tasks.release('sleep');}
}
}
}
} catch(_){await sleep(30000);}
}
}
// ── Command Loop ──────────────────────────────────────────────────────
async cmdLoop(bot, gen) {
while (this.alive(bot,gen)) {
try {
await sleep(400);
if (this.cmdQueue.length===0) continue;
const cmd=this.cmdQueue.shift();
if (!this.alive(bot,gen)) continue;
const player=Object.values(bot.players||{}).find(p=>p.username.toLowerCase()===cmd.sender.toLowerCase()&&p.entity);
this.log('⚙️',`أمر [${cmd.type}] من ${cmd.sender}`);
switch(cmd.type) {
case 'come': {
if (!player?.entity) { if(teamChat.tryLock(this.cfg.id))await typeMsg(bot,'where are you? cant see u'); break; }
if (!this.tasks.tryAcquire('cmd_come',2,25000)) break;
try {
const pp=player.entity.position;
if (teamChat.tryLock(this.cfg.id)) await typeMsg(bot,pick(['coming!','on my way','omw']));
if (!dangerNear(bot)&&bot.pathfinder) {
bot.pathfinder.setGoal(new goals.GoalNear(pp.x,pp.y,pp.z,2),true);
await sleep(15000); try{bot.pathfinder.setGoal(null);}catch(_){}
}
} finally{this.clearCtrl();this.tasks.release('cmd_come');}
break;
}
case 'stop': {
this.tasks.reset(); try{bot.pathfinder.setGoal(null);}catch(_){}
this.clearCtrl(); this.followTarget=null;
if(teamChat.tryLock(this.cfg.id))await typeMsg(bot,pick(['ok stopped','sure','ok']));
break;
}
case 'follow': {
this.followTarget=cmd.sender;
if(teamChat.tryLock(this.cfg.id))await typeMsg(bot,pick(['following you!','ok following']));
break;
}
case 'plugins': {
await this.scanPlugins(bot);
if(teamChat.tryLock(this.cfg.id))await typeMsg(bot,`Scanning plugins... found ${detectedPlugins.length} so far`);
break;
}
case 'optimize': {
await this.optimizePerformance(bot);
if(teamChat.tryLock(this.cfg.id))await typeMsg(bot,'Optimization commands sent!');
break;
}
}
} catch(_){await sleep(2000);}
}
}
// ── Follow Loop ───────────────────────────────────────────────────────
async followLoop(bot, gen) {
while (this.alive(bot,gen)) {
try {
await sleep(1500);
if (!this.followTarget||!bot.entity) continue;
if (this.tasks.busy&&this.tasks.state!==STATE.FOLLOWING){await sleep(3000);continue;}
const player=Object.values(bot.players||{}).find(p=>p.username===this.followTarget&&p.entity);
if (!player?.entity) continue;
const dist=bot.entity.position.distanceTo(player.entity.position);
if (dist>5) {
if (!this.tasks.tryAcquire('follow',0,6000)){await sleep(2000);continue;}
try {
if (!dangerNear(bot)&&bot.pathfinder) {
const pp=player.entity.position;
bot.pathfinder.setGoal(new goals.GoalNear(pp.x,pp.y,pp.z,3),true);
await sleep(2500); try{bot.pathfinder.setGoal(null);}catch(_){}
}
} finally{this.clearCtrl();this.tasks.release('follow');}
}
} catch(_){await sleep(3000);}
}
}
// ── OP Optimizer (OP-OPT) ─────────────────────────────────────────────
async doOPActions(bot) {
if (this.opActionsRun) return;
this.opActionsRun = true;
this.log('⚡', 'تطبيق تحسينات السيرفر + إعداد الأكل التلقائي...');
addEvent('op', `⚡ ${this.cfg.username} يطبق تحسينات السيرفر التلقائية`);
// ── أولاً: أعطِ البوت أكلاً وفيراً (أهم خطوة) ───────────────────────
if (this.alive(bot, this.gen)) {
try {
bot.chat('/give @s cooked_beef 64');
await sleep(1200);
bot.chat('/give @s bread 32');
await sleep(1200);
this.log('🥩', 'تم منح الأكل تلقائياً بعد الحصول على OP');
addEvent('info', `🥩 ${this.cfg.username} أعطى نفسه أكلاً بعد OP`);
// reset eating lock ليأكل فوراً في الـ hungerLoop
this.eating = false;
} catch(_) {}
await sleep(1500);
}
// ── ثانياً: تحسينات السيرفر ─────────────────────────────────────────
const cmds = [
'/gamerule doPatrolSpawning false',
'/gamerule doTraderSpawning false',
'/gamerule doInsomnia false',
'/gamerule announceAdvancements false',
'/gamerule maxEntityCramming 8',
'/gamerule spawnRadius 8',
];
for (const cmd of cmds) {
if (!this.alive(bot, this.gen)) break;
await sleep(700 + randInt(200, 400));
try { bot.chat(cmd); } catch(_) {}
}
if (this.alive(bot, this.gen)) {
await sleep(1500);
try { bot.chat('✅ تم تحسين أداء السيرفر تلقائياً — أداء أفضل للجميع!'); } catch(_) {}
}
}
// ── Role Loop (ROLE-01) — سلوك خاص لكل دور ───────────────────────────
async roleLoop(bot, g) {
await sleep(randInt(20000, 40000));
while (this.alive(bot, g)) {
try {
await sleep(randInt(50000, 100000));
if (!this.alive(bot, g)) continue;
// ── قراءة المهام المخصصة ديناميكياً ──────────────────────────────
// assignedTasks يُحدَّث من TeamCoordinator.redistributeTasks() عند
// كل connect/disconnect، فلا حاجة لإعادة تشغيل البوت.
const tasks = this.assignedTasks.length > 0
? this.assignedTasks
: (this.role ? [this.role.id] : []); // fallback لو لم يُوزَّع بعد
const realPlayers = () => Object.keys(bot.players || {}).filter(n =>
coord?.slots.every(s => s.cfg.username !== n)
);
// ──────────────────────────────────────────────────────────────────
// 🛡️ guardian — مراقبة AFK وطرد الخاملين
// ──────────────────────────────────────────────────────────────────
if (tasks.includes('guardian')) {
const now = Date.now();
for (const [name] of playerRegistry) {
if (coord?.slots.some(s => s.cfg.username === name)) continue;
const reg = playerRegistry.get(name);
if (!reg?.joinedAt) continue;
if (now - reg.joinedAt > 35 * 60000) {
const lastAct = reg.lastChat || reg.joinedAt;
if (now - lastAct > 35 * 60000) {
this.log('⏰', `AFK مشتبه: ${name}`);
// SEC: تصفية اسم اللاعب قبل إرساله في أمر /kick (منع command injection)
const safeName = name.replace(/[^a-zA-Z0-9_]/g,'').slice(0,16);
if (!safeName) { reg.joinedAt = now; continue; }
if (this.isOp) {
try { bot.chat(`/kick ${safeName} AFK لأكثر من 35 دقيقة`); } catch(_) {}
addEvent('info', `⏰ Guardian: طرد ${safeName} لعدم النشاط`);
} else {
try { bot.chat(`${safeName} يبدو AFK منذ فترة طويلة 😴`); } catch(_) {}
}
reg.joinedAt = now;
}
}
}
}
// ──────────────────────────────────────────────────────────────────
// 💬 diplomat — رسائل ترحيب + تشجيع دورية
// ──────────────────────────────────────────────────────────────────
if (tasks.includes('diplomat')) {
const rp = realPlayers();
if (rp.length > 0 && Math.random() < 0.3) {
const msgs = [
'استمتعوا باللعب! 🎮',
'السيرفر شغّال 24/7 على طول 💪',
'لو في مشكلة قولولنا 💬',
'خليكوا حلوين مع بعض 😄',
'السيرفر محمي ومراقب دايماً 🛡️',
`${rp.length} لاعب متصل الآن — نشاط حلو! 🔥`,
];
try { bot.chat(msgs[Math.floor(Math.random() * msgs.length)]); } catch(_) {}
}
}
// ──────────────────────────────────────────────────────────────────
// 🎖️ commander — تقرير دوري + إعلان عند نشاط عالي
// ──────────────────────────────────────────────────────────────────
if (tasks.includes('commander')) {
const rp = realPlayers();
teamState.lastReport = {
time: Date.now(), botsOnline: coord?.count() || 0,
realPlayers: rp.length, tps: performanceData.tps,
};
if (rp.length >= 5 && !teamState.eventPending) {
teamState.eventPending = true;
addEvent('info', `🎖️ Commander: ${rp.length} لاعبين متصلين!`);
try { bot.chat(`/title @a title {"text":"🔥 السيرفر نشط!","color":"gold"}`); } catch(_) {}
setTimeout(() => { teamState.eventPending = false; }, 10 * 60000);
}
}
// ──────────────────────────────────────────────────────────────────
// 🌾 farmer — تذكير بالأكل للاعبين
// ──────────────────────────────────────────────────────────────────
if (tasks.includes('farmer') && Math.random() < 0.2) {
const rp = realPlayers();
if (rp.length > 0) {
const foodMsgs = [
'🌾 المزرعة التلقائية شغّالة — أكل متاح في الـ spawn!',
'🍞 لو جعان فيه أكل في الـ chest الرئيسي عند spawn 😄',
'🥕 تذكّر تاخد أكل معاك قبل ما تنزل في الـ cave!',
];
try { bot.chat(foodMsgs[Math.floor(Math.random() * foodMsgs.length)]); } catch(_) {}
}
}
// ──────────────────────────────────────────────────────────────────
// ⚡ optimizer — تحسين أداء السيرفر عند TPS منخفض
// ──────────────────────────────────────────────────────────────────
if (tasks.includes('optimizer')) {
const tps = performanceData.tps;
if (tps != null && tps < 14) {
this.log('⚡', `optimizer: TPS=${tps} — محاولة تخفيف الضغط`);
addEvent('perf', `⚡ Optimizer (${this.cfg.username}): TPS ${tps.toFixed(1)} — تفعيل تخفيف`);
if (this.isOp) {
try {
bot.chat('/gamerule doMobSpawning false');
await sleep(2000);
bot.chat('/gamerule doMobSpawning true');
} catch(_) {}
} else {
// بدون OP: فقط أبلغ اللاعبين
try { bot.chat(`⚠️ TPS=${tps.toFixed(1)} — السيرفر شايل شوية، خليكوا light!`); } catch(_) {}
}
} else if (tps != null && tps >= 18 && performanceData.ecoMode) {
// TPS جيد — ارفع eco mode
addEvent('perf', `✅ Optimizer: TPS عادل (${tps.toFixed(1)}) — ECO mode سيُخفَّف`);
}
}
} catch(_) { await sleep(5000); }
}
}
// ── Chat Handler ──────────────────────────────────────────────────────
handleChat(sender, message, bot, gen) {
// تحديث آخر نشاط للاعب (لكشف AFK)
const pReg = playerRegistry.get(sender);
if (pReg) pReg.lastChat = Date.now();
// كشف سلبي للبلاجن من أي رسالة شات (بدون OP)
this.parsePluginsFromChat(message);
// كشف TPS من أي رسالة (بعض البلاجن تبث TPS في الشات)
this.parseTpsFromChat(message);
const lo=message.toLowerCase();
const myName=bot.username.toLowerCase();
const addressed=lo.includes(myName)||lo.includes(myName.split('_')[0]);
if (addressed) {
if (/\bcome\b|تعال|هنا|اجي/.test(lo)) { this.cmdQueue.push({type:'come',sender}); return; }
if (/\bstop\b|وقف|كفي/.test(lo)) { this.cmdQueue.push({type:'stop',sender}); return; }
if (/\bfollow\b|اتبع|تابعني/.test(lo)) { this.cmdQueue.push({type:'follow',sender}); return; }
if (/plugins|بلاجن/.test(lo)) { this.cmdQueue.push({type:'plugins',sender}); return; }
if (/optimize|أداء|تحسين/.test(lo)) { this.cmdQueue.push({type:'optimize',sender}); return; }
}
let pool=null;
if (/hello|hi|hey|yo|مرحبا|هلا|السلام/.test(lo)) pool=SMART_REPLIES.greet;
else if (/how are|ازيك|عامل/.test(lo)) pool=SMART_REPLIES.how;
else if (/\bbot\b|are you.*bot|u a bot|بوت/.test(lo)) pool=SMART_REPLIES.bot;
else if (/\bafk\b|sleeping|idle/.test(lo)) pool=SMART_REPLIES.afk;
else if (/bye|cya|later|goodbye|مع السلامة/.test(lo)) pool=SMART_REPLIES.bye;
else if (/\blag\b|ping/.test(lo)) pool=SMART_REPLIES.lag;
else if (/food|hungry|جوعان/.test(lo)) pool=SMART_REPLIES.food;
else if (/die|died|rip|مات/.test(lo)) pool=SMART_REPLIES.death;
if (pool&&Math.random()<0.42) {
const reply=pick(pool);
setTimeout(async()=>{
if (!this.alive(bot,gen)||this.chatOff) return;
if (!teamChat.tryLock(this.cfg.id)) return;
await typeMsg(bot,reply);
}, randInt(3500,14000));
}
}
snapshot() {
const up=this.spawnTime?Math.round((Date.now()-this.spawnTime)/1000):0;
const pos=this.bot?.entity?.position;
return {
id:this.cfg.id, label:this.cfg.label, emoji:this.cfg.emoji,
username:this.bot?.username||this.cfg.username,
personality:this.cfg.personality,
connected:!!this.bot, connecting:this.connecting,
uptime:up, totalUptime:this.totalUptime+up,
sessions:this.sessCount, avgPing:this.avgPing,
chatOff:this.chatOff, deepSleep:this.deepSleep,
state:this.tasks.state, isOp:this.isOp,
kicks:this.kickHistory,
connRefused:this.connRefused,
env:this.envCache,
position: pos ? { x:Math.round(pos.x), y:Math.round(pos.y), z:Math.round(pos.z) } : null,
role: this.role ? { id:this.role.id, label:this.role.label, color:this.role.color, desc:this.role.desc } : null,
assignedTasks: this.assignedTasks.slice(),
performanceMode: this.assignedTasks.includes('optimizer'),
lightBot: this.cfg.lightBot || false,
};
}
}
// ════════════════════════════════════════════════════════════════════════════
// TEAM COORDINATOR
// ════════════════════════════════════════════════════════════════════════════
class TeamCoordinator {
constructor() { this.slots=[]; this.live=new Set(); this.started=false; }
add(s) { this.slots.push(s); }
// ─── إعادة توزيع المهام ديناميكياً ──────────────────────────────────────
redistributeTasks() {
const n = Math.min(Math.max(this.live.size, 1), 4);
const map = TASK_MAP[n] || TASK_MAP[4];
this.slots.forEach((s, idx) => {
// كل بوت يأخذ الصف المقابل له، أو آخر صف لو كان idx > map.length
s.assignedTasks = map[Math.min(idx, map.length - 1)].slice();
const taskLabel = s.assignedTasks.join(', ');
if (typeof s.log === 'function') s.log('📋', `مهام جديدة (${n} بوت): [${taskLabel}]`);
addEvent('info', `📋 ${s.cfg.username}: [${taskLabel}]`);
});
}
onConnect(id) {
this.live.add(id);
this.redistributeTasks();
}
onDisconnect(id) {
this.live.delete(id);
if (this.dead) return;
this.redistributeTasks();
if (this.live.size === 0) {
const b1 = this.slots[0];
setTimeout(() => {
if (!b1.deepSleep && !b1.bot && !b1.connecting && !b1._reconnTimer) b1.scheduleReconnect();
}, 2500);
}
}
destroy() {
this.dead = true;
this.stopAll();
}
count() { return this.live.size; }
status() { return this.slots.map(s=>s.snapshot()); }
async startAll() {
this.started=true;
for (let i=0; i<this.slots.length; i++) {
const s=this.slots[i];
if (i>0) await sleep(randInt(CFG.staggerMin, CFG.staggerMax));
s.connect();
}
}
stopAll() {
for (const s of this.slots) {
s.deepSleep=true;
if (s._reconnTimer) { clearTimeout(s._reconnTimer); s._reconnTimer=null; }
if (s.bot) { try { s.bot.quit(); } catch(_){} }
}
}
restartAll() { this.stopAll(); setTimeout(()=>{ this.slots.forEach(s=>{s.deepSleep=false;s.connect();}); }, 3000); }
}
// ════════════════════════════════════════════════════════════════════════════
// MEMORY CLEANUP — MEM-01
// ════════════════════════════════════════════════════════════════════════════
setInterval(() => {
if (EVENT_LOG.length > 100) EVENT_LOG.length = 100;
if (performanceData.history.length > 20) performanceData.history.length = 20;
// FIX: منع تسرب الذاكرة — حد playerRegistry بـ 200 مدخل (offline players first)
if (playerRegistry.size > 200) {
const toDelete = [...playerRegistry.entries()]
.filter(([,p]) => p.joinedAt === null) // offline أولاً
.slice(0, playerRegistry.size - 200);
toDelete.forEach(([k]) => playerRegistry.delete(k));
}
// حذف معدلات الـ rate limit القديمة (أقدم من 5 دقائق)
const rateCutoff = Date.now() - 5 * 60000;
for (const [k, v] of _chatRateMap) { if (v < rateCutoff) _chatRateMap.delete(k); }
try { if (global.gc) global.gc(); } catch(_){}
saveData();
}, 30*60000);
// ════════════════════════════════════════════════════════════════════════════
// TPS SPARKLINE — رسم بياني SVG للأداء
// ════════════════════════════════════════════════════════════════════════════
function tpsSparkline() {
const h = performanceData.history;
if (h.length < 2) return '<div style="color:#3d8c6a;font-size:12px;text-align:center;padding:12px 0">لا بيانات كافية بعد — في انتظار قراءة TPS...</div>';
const W=340, H=70, PAD=6;
const vals = h.map(p => p.tps);
const mn = Math.max(0, Math.min(...vals) - 1);
const mx = Math.min(22, Math.max(...vals) + 1);
const range = mx - mn || 1;
const pts = vals.map((v,i) => {
const x = PAD + (i / (vals.length-1)) * (W - PAD*2);
const y = H - PAD - ((v-mn)/range) * (H - PAD*2);
return `${x.toFixed(1)},${y.toFixed(1)}`;
}).join(' ');
// خط الـ 15 TPS (حد lag)
const lagY = (H - PAD - ((15-mn)/range) * (H - PAD*2)).toFixed(1);
const lastTps = vals[vals.length-1];
const lineColor = lastTps >= 18 ? '#00e676' : lastTps >= 14 ? '#ffca28' : '#ff5252';
const fillPts = `${PAD},${H} ` + pts + ` ${W-PAD},${H}`;
return `<svg width="100%" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" style="display:block">
<defs>
<linearGradient id="tg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="${lineColor}" stop-opacity="0.3"/>
<stop offset="100%" stop-color="${lineColor}" stop-opacity="0.02"/>
</linearGradient>
</defs>
<polygon points="${fillPts}" fill="url(#tg)"/>
<line x1="${PAD}" y1="${lagY}" x2="${W-PAD}" y2="${lagY}" stroke="#ffca28" stroke-width="1" stroke-dasharray="6,4" opacity="0.5"/>
<polyline points="${pts}" fill="none" stroke="${lineColor}" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="${(PAD + (vals.length-1)/(vals.length-1)*(W-PAD*2)).toFixed(1)}" cy="${(H - PAD - ((lastTps-mn)/range)*(H-PAD*2)).toFixed(1)}" r="4" fill="${lineColor}"/>
<text x="${W-PAD-4}" y="14" text-anchor="end" fill="${lineColor}" font-size="11" font-weight="700">${lastTps.toFixed(1)} TPS</text>
<text x="${PAD+4}" y="${parseFloat(lagY)-4}" fill="#ffca28" font-size="10" opacity="0.7">15 TPS</text>
</svg>`;
}
// ════════════════════════════════════════════════════════════════════════════
// DASHBOARD HTML — لوحة التحكم الكاملة
// ════════════════════════════════════════════════════════════════════════════
function getDashboardHTML(coord) {
const team = coord.status();
const online = coord.count();
const totSess = team.reduce((a,s)=>a+s.sessions,0);
const totUp = Math.floor(team.reduce((a,s)=>a+s.totalUptime,0)/3600);
const uptime = process.uptime();
const procUp = `${Math.floor(uptime/3600)}h ${Math.floor((uptime%3600)/60)}m`;
const plugCount = detectedPlugins.length;
const perfPlugs = detectedPlugins.filter(p=>p.cat==='performance');
const tps = performanceData.tps;
const tpsColor = !tps ? '#64748b' : tps>=18 ? '#22c55e' : tps>=14 ? '#f59e0b' : '#ef4444';
const tpsLabel = !tps ? 'N/A' : `${tps.toFixed(1)} TPS`;
const ecoMode = performanceData.ecoMode;
const sparkSVG = tpsSparkline();
const botCards = team.map(s => {
const h=Math.floor(s.uptime/3600), m=Math.floor((s.uptime%3600)/60), sc=s.uptime%60;
const pingColor = !s.avgPing?'var(--muted)':s.avgPing<200?'#34d399':s.avgPing<500?'#fcd34d':'#f87171';
let statusDot, statusText, statusColor;
if (s.deepSleep) { statusDot='💤'; statusText='سبات عميق'; statusColor='#c4b5fd'; }
else if (s.connected) { statusDot='🟢'; statusText=`متصل — ${h}h ${m}m ${sc}s`; statusColor='#34d399'; }
else if (s.connecting){ statusDot='🟡'; statusText='جاري الاتصال...'; statusColor='#fcd34d'; }
else { statusDot='🔴'; statusText='في الانتظار'; statusColor='var(--muted)'; }
const envChips = [];
if (s.env?.hasOre) envChips.push('<span class="chip chip-ore">⛏️ خام</span>');
if (s.env?.hasWood) envChips.push('<span class="chip chip-wood">🪵 أشجار</span>');
if (s.env?.hasDanger) envChips.push('<span class="chip chip-danger">⚠️ خطر</span>');
if (s.env?.isNight) envChips.push('<span class="chip chip-night">🌙 ليل</span>');
if (s.env?.hasPlayers)envChips.push(`<span class="chip chip-player">👤 ${s.env.playerCount}</span>`);
const statusClass = s.connected ? 'online' : s.deepSleep ? 'sleeping' : s.connecting ? 'connecting' : 'offline';
const envChipsNew = [];
if (s.env?.hasOre) envChipsNew.push('<span class="env-ch" style="color:#fbbf24;border-color:rgba(251,191,36,.2)">⛏️ خام</span>');
if (s.env?.hasWood) envChipsNew.push('<span class="env-ch" style="color:#a78bfa;border-color:rgba(167,139,250,.2)">🪵 أشجار</span>');
if (s.env?.hasDanger) envChipsNew.push('<span class="env-ch" style="color:#f87171;border-color:rgba(248,113,113,.2)">⚠️ خطر</span>');
if (s.env?.isNight) envChipsNew.push('<span class="env-ch" style="color:#818cf8;border-color:rgba(129,140,248,.2)">🌙 ليل</span>');
if (s.env?.hasPlayers) envChipsNew.push(`<span class="env-ch" style="color:#22d3ee;border-color:rgba(34,211,238,.2)">👤 ${s.env.playerCount}</span>`);
return `
<div class="bc ${statusClass}">
<div class="bc-stripe"></div>
<div class="bc-body">
<div class="bc-top">
<div class="bc-av">${s.emoji}</div>
<div class="bc-nfo">
<div class="bc-name">${s.username}</div>
<div class="bc-role-lbl">${s.label}</div>
<div class="bc-tags">
${s.role ? `<span class="bc-tag" style="background:${s.role.color}18;color:${s.role.color};border:1px solid ${s.role.color}40">${s.role.label}</span>` : ''}
${s.isOp ? '<span class="bc-tag" style="background:rgba(251,191,36,.1);color:#fbbf24;border:1px solid rgba(251,191,36,.3)">👑 OP</span>' : ''}
${s.lightBot ? '<span class="bc-tag" style="background:rgba(34,211,238,.08);color:#22d3ee;border:1px solid rgba(34,211,238,.25)">💡 مساعد</span>' : ''}
${s.performanceMode ? '<span class="bc-tag" style="background:rgba(251,191,36,.08);color:#fbbf24;border:1px solid rgba(251,191,36,.2)">🌿 ECO</span>' : ''}
</div>
</div>
<div class="bc-st" style="color:${statusColor}">${statusDot} ${statusText}</div>
</div>
<div class="bc-div"></div>
<div class="bc-stats">
<div class="bs"><div class="bsl">⏱️ الجلسة</div><div class="bsv" id="uptime-${s.id}">${h}h ${m}m ${sc}s</div></div>
<div class="bs"><div class="bsl">📊 إجمالي</div><div class="bsv">${Math.floor(s.totalUptime/3600)}h ${Math.floor((s.totalUptime%3600)/60)}m</div></div>
<div class="bs"><div class="bsl">📶 Ping</div><div class="bsv" id="ping-${s.id}" style="color:${pingColor}">${s.avgPing!=null?s.avgPing+'ms':'N/A'}</div></div>
<div class="bs"><div class="bsl">🔄 جلسات</div><div class="bsv">${s.sessions}${s.kicks.length?` · <span style="color:#f87171">${s.kicks.length}x طُرد</span>`:''}</div></div>
<div class="bs" style="grid-column:span 2"><div class="bsl">⚙️ الحالة</div><div class="bsv" id="state-${s.id}">${s.state}</div></div>
</div>
<div class="bc-pos">
<div class="bsl">📍 الموقع</div>
<div class="bsv" id="pos-${s.id}">${s.position?`${s.position.x}, ${s.position.y}, ${s.position.z}`:'—'}</div>
</div>
${envChipsNew.length?`<div class="bc-env">${envChipsNew.join('')}</div>`:''}
<div class="bc-btns">
<button onclick="botCmd('${s.id}','stop')" class="bbt st">⏹ وقف</button>
<button onclick="botCmd('${s.id}','restart')" class="bbt rs">🔄 إعادة</button>
${s.connected?`<button onclick="grantOp('${s.id}')" class="bbt op">👑 OP+فحص</button>`:''}
${s.isOp?`<button onclick="botCmd('${s.id}','optimize')" class="bbt ot">⚡ تحسين</button>`:''}
</div>
</div>
</div>`;
}).join('');
const pluginRows = detectedPlugins.slice(0,30).map(p => {
const catColor = {performance:'#22c55e',protection:'#3b82f6',utility:'#8b5cf6',
security:'#f59e0b',admin:'#ec4899',economy:'#06b6d4',afk:'#10b981',misc:'#64748b'}[p.cat]||'#64748b';
return `<tr>
<td><strong>${p.name}</strong></td>
<td><span class="cat-badge" style="background:${catColor}22;color:${catColor};border:1px solid ${catColor}44">${p.cat}</span></td>
<td style="color:#94a3b8;font-size:12px">${p.desc}</td>
</tr>`;
}).join('') || '<tr><td colspan="3" style="text-align:center;color:#64748b;padding:20px">لم يُكتشف أي بلاجن بعد — أعطِ البوت OP</td></tr>';
const eventRows = EVENT_LOG.slice(0,20).map(e => {
const typeColor={connect:'#34d399',disconnect:'#5a7fa8',kick:'#f87171',
plugin:'#c4b5fd',perf:'#fcd34d',op:'#fbbf24',chat:'#67e8f9',
sleep:'#a78bfa',info:'#93c5fd',session:'#7f9cc0'}[e.type]||'#7f9cc0';
return `<div class="ev-row"><span style="color:${typeColor}">${e.msg}</span><span class="ev-time">${e.time.slice(11,19)}</span></div>`;
}).join('') || '<div class="ev-row" style="color:var(--muted)">لا أحداث بعد...</div>';
return `<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>⛏️ Sayed Bot Team v18</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&family=JetBrains+Mono:wght@400;600&display=swap');
:root{
--bg:#07090f;
--panel:rgba(255,255,255,0.025);
--card:rgba(255,255,255,0.04);
--border:rgba(255,255,255,0.07);
--border-a:rgba(99,102,241,0.35);
--text:#e2e8f0;--muted:#4b5563;--sub:#9ca3af;
--accent:#6366f1;--accent2:#a78bfa;
--cyan:#22d3ee;--green:#34d399;--red:#f87171;--amber:#fbbf24;--purple:#c084fc;
--mono:'JetBrains Mono',monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{min-height:100vh}
body{
background:var(--bg);
background-image:
radial-gradient(ellipse 80% 50% at 20% 0%,rgba(99,102,241,.09) 0%,transparent 60%),
radial-gradient(ellipse 60% 40% at 85% 100%,rgba(167,139,250,.07) 0%,transparent 60%),
radial-gradient(ellipse 40% 30% at 50% 50%,rgba(6,182,212,.03) 0%,transparent 70%);
color:var(--text);font-family:'Tajawal',sans-serif;font-size:14px;direction:rtl;
}
::-webkit-scrollbar{width:4px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(99,102,241,.3);border-radius:2px}
/* ── NAV ── */
.nav{
position:sticky;top:0;z-index:100;
background:rgba(7,9,15,.88);
backdrop-filter:blur(24px);
border-bottom:1px solid var(--border);
padding:0 20px;
display:flex;align-items:center;gap:12px;
height:54px;
}
.nav-brand{display:flex;align-items:center;gap:10px;margin-left:auto}
.nav-logo{font-size:20px}
.nav-title{font-size:14px;font-weight:800;color:var(--text)}
.nav-ver{
font-size:10px;font-weight:700;
padding:2px 8px;border-radius:20px;
background:rgba(99,102,241,.14);
border:1px solid rgba(99,102,241,.3);
color:var(--accent2);
}
.nav-live{
display:flex;align-items:center;gap:6px;
font-size:12px;font-weight:700;
padding:5px 13px;border-radius:20px;
background:rgba(52,211,153,.07);
border:1px solid rgba(52,211,153,.2);
color:#34d399;margin-right:8px;
}
.pulse{width:6px;height:6px;border-radius:50%;background:#34d399;flex-shrink:0;
animation:pulse-a 2s ease-in-out infinite}
@keyframes pulse-a{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.7)}}
.nav-actions{display:flex;align-items:center;gap:7px;margin-right:auto}
.nb{
display:inline-flex;align-items:center;gap:5px;
padding:6px 13px;border-radius:9px;font-size:12px;font-weight:700;
font-family:'Tajawal',sans-serif;
border:1px solid var(--border);background:var(--panel);
color:var(--sub);cursor:pointer;transition:all .2s;
text-decoration:none;white-space:nowrap;
}
.nb:hover{border-color:var(--accent);color:var(--text);background:rgba(99,102,241,.1)}
.nb.g{border-color:rgba(52,211,153,.3);color:#34d399;background:rgba(52,211,153,.06)}
.nb.g:hover{background:rgba(52,211,153,.12)}
.nb.r{border-color:rgba(248,113,113,.3);color:#f87171;background:rgba(248,113,113,.06)}
.nb.r:hover{background:rgba(248,113,113,.12)}
.nb.p{border-color:rgba(167,139,250,.3);color:#a78bfa;background:rgba(167,139,250,.06)}
.nb.p:hover{background:rgba(167,139,250,.12)}
.nb.am{border-color:rgba(251,191,36,.3);color:#fbbf24;background:rgba(251,191,36,.06)}
.nb.am:hover{background:rgba(251,191,36,.12)}
.nb.cy{border-color:rgba(34,211,238,.3);color:#22d3ee;background:rgba(34,211,238,.06)}
.nb.cy:hover{background:rgba(34,211,238,.12)}
/* ── WRAP ── */
.wrap{max-width:1160px;margin:0 auto;padding:22px 18px 52px}
/* ── HERO STATS ── */
.stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:18px}
@media(max-width:700px){.stat-grid{grid-template-columns:repeat(2,1fr)}}
.sc{
background:var(--card);border:1px solid var(--border);border-radius:16px;
padding:20px 18px;position:relative;overflow:hidden;
transition:border-color .25s,transform .2s;
}
.sc:hover{border-color:var(--border-a);transform:translateY(-2px)}
.sc::after{
content:'';position:absolute;inset:0;
background:linear-gradient(135deg,rgba(99,102,241,.05) 0%,transparent 55%);
pointer-events:none;
}
.sc-ico{font-size:22px;margin-bottom:10px}
.sc-val{font-size:26px;font-weight:800;line-height:1;margin-bottom:5px}
.sc-lbl{font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.6px}
/* ── SERVER BAND ── */
.srv-band{
display:flex;background:var(--card);
border:1px solid var(--border);border-radius:14px;
overflow:hidden;margin-bottom:18px;
}
.sv-i{
flex:1;padding:12px 16px;
border-right:1px solid var(--border);
}
.sv-i:first-child{padding-right:16px}
.sv-i:last-child{border-right:none}
.sv-l{font-size:10px;color:var(--muted);font-weight:700;text-transform:uppercase;letter-spacing:.8px;margin-bottom:4px}
.sv-v{font-size:14px;font-weight:700;font-family:var(--mono)}
/* ── ECO BANNER ── */
.eco-bar{
display:flex;align-items:center;gap:14px;
background:rgba(251,191,36,.06);
border:1.5px solid rgba(251,191,36,.25);
border-radius:14px;padding:13px 18px;margin-bottom:18px;
}
.eco-ico{font-size:26px}
.eco-tm{font-size:14px;font-weight:800;color:#fbbf24}
.eco-ts{font-size:11px;color:rgba(251,191,36,.6);margin-top:2px}
.eco-v{font-size:20px;font-weight:900;color:#fbbf24;font-family:var(--mono);margin-right:auto}
/* ── TPS CHART ── */
.tps-card{
background:var(--card);border:1px solid var(--border);
border-radius:16px;padding:16px 18px;margin-bottom:18px;
}
.tps-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.tps-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:7px}
.tps-legend{display:flex;align-items:center;gap:16px;font-size:11px;color:var(--muted)}
.leg-item{display:flex;align-items:center;gap:5px}
.leg-line{width:16px;height:2px;border-radius:2px}
/* ── SECTION ── */
.section{margin-bottom:22px}
.sh{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.sh h2{font-size:15px;font-weight:700;display:flex;align-items:center;gap:8px}
.sh-sub{font-size:11px;color:var(--muted)}
/* ── ROLES ── */
.roles-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
.rc{
background:var(--card);border:1px solid var(--border);
border-radius:14px;padding:14px 16px;transition:border-color .2s;
}
.rc:hover{border-color:var(--border-a)}
.rc-tag{font-size:11px;font-weight:800;margin-bottom:5px}
.rc-desc{font-size:12px;color:var(--muted);line-height:1.5;margin-bottom:8px}
.rc-bot{font-size:12px;font-weight:600;display:flex;align-items:center;gap:5px}
/* ── BOT CARDS ── */
.bots-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:16px}
.bc{
background:var(--card);border:1px solid var(--border);
border-radius:18px;overflow:hidden;position:relative;
transition:border-color .25s,box-shadow .25s,transform .2s;
}
.bc:hover{transform:translateY(-3px)}
.bc.online{border-color:rgba(52,211,153,.28)}
.bc.online:hover{border-color:rgba(52,211,153,.5);box-shadow:0 8px 30px rgba(52,211,153,.07)}
.bc.offline{border-color:rgba(107,114,128,.18)}
.bc.sleeping{border-color:rgba(167,139,250,.25)}
.bc.sleeping:hover{border-color:rgba(167,139,250,.45);box-shadow:0 8px 30px rgba(167,139,250,.07)}
.bc.connecting{border-color:rgba(251,191,36,.22)}
/* stripe */
.bc-stripe{height:3px;width:100%}
.online .bc-stripe{background:linear-gradient(90deg,#34d399,#22d3ee)}
.offline .bc-stripe{background:linear-gradient(90deg,#374151,#4b5563)}
.sleeping .bc-stripe{background:linear-gradient(90deg,#7c3aed,#a78bfa)}
.connecting .bc-stripe{background:linear-gradient(90deg,#d97706,#fbbf24)}
/* body */
.bc-body{padding:16px 17px}
.bc-top{display:flex;align-items:flex-start;gap:12px;margin-bottom:14px}
.bc-av{
width:46px;height:46px;border-radius:13px;flex-shrink:0;
background:rgba(255,255,255,.05);border:1px solid var(--border);
display:flex;align-items:center;justify-content:center;font-size:24px;
}
.bc-nfo{flex:1;min-width:0}
.bc-name{font-size:15px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bc-role-lbl{font-size:11px;color:var(--muted);margin-top:2px}
.bc-tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}
.bc-tag{font-size:10px;padding:2px 7px;border-radius:6px;font-weight:700}
.bc-st{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:700;white-space:nowrap}
.bc-div{height:1px;background:var(--border);margin-bottom:12px}
.bc-stats{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:10px}
.bs{background:rgba(255,255,255,.025);border-radius:9px;padding:8px 10px}
.bs .bsl{font-size:10px;color:var(--muted);margin-bottom:2px;font-weight:500}
.bs .bsv{font-size:13px;font-weight:700;font-family:var(--mono)}
.bc-pos{background:rgba(34,211,238,.04);border:1px solid rgba(34,211,238,.1);border-radius:9px;padding:8px 10px;margin-bottom:10px}
.bc-pos .bsl{font-size:10px;color:var(--muted);margin-bottom:2px}
.bc-pos .bsv{font-size:12px;font-family:var(--mono);color:#22d3ee}
.bc-env{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:10px}
.env-ch{font-size:10px;padding:3px 8px;border-radius:7px;font-weight:700;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08)}
.bc-btns{display:flex;gap:6px;flex-wrap:wrap}
.bbt{
flex:1;padding:7px 10px;border-radius:9px;font-size:11px;font-weight:700;
font-family:'Tajawal',sans-serif;cursor:pointer;
border:1px solid var(--border);background:rgba(255,255,255,.03);color:var(--muted);
transition:all .2s;white-space:nowrap;
}
.bbt:hover{border-color:var(--accent);color:var(--text);background:rgba(99,102,241,.1)}
.bbt.st{border-color:rgba(248,113,113,.2);color:#f87171}
.bbt.st:hover{background:rgba(248,113,113,.1)}
.bbt.rs{border-color:rgba(52,211,153,.2);color:#34d399}
.bbt.rs:hover{background:rgba(52,211,153,.08)}
.bbt.op{border-color:rgba(251,191,36,.2);color:#fbbf24}
.bbt.op:hover{background:rgba(251,191,36,.08)}
.bbt.ot{border-color:rgba(99,102,241,.2);color:#a78bfa}
.bbt.ot:hover{background:rgba(99,102,241,.1)}
/* ── TABLE ── */
.tbl{width:100%;border-collapse:collapse}
.tbl th{
text-align:right;padding:10px 14px;font-size:10px;
text-transform:uppercase;letter-spacing:.8px;
color:var(--muted);font-weight:700;border-bottom:1px solid var(--border);
}
.tbl td{padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.03);font-size:13px}
.tbl tr:hover td{background:rgba(255,255,255,.02)}
.cat-chip{font-size:10px;padding:2px 8px;border-radius:6px;font-weight:700}
.new-badge{font-size:10px;padding:2px 6px;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.3);color:#a78bfa;border-radius:6px;margin-right:5px;font-weight:700}
/* ── EVENTS ── */
.evbox{
background:var(--card);border:1px solid var(--border);
border-radius:14px;padding:6px;max-height:250px;overflow-y:auto;
}
.ev-row{display:flex;align-items:baseline;justify-content:space-between;padding:7px 10px;border-radius:8px;font-size:12px}
.ev-row:hover{background:rgba(255,255,255,.025)}
.ev-time{font-size:10px;color:var(--muted);font-family:var(--mono);white-space:nowrap;margin-right:10px}
/* ── PANEL ── */
.panel{background:var(--card);border:1px solid var(--border);border-radius:16px;overflow:hidden}
.panel-body{padding:16px 18px}
/* ── EMPTY ── */
.empty{text-align:center;color:var(--muted);padding:28px;font-size:13px;line-height:1.7}
/* ── SCAN INFO ── */
.scan-info{
display:flex;align-items:center;gap:12px;
background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.2);
border-radius:12px;padding:12px 16px;margin-bottom:14px;
font-size:12px;color:#a78bfa;line-height:1.6;
}
/* ── CHAT ── */
.chat-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.chat-sel,.chat-in{
background:var(--panel);border:1px solid var(--border);
color:var(--text);border-radius:10px;padding:9px 13px;
font-family:'Tajawal',sans-serif;font-size:13px;outline:none;
transition:border-color .2s;
}
.chat-in{flex:1;min-width:200px}
.chat-in:focus,.chat-sel:focus{border-color:var(--accent)}
.chat-send{
background:rgba(99,102,241,.12);border:1px solid rgba(99,102,241,.3);
color:#a78bfa;border-radius:10px;padding:9px 22px;
font-size:13px;font-weight:700;font-family:'Tajawal',sans-serif;
cursor:pointer;transition:all .2s;
}
.chat-send:hover{background:rgba(99,102,241,.22)}
/* ── TOAST ── */
.toast{
position:fixed;bottom:22px;left:50%;
transform:translateX(-50%) translateY(16px);
background:rgba(10,12,20,.95);backdrop-filter:blur(20px);
border:1px solid var(--border);border-radius:12px;
padding:12px 24px;font-size:13px;font-weight:700;
opacity:0;transition:all .3s;pointer-events:none;
z-index:9999;max-width:380px;text-align:center;
}
.toast.ok{border-color:rgba(52,211,153,.4);color:#34d399}
.toast.err{border-color:rgba(248,113,113,.4);color:#f87171}
.toast.info{border-color:rgba(99,102,241,.4);color:#a78bfa}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* ── FOOT ── */
.foot{text-align:center;color:var(--muted);font-size:11px;padding:16px 0 6px}
</style>
</head>
<body>
<!-- NAV -->
<nav class="nav">
<div class="nav-brand">
<span class="nav-logo">⛏️</span>
<span class="nav-title">Sayed Bot Team</span>
<span class="nav-ver">v18</span>
<div class="nav-live" id="navLive">
<span class="pulse" id="liveDot"></span>
<span id="navSt">${online}/${team.length} متصل</span>
</div>
</div>
<div class="nav-actions">
<button class="nb g" onclick="location.reload()">🔄 تحديث</button>
<button class="nb am" onclick="ctrlAll('restart')">⚡ إعادة الكل</button>
<button class="nb r" onclick="ctrlAll('stop')">⏹ إيقاف</button>
<button class="nb p" onclick="forceScan()">🔍 فحص بلاجن</button>
<button class="nb am" onclick="startAternos()" id="aternosBtn">🔔 Aternos</button>
<a href="https://aternos.org/server/" target="_blank" class="nb cy">🌐 فتح Aternos</a>
<a href="/setup" class="nb" style="text-decoration:none">⚙️ إعداد</a>
</div>
</nav>
<div class="wrap">
${ecoMode ? `
<div class="eco-bar">
<span class="eco-ico">🌿</span>
<div>
<div class="eco-tm">ECO MODE مفعّل — تخفيف ذكي للضغط</div>
<div class="eco-ts">TPS منخفض — البوتات أوقفت المشي والحفر تلقائياً للحفاظ على أداء السيرفر</div>
</div>
<div class="eco-v">${tpsLabel}</div>
</div>` : ''}
<!-- STATS -->
<div class="stat-grid">
<div class="sc">
<div class="sc-ico">🤖</div>
<div class="sc-val" style="color:${online>0?'#34d399':'#f87171'}">${online}/${team.length}</div>
<div class="sc-lbl">بوتات متصلة</div>
</div>
<div class="sc">
<div class="sc-ico">⚡</div>
<div class="sc-val" data-tps style="color:${tpsColor}">${tpsLabel}</div>
<div class="sc-lbl">TPS السيرفر</div>
</div>
<div class="sc">
<div class="sc-ico">🔌</div>
<div class="sc-val" style="color:#a78bfa">${plugCount}</div>
<div class="sc-lbl">بلاجن مكتشفة</div>
</div>
<div class="sc">
<div class="sc-ico">👥</div>
<div class="sc-val" style="color:#22d3ee">${playerRegistry.size}</div>
<div class="sc-lbl">لاعبين مسجّلين</div>
</div>
</div>
<!-- SERVER INFO -->
<div class="srv-band">
<div class="sv-i">
<div class="sv-l">🌐 العنوان</div>
<div class="sv-v">${CFG.host}</div>
</div>
<div class="sv-i">
<div class="sv-l">🔌 البورت</div>
<div class="sv-v">${CFG.port}</div>
</div>
<div class="sv-i">
<div class="sv-l">🎮 الإصدار</div>
<div class="sv-v">${CFG.version==='auto'?`تلقائي (${autoVersion||'?'})`:CFG.version}</div>
</div>
<div class="sv-i">
<div class="sv-l">⚡ TPS حالي</div>
<div class="sv-v" data-tps style="color:${tpsColor}">${tpsLabel}</div>
</div>
<div class="sv-i">
<div class="sv-l">🚀 أداء</div>
<div class="sv-v" style="color:#34d399">${perfPlugs.length} بلاجن</div>
</div>
<div class="sv-i">
<div class="sv-l">🕐 تشغيل</div>
<div class="sv-v" style="color:#fbbf24">${procUp}</div>
</div>
</div>
<!-- TPS GRAPH -->
<div class="tps-card">
<div class="tps-hd">
<div class="tps-title">📈 سجل أداء السيرفر — TPS History</div>
<div class="tps-legend">
<span class="leg-item"><span class="leg-line" style="background:${tpsColor}"></span>TPS</span>
<span class="leg-item"><span class="leg-line" style="background:#fbbf24;border-top:1px dashed #fbbf24;height:0"></span>حد 15</span>
<span style="font-family:var(--mono);font-size:11px;color:var(--sub)">${performanceData.history.length} قراءة</span>
</div>
</div>
${sparkSVG}
</div>
<!-- ROLES -->
<div class="section">
<div class="sh">
<h2>🎭 أدوار الفريق</h2>
<span class="sh-sub">كل بوت له مهمة محددة</span>
</div>
<div class="roles-grid">
${team.map(s => s.role ? `
<div class="rc" style="border-color:${s.role.color}22">
<div class="rc-tag" style="color:${s.role.color}">${s.role.label}</div>
<div class="rc-desc">${s.role.desc}</div>
<div class="rc-bot">${s.emoji} ${s.username} ${s.connected?`<span style="color:#34d399;margin-right:auto">● متصل</span>`:`<span style="color:#4b5563;margin-right:auto">● غير متصل</span>`}</div>
</div>` : '').join('')}
</div>
</div>
<!-- BOTS -->
<div class="section">
<div class="sh">
<h2>🤖 البوتات</h2>
<span class="sh-sub">تحديث كل 15 ثانية</span>
</div>
<div class="bots-grid">${botCards}</div>
</div>
<!-- PLUGINS -->
<div class="section">
<div class="sh">
<h2>🔌 البلاجن المكتشفة</h2>
<div style="display:flex;gap:8px;align-items:center">
<span class="sh-sub">${plugCount} بلاجن</span>
<button class="nb p" onclick="forceScan()" style="padding:5px 12px;font-size:11px">🔍 فحص الآن</button>
</div>
</div>
${plugCount===0?`<div class="scan-info">
ℹ️ لم يُكتشف أي بلاجن بعد — البوتات تكشف تلقائياً من الشات أو أعطِها <strong>OP</strong> للكشف الكامل
<button onclick="forceScan()" style="margin-right:auto;flex-shrink:0;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.3);color:#a78bfa;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;font-family:'Tajawal',sans-serif;cursor:pointer">🔍 ابدأ الآن</button>
</div>`:''}
<div class="panel" style="overflow:auto">
<table class="tbl">
<thead><tr><th>الاسم</th><th>التصنيف</th><th>الوصف</th></tr></thead>
<tbody>${pluginRows}</tbody>
</table>
</div>
</div>
<!-- PLAYERS -->
<div class="section">
<div class="sh">
<h2>👥 سجل اللاعبين</h2>
<span class="sh-sub">${playerRegistry.size} لاعب</span>
</div>
<div class="panel" style="overflow:auto">
${playerRegistry.size>0?`<table class="tbl">
<thead><tr><th>اللاعب</th><th>أول ظهور</th><th>آخر ظهور</th><th>الزيارات</th><th>الحالة</th></tr></thead>
<tbody>
${[...playerRegistry.entries()].slice(0,25).map(([name,p])=>{
const isOnline=p.joinedAt!==null;
return `<tr>
<td><strong>${name}</strong>${p.visits===1?'<span class="new-badge">جديد ✨</span>':''}</td>
<td style="color:var(--sub);font-family:var(--mono);font-size:12px">${p.firstSeen?p.firstSeen.slice(0,16):'—'}</td>
<td style="color:var(--sub);font-family:var(--mono);font-size:12px">${p.lastSeen?p.lastSeen.slice(0,16):'—'}</td>
<td style="color:#22d3ee;font-weight:700;font-family:var(--mono)">${p.visits}</td>
<td>${isOnline?'<span style="color:#34d399;font-size:11px;font-weight:700">● متصل</span>':'<span style="color:#4b5563;font-size:11px">○ غير متصل</span>'}</td>
</tr>`;
}).join('')}
</tbody>
</table>`:`<div class="empty">لم يُرصد أي لاعع بعد — البوتات تراقب وتسجّل تلقائياً عند دخول أي لاعب 👀</div>`}
</div>
</div>
<!-- EVENTS -->
<div class="section">
<div class="sh">
<h2>📋 سجل الأحداث</h2>
<button class="nb" onclick="document.getElementById('evbox').innerHTML='<div class=ev-row style=color:var(--muted)>تم المسح</div>'" style="font-size:11px;padding:5px 10px">🗑️ مسح</button>
</div>
<div class="evbox" id="evbox">${eventRows}</div>
</div>
<!-- CHAT -->
<div class="section">
<div class="sh">
<h2>💬 إرسال رسالة في السيرفر</h2>
<span class="sh-sub">عبر أي بوت متصل</span>
</div>
<div class="panel-body panel">
<div class="chat-row">
<select id="chatBotId" class="chat-sel">
${team.map((s,i)=>`<option value="${i}">${s.emoji} ${s.username}${s.connected?' ✅':' ❌'}</option>`).join('')}
</select>
<input id="chatMsg" class="chat-in" type="text" placeholder="اكتب رسالة..." dir="auto"
onkeydown="if(event.key==='Enter')sendChatMsg()">
<button class="chat-send" onclick="sendChatMsg()">📤 إرسال</button>
</div>
</div>
</div>
<div class="foot">⛏️ Sayed Bot Team v18 — تحديث تلقائي كل 15 ثانية</div>
</div>
<div class="toast" id="toast"></div>
<script>
let _liveTimer=null;
let _sr=setTimeout(()=>location.reload(),90000);
function resetSR(){clearTimeout(_sr);_sr=setTimeout(()=>location.reload(),90000)}
async function liveUpdate(){
try{
const r=await fetch('/api/status');
if(!r.ok)return;
const d=await r.json();
resetSR();
// nav status
const online=d.online??0,total=d.bots?.length??0;
const navSt=document.getElementById('navSt');
if(navSt)navSt.textContent=\`\${online}/\${total} متصل\`;
const navLive=document.getElementById('navLive');
if(navLive){
navLive.style.color=online>0?'#34d399':'#f87171';
navLive.style.borderColor=online>0?'rgba(52,211,153,.2)':'rgba(248,113,113,.2)';
navLive.style.background=online>0?'rgba(52,211,153,.07)':'rgba(248,113,113,.06)';
}
// bot stats
if(Array.isArray(d.bots)){
d.bots.forEach(s=>{
const pingEl=document.getElementById('ping-'+s.id);
if(pingEl&&s.avgPing!=null)pingEl.textContent=s.avgPing+'ms';
const posEl=document.getElementById('pos-'+s.id);
if(posEl)posEl.textContent=s.position?\`\${s.position.x}, \${s.position.y}, \${s.position.z}\`:'—';
const stEl=document.getElementById('state-'+s.id);
if(stEl)stEl.textContent=s.state||'—';
const upEl=document.getElementById('uptime-'+s.id);
if(upEl&&s.uptime!=null){
const h=Math.floor(s.uptime/3600),m=Math.floor((s.uptime%3600)/60),sc=s.uptime%60;
upEl.textContent=\`\${h}h \${m}m \${sc}s\`;
}
});
}
// TPS
const tps=d.performance?.tps;
if(tps!=null){
const c=tps>=18?'#34d399':tps>=14?'#fbbf24':'#f87171';
document.querySelectorAll('[data-tps]').forEach(el=>{el.textContent=tps.toFixed(1)+' TPS';el.style.color=c});
}
// events
if(Array.isArray(d.events)&&d.events.length){
const evbox=document.getElementById('evbox');
if(evbox){
const tc={connect:'#34d399',disconnect:'#5a7fa8',kick:'#f87171',
plugin:'#a78bfa',perf:'#fbbf24',op:'#fbbf24',chat:'#22d3ee',
sleep:'#a78bfa',info:'#93c5fd',session:'#7f9cc0'};
evbox.innerHTML=d.events.slice(0,20).map(e=>
\`<div class="ev-row"><span style="color:\${tc[e.type]||'#7f9cc0'}">\${e.msg}</span><span class="ev-time">\${e.time.slice(11,19)}</span></div>\`
).join('');
}
}
// aternos btn
const ab=document.getElementById('aternosBtn');
if(ab){
ab.textContent=d.aternosRunning?'⏳ Aternos...':'🔔 Aternos';
ab.disabled=!!d.aternosRunning;
}
}catch(_){}
}
_liveTimer=setInterval(liveUpdate,15000);
async function botCmd(id,cmd){
try{
const r=await fetch('/api/cmd',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({botId:parseInt(id),cmd})});
const d=await r.json();
showToast(d.ok?'✅ '+d.msg:'❌ '+(d.error||'خطأ'),d.ok?'ok':'err');
setTimeout(liveUpdate,1000);
}catch(e){showToast('❌ فشل الاتصال','err')}
}
async function ctrlAll(action){
try{
await fetch('/api/control',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({action})});
showToast(action==='stop'?'⏹ تم إيقاف الكل':'🔄 جاري إعادة تشغيل الكل','ok');
setTimeout(liveUpdate,2000);
}catch(e){showToast('❌ فشل','err')}
}
async function grantOp(id){
try{
const r=await fetch('/api/grant-op',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({botId:parseInt(id)})});
const d=await r.json();
showToast(d.ok?'👑 '+d.msg:'❌ '+(d.error||'خطأ'),d.ok?'ok':'err');
}catch(e){showToast('❌ فشل','err')}
}
async function forceScan(){
try{
const r=await fetch('/api/force-scan',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});
const d=await r.json();
showToast(d.ok?'🔍 '+d.msg:'❌ '+(d.error||'خطأ'),d.ok?'ok':'err');
}catch(e){showToast('❌ فشل','err')}
}
async function sendChatMsg(){
const botId=document.getElementById('chatBotId')?.value??0;
const msg=document.getElementById('chatMsg')?.value?.trim();
if(!msg){showToast('⚠️ الرسالة فارغة','err');return}
try{
const r=await fetch('/api/send-chat',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({botId:parseInt(botId),msg})});
const d=await r.json();
if(d.ok){showToast('✅ '+d.msg,'ok');const i=document.getElementById('chatMsg');if(i)i.value='';}
else showToast('❌ '+(d.error||'خطأ'),'err');
}catch(e){showToast('❌ فشل','err')}
}
async function startAternos(){
showToast('🔔 السيرفر متوقف! افتح aternos.org وشغّله يدوياً','info');
try{await fetch('/api/aternos-start',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});}catch(_){}
setTimeout(()=>{window.open('https://aternos.org/server/','_blank');},500);
}
function showToast(msg,type='ok'){
const t=document.getElementById('toast');
if(!t)return;
t.textContent=msg;
t.className='toast '+type+' show';
setTimeout(()=>{t.className='toast '+type},3200);
}
</script>
</body>
</html>`;
}
// ════════════════════════════════════════════════════════════════════════════
// SETUP WIZARD HTML — Gaming UI
// ════════════════════════════════════════════════════════════════════════════
function getSetupHTML() {
const verGroups = {
'الإصدارات القديمة (1.0 – 1.7)': ['1.0.0','1.1.0','1.2.1','1.3.1','1.4.2','1.5.0','1.6.1','1.7.2'],
'الإصدارات الكلاسيكية (1.8 – 1.12)': ['1.8.0','1.9.0','1.10.0','1.11.0','1.12.0'],
'الإصدارات الحديثة (1.13 – 1.19)': ['1.13.0','1.14.0','1.15.0','1.16.0','1.17.0','1.18.0','1.19.0'],
'الإصدارات الجديدة (1.20 – 1.21)': ['1.20.0','1.21.0','1.21.2','1.21.3','1.21.4','1.21.5','1.21.6'],
'إصدارات Bedrock': ['26.1.0','26.2.0'],
};
const verOptions = Object.entries(verGroups).map(([grp, vers]) =>
`<optgroup label="${grp}">${vers.map(v=>`<option value="${v}">${v}</option>`).join('')}</optgroup>`
).join('');
return `<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>⛏️ Sayed Bot Team v16 — إعداد السيرفر</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap');
:root{
--bg:#050c10; --surface:#091520; --card:#0d1e2a;
--border:#0d3326; --border2:#135c3e;
--text:#d6f5e8; --muted:#3d8c6a; --dim:#0f3c28;
--accent:#00e676; --accent2:#7c4dff; --green:#00e676;
--red:#ff5252; --yellow:#ffca28; --purple:#ce93d8;
--glow:0 0 20px rgba(0,230,118,.35);
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{min-height:100vh}
body{
background:var(--bg);
background-image:
radial-gradient(ellipse at 20% 20%, rgba(0,230,118,.09) 0%, transparent 60%),
radial-gradient(ellipse at 80% 80%, rgba(124,77,255,.07) 0%, transparent 60%);
color:var(--text);
font-family:'Tajawal',sans-serif;
display:flex;flex-direction:column;align-items:center;
justify-content:flex-start;padding:20px 16px 60px;
}
/* ─── particles ─── */
#particles{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.p{position:absolute;border-radius:2px;opacity:.15;animation:float linear infinite}
@keyframes float{0%{transform:translateY(100vh) rotate(0deg)}100%{transform:translateY(-100px) rotate(720deg)}}
/* ─── Header ─── */
.site-header{
width:100%;max-width:640px;text-align:center;
padding:36px 0 24px;position:relative;z-index:1;
}
.logo{font-size:56px;display:block;margin-bottom:12px;animation:bounce 2s ease-in-out infinite}
@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
.site-header h1{
font-size:clamp(22px,5vw,34px);font-weight:800;
background:linear-gradient(135deg,#00e676,#00bfa5,#7c4dff);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
margin-bottom:8px;letter-spacing:-.5px;
}
.site-header p{color:var(--muted);font-size:15px;line-height:1.6}
.badge-row{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:14px}
.badge{background:var(--card);border:1px solid var(--border);border-radius:20px;
padding:5px 12px;font-size:12px;color:var(--muted);display:flex;align-items:center;gap:5px}
.badge span{color:var(--green)}
/* ─── Progress Bar ─── */
.progress-bar{
width:100%;max-width:640px;
display:flex;align-items:center;gap:0;
margin-bottom:28px;position:relative;z-index:1;
}
.prog-step{
flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;
cursor:pointer;position:relative;
}
.prog-step:not(:last-child)::after{
content:'';position:absolute;top:16px;
right:calc(-50% + 16px);left:calc(50% + 16px);
height:2px;background:var(--border);transition:background .4s;
}
.prog-step.done:not(:last-child)::after{background:var(--accent)}
.prog-dot{
width:32px;height:32px;border-radius:50%;
background:var(--surface);border:2px solid var(--border);
display:flex;align-items:center;justify-content:center;
font-size:14px;transition:all .3s;
}
.prog-step.active .prog-dot{border-color:var(--accent);background:rgba(59,130,246,.15);
box-shadow:0 0 14px rgba(0,230,118,.5);}
.prog-step.done .prog-dot{background:var(--accent);border-color:var(--accent)}
.prog-label{font-size:11px;color:var(--muted);transition:color .3s}
.prog-step.active .prog-label{color:var(--text)}
/* ─── Card ─── */
.card{
width:100%;max-width:640px;
background:var(--card);
border:1px solid var(--border);
border-radius:20px;overflow:hidden;
position:relative;z-index:1;
box-shadow:0 8px 40px rgba(0,0,0,.5);
}
.card-top{
background:linear-gradient(160deg,#0a1628,#0d1f3c);
border-bottom:1px solid var(--border);
padding:24px 28px 20px;
}
.card-top h2{font-size:20px;font-weight:700;margin-bottom:4px}
.card-top p{color:var(--muted);font-size:13px}
.card-body{padding:24px 28px}
/* ─── Steps ─── */
.step-pane{display:none}
.step-pane.active{display:block;animation:fadeSlide .3s ease}
@keyframes fadeSlide{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* ─── Form Elements ─── */
.field{margin-bottom:20px}
.field label{
display:block;font-size:13px;font-weight:700;
color:var(--text);margin-bottom:7px;
}
.field label .tip{
font-weight:400;color:var(--muted);font-size:12px;
margin-right:6px;
}
.field label .req{color:var(--red);margin-right:2px}
.input-wrap{position:relative}
.input-wrap .icon{
position:absolute;top:50%;transform:translateY(-50%);
right:12px;font-size:18px;pointer-events:none;
}
input[type=text],input[type=number],select{
width:100%;background:var(--surface);
border:1.5px solid var(--border);border-radius:12px;
color:var(--text);padding:11px 40px 11px 14px;
font-size:14px;font-family:'Tajawal',sans-serif;
transition:border-color .25s,box-shadow .25s;
direction:ltr;text-align:left;
}
input[type=text]:focus,input[type=number]:focus,select:focus{
outline:none;border-color:var(--accent);
box-shadow:0 0 0 3px rgba(59,130,246,.15);
}
input.ok{border-color:var(--green)}
input.bad{border-color:var(--red)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
/* ─── Ping Bar ─── */
.ping-row{display:flex;gap:10px;margin-top:10px}
.ping-btn{
flex-shrink:0;background:rgba(59,130,246,.12);
border:1px solid var(--accent);border-radius:10px;
color:var(--accent);padding:10px 16px;font-size:13px;
font-family:'Tajawal',sans-serif;font-weight:700;
cursor:pointer;transition:all .2s;white-space:nowrap;
}
.ping-btn:hover{background:rgba(59,130,246,.22)}
.ping-result{
flex:1;background:var(--surface);border:1.5px solid var(--border);
border-radius:10px;padding:10px 14px;font-size:13px;
color:var(--muted);display:flex;align-items:center;gap:8px;
}
/* ─── Bot Count Cards ─── */
.count-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:4px}
.count-card{
background:var(--surface);border:2px solid var(--border);
border-radius:14px;padding:14px 8px;text-align:center;
cursor:pointer;transition:all .25s;position:relative;
}
.count-card:hover{border-color:var(--accent);background:rgba(59,130,246,.07)}
.count-card.selected{
border-color:var(--accent);background:rgba(59,130,246,.12);
box-shadow:0 0 0 2px rgba(59,130,246,.25);
}
.count-card .num{font-size:26px;font-weight:800;color:var(--accent);line-height:1}
.count-card .lbl{font-size:11px;color:var(--muted);margin-top:4px}
.count-card .icons{font-size:16px;margin-top:6px;letter-spacing:-2px}
.count-card .badge-rec{
position:absolute;top:-8px;right:50%;transform:translateX(50%);
background:var(--green);color:#fff;border-radius:10px;
font-size:10px;padding:2px 7px;font-weight:700;
}
input[name=botCount]{display:none}
/* ─── Bot Name Rows ─── */
.bot-row{
display:flex;gap:10px;align-items:center;
margin-bottom:10px;
background:var(--surface);border:1.5px solid var(--border);
border-radius:12px;padding:10px 14px;
transition:border-color .2s;
}
.bot-row:focus-within{border-color:var(--accent)}
.bot-icon{font-size:22px;flex-shrink:0}
.bot-info{flex:1;min-width:0}
.bot-info small{display:block;font-size:11px;color:var(--muted);margin-bottom:3px}
.bot-info input{
background:transparent;border:none;padding:0;
font-size:14px;font-weight:700;color:var(--text);
width:100%;
}
.bot-info input:focus{outline:none;box-shadow:none;border:none}
/* ─── Info boxes ─── */
.info-box{
background:rgba(59,130,246,.07);border:1px solid rgba(59,130,246,.2);
border-radius:12px;padding:12px 16px;margin-bottom:18px;
font-size:13px;color:#93c5fd;line-height:1.7;
}
.warn-box{
background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.2);
border-radius:12px;padding:12px 16px;margin-bottom:18px;
font-size:13px;color:#fcd34d;line-height:1.7;
}
.tip-box{
background:rgba(16,185,129,.07);border:1px solid rgba(16,185,129,.2);
border-radius:12px;padding:12px 16px;margin-bottom:18px;
font-size:13px;color:#6ee7b7;line-height:1.7;
}
/* ─── Buttons ─── */
.btn-row{display:flex;gap:10px;margin-top:24px}
.btn-primary{
flex:1;background:linear-gradient(135deg,var(--accent),var(--accent2));
color:white;border:none;border-radius:12px;padding:13px 20px;
font-size:16px;font-weight:800;font-family:'Tajawal',sans-serif;
cursor:pointer;transition:opacity .2s,transform .1s;
}
.btn-primary:hover{opacity:.92;transform:translateY(-1px)}
.btn-primary:active{transform:translateY(0)}
.btn-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}
.btn-back{
background:var(--surface);color:var(--muted);
border:1.5px solid var(--border);border-radius:12px;padding:13px 20px;
font-size:14px;font-family:'Tajawal',sans-serif;font-weight:700;
cursor:pointer;transition:all .2s;
}
.btn-back:hover{border-color:var(--accent);color:var(--text)}
/* ─── Notifications ─── */
.notif{
border-radius:12px;padding:12px 16px;margin-bottom:16px;
font-size:13px;font-weight:600;display:none;
animation:fadeSlide .3s ease;
}
.notif.ok{background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);color:#6ee7b7}
.notif.err{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);color:#fca5a5}
/* ─── Launch animation ─── */
.launch-overlay{
position:fixed;inset:0;background:#070d1a;
display:none;align-items:center;justify-content:center;
flex-direction:column;gap:20px;z-index:9999;
}
.launch-overlay.show{display:flex}
.launch-logo{font-size:80px;animation:pulse 1s ease-in-out infinite}
@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}}
.launch-text{font-size:22px;font-weight:800;color:var(--accent)}
.launch-sub{font-size:14px;color:var(--muted)}
.launch-bar{
width:260px;height:6px;background:var(--surface);
border-radius:10px;overflow:hidden;
}
.launch-bar-fill{
height:100%;width:0;background:linear-gradient(90deg,var(--accent),var(--purple));
border-radius:10px;animation:fill 2.5s ease forwards;
}
@keyframes fill{to{width:100%}}
/* ─── Version select ─── */
optgroup{background:var(--surface);color:var(--muted);font-size:12px}
option{background:var(--surface);color:var(--text)}
</style>
</head>
<body>
<div id="particles"></div>
<div class="site-header">
<span class="logo">⛏️</span>
<h1>Sayed Bot Team v18</h1>
<p>بوتات Minecraft تعمل 24/7 — تبقي سيرفرك Aternos شغّال دايماً</p>
<div class="badge-row">
<div class="badge">🟢 <span>يعمل الآن</span></div>
<div class="badge">🔒 <span>9 طبقات حماية</span></div>
<div class="badge">🤖 <span>4 بوتات</span></div>
<div class="badge">⚡ <span>كشف بلاجن تلقائي</span></div>
</div>
</div>
<div class="progress-bar" id="progBar">
<div class="prog-step active" data-s="1" onclick="goStep(1)">
<div class="prog-dot">🌐</div>
<div class="prog-label">السيرفر</div>
</div>
<div class="prog-step" data-s="2" onclick="goStep(2)">
<div class="prog-dot">🎮</div>
<div class="prog-label">الإصدار</div>
</div>
<div class="prog-step" data-s="3" onclick="goStep(3)">
<div class="prog-dot">🤖</div>
<div class="prog-label">البوتات</div>
</div>
<div class="prog-step" data-s="4" onclick="goStep(4)">
<div class="prog-dot">🚀</div>
<div class="prog-label">تشغيل</div>
</div>
</div>
<div class="card">
<div class="card-top" id="cardTop">
<h2 id="stepTitle">🌐 خطوة 1 من 4 — بيانات السيرفر</h2>
<p id="stepSub">أدخل عنوان سيرفر Aternos والبورت</p>
</div>
<div class="card-body">
<div id="notif" class="notif"></div>
<!-- ══ STEP 1: Server ══ -->
<div class="step-pane active" id="pane1">
<div class="info-box">
📋 <strong>من أين تحصل على IP السيرفر؟</strong><br>
روح <strong>Aternos → My Servers → اختار السيرفر → Connect</strong><br>
هتلاقي عنوان زي: <code style="color:#60a5fa">myserver.aternos.me:25565</code>
</div>
<div class="field">
<label>🌐 عنوان السيرفر (IP) <span class="req">*</span></label>
<div class="input-wrap">
<span class="icon">🏠</span>
<input type="text" id="hostInput" placeholder="myserver.aternos.me"
value="${CFG.host||''}" autocomplete="off" spellcheck="false">
</div>
</div>
<div class="field">
<label>🔌 البورت <span class="tip">الافتراضي في Aternos هو 25565</span></label>
<div class="input-wrap">
<span class="icon">🔢</span>
<input type="number" id="portInput" placeholder="25565"
value="${CFG.port||25565}" min="1" max="65535">
</div>
</div>
<div class="field">
<label>🔍 اختبر الاتصال <span class="tip">تأكد إن السيرفر شغّال قبل المتابعة</span></label>
<div class="ping-row">
<button class="ping-btn" onclick="doPing()">📡 Ping</button>
<div class="ping-result" id="pingResult">
<span>⬜</span> <span id="pingText">لم يتم الاختبار بعد</span>
</div>
</div>
</div>
<div class="btn-row">
<button class="btn-primary" onclick="step1Next()">التالي ← إصدار السيرفر</button>
</div>
</div>
<!-- ══ STEP 2: Version ══ -->
<div class="step-pane" id="pane2">
<div class="tip-box">
💡 <strong>لا تعرف الإصدار؟</strong> اختر <strong>"كشف تلقائي"</strong> — البوت هيكتشف الإصدار لوحده عند الاتصال.<br>
لو عارف الإصدار حدده يدوياً للاتصال أسرع.
</div>
<div class="field">
<label>🎮 إصدار السيرفر</label>
<div class="input-wrap">
<span class="icon">📦</span>
<select id="verSelect">
<option value="auto" ${!CFG.version||CFG.version==='auto'?'selected':''}>🔍 كشف تلقائي (موصى به)</option>
${verOptions}
</select>
</div>
</div>
<div class="warn-box" id="verWarn" style="display:none">
⚠️ إصدارات Bedrock (26.x) تحتاج إن السيرفر يشتغل على Bedrock mode في Aternos
</div>
<div class="info-box">
✅ <strong>الإصدارات المدعومة:</strong><br>
Java: 1.0.0 ← 1.21.6 &nbsp;|&nbsp; Bedrock: 26.1.0 و 26.2.0
</div>
<div class="btn-row">
<button class="btn-back" onclick="goStep(1)">→ رجوع</button>
<button class="btn-primary" onclick="goStep(3)">التالي ← اختر البوتات</button>
</div>
</div>
<!-- ══ STEP 3: Bots ══ -->
<div class="step-pane" id="pane3">
<div class="field">
<label>🤖 كم بوت تريد؟ <span class="tip">كل بوت له شخصية مختلفة</span></label>
<div class="count-grid">
<div class="count-card" data-n="1" onclick="selectCount(1)">
<div class="num">1</div>
<div class="icons">⛏️</div>
<div class="lbl">بوت واحد</div>
</div>
<div class="count-card" data-n="2" onclick="selectCount(2)">
<div class="num">2</div>
<div class="icons">⛏️🏗️</div>
<div class="lbl">اثنان</div>
</div>
<div class="count-card selected" data-n="3" onclick="selectCount(3)">
<div class="num">3</div>
<div class="icons">⛏️🏗️🗺️</div>
<div class="lbl">ثلاثة</div>
</div>
<div class="count-card" data-n="4" onclick="selectCount(4)">
<div class="badge-rec">MAX</div>
<div class="num">4</div>
<div class="icons">⛏️🏗️🗺️🔴</div>
<div class="lbl">أربعة</div>
</div>
</div>
<input type="hidden" name="botCount" id="botCountHidden" value="${CFG.botCount||3}">
</div>
<div class="field" style="margin-top:20px">
<label>👤 أسماء البوتات <span class="tip">يجب أن تبدو كأسماء لاعبين حقيقيين</span></label>
<div class="bot-row" id="botRow1">
<div class="bot-icon">⛏️</div>
<div class="bot-info">
<small>حفّار — يحفر ويجمع خامات</small>
<input type="text" id="b1" value="${CFG.bot1Name||'Alex_miner26'}" placeholder="Alex_miner26" dir="ltr">
</div>
</div>
<div class="bot-row" id="botRow2">
<div class="bot-icon">🏗️</div>
<div class="bot-info">
<small>بنّاء — يبني ويجمع خشب وحجارة</small>
<input type="text" id="b2" value="${CFG.bot2Name||'Steve_builder'}" placeholder="Steve_builder" dir="ltr">
</div>
</div>
<div class="bot-row" id="botRow3">
<div class="bot-icon">🗺️</div>
<div class="bot-info">
<small>مستكشف — يجوب الخريطة ويكتشف</small>
<input type="text" id="b3" value="${CFG.bot3Name||'NightExplorer'}" placeholder="NightExplorer" dir="ltr">
</div>
</div>
<div class="bot-row" id="botRow4" style="display:none">
<div class="bot-icon">🔴</div>
<div class="bot-info">
<small>مهندس ريدستون — يعمل مزارع وأجهزة</small>
<input type="text" id="b4" value="${CFG.bot4Name||'RedstoneKing'}" placeholder="RedstoneKing" dir="ltr">
</div>
</div>
<div class="warn-box" style="margin-top:12px">
⚠️ <strong>مهم:</strong> اختار أسماء تبدو طبيعية مثل لاعب حقيقي (حروف + أرقام).<br>
تجنب كلمات مثل "bot" أو "robot" في الاسم — السيرفر قد يكشفها.
</div>
</div>
<div class="btn-row">
<button class="btn-back" onclick="goStep(2)">→ رجوع</button>
<button class="btn-primary" onclick="goStep(4)">التالي ← مراجعة وتشغيل</button>
</div>
</div>
<!-- ══ STEP 4: Review & Launch ══ -->
<div class="step-pane" id="pane4">
<div class="tip-box">
🚀 كل شيء جاهز! راجع الإعدادات وابدأ تشغيل البوتات.
</div>
<div style="background:var(--surface);border:1.5px solid var(--border);border-radius:14px;padding:16px 20px;margin-bottom:18px">
<div style="display:grid;grid-template-columns:auto 1fr;gap:8px 16px;font-size:14px;line-height:1.8">
<span style="color:var(--muted)">🌐 السيرفر</span> <span id="rv-host" style="font-weight:700;color:#60a5fa;direction:ltr">—</span>
<span style="color:var(--muted)">🔌 البورت</span> <span id="rv-port" style="font-weight:700">—</span>
<span style="color:var(--muted)">🎮 الإصدار</span> <span id="rv-ver" style="font-weight:700">—</span>
<span style="color:var(--muted)">🤖 البوتات</span> <span id="rv-bots" style="font-weight:700">—</span>
<span style="color:var(--muted)">👥 الأسماء</span> <span id="rv-names" style="font-weight:700;font-size:12px;direction:ltr">—</span>
</div>
</div>
<div class="info-box">
🔒 <strong>بعد التشغيل:</strong><br>
• افتح لوحة السيرفر في Aternos واعطِ OP للبوتات<br>
• البوتات ستكتشف كل البلاجن تلقائياً عند الاتصال<br>
• أضف <strong>UptimeRobot</strong> لإبقاء الـ Space شغّال 24/7
</div>
<div class="btn-row">
<button class="btn-back" onclick="goStep(3)">→ رجوع</button>
<button class="btn-primary" id="launchBtn" onclick="doLaunch()">🚀 ابدأ تشغيل البوتات!</button>
</div>
</div>
</div>
</div>
<!-- Launch Overlay -->
<div class="launch-overlay" id="launchOverlay">
<div class="launch-logo">⛏️</div>
<div class="launch-text">جاري تشغيل البوتات...</div>
<div class="launch-sub">يرجى الانتظار، سيتم التحويل تلقائياً</div>
<div class="launch-bar"><div class="launch-bar-fill"></div></div>
</div>
<script>
/* ═══ Particles ═══ */
(function(){
const c=document.getElementById('particles');
const emojis=['⛏️','⬛','🟫','💎','🪨','🌿','🔥','⭐'];
for(let i=0;i<18;i++){
const p=document.createElement('div');
p.className='p';
p.textContent=emojis[Math.floor(Math.random()*emojis.length)];
p.style.cssText=\`
left:\${Math.random()*100}%;
width:20px;height:20px;
font-size:\${12+Math.random()*14}px;
animation-duration:\${8+Math.random()*16}s;
animation-delay:-\${Math.random()*20}s;
opacity:\${0.05+Math.random()*0.1};
\`;
c.appendChild(p);
}
})();
/* ═══ State ═══ */
let curStep = 1;
let selectedCount = ${CFG.botCount||3};
/* ═══ Goto Step ═══ */
function goStep(n) {
if (n > curStep) {
if (curStep===1 && !validateStep1()) return;
}
curStep = n;
document.querySelectorAll('.step-pane').forEach(p=>p.classList.remove('active'));
document.getElementById('pane'+n).classList.add('active');
document.querySelectorAll('.prog-step').forEach(s=>{
const sn=parseInt(s.dataset.s);
s.classList.toggle('active', sn===n);
s.classList.toggle('done', sn<n);
});
const titles=['','🌐 خطوة 1 من 4 — بيانات السيرفر','🎮 خطوة 2 من 4 — إصدار السيرفر','🤖 خطوة 3 من 4 — اختيار البوتات','🚀 خطوة 4 من 4 — مراجعة وتشغيل'];
const subs=['','أدخل عنوان سيرفر Aternos والبورت','حدد إصدار السيرفر أو اتركه تلقائي','اختر عدد البوتات وأسمائهم','راجع الإعدادات واضغط تشغيل'];
document.getElementById('stepTitle').textContent=titles[n];
document.getElementById('stepSub').textContent=subs[n];
if (n===4) fillReview();
hideNotif();
window.scrollTo({top:0,behavior:'smooth'});
}
/* ═══ Step 1 ═══ */
function step1Next() {
if (!validateStep1()) return;
goStep(2);
}
function validateStep1() {
const h = document.getElementById('hostInput').value.trim();
if (!h) { showNotif('❌ يرجى إدخال عنوان السيرفر أولاً','err'); return false; }
if (h.includes(' ')) { showNotif('❌ العنوان لا يجب أن يحتوي على مسافات','err'); return false; }
return true;
}
/* ═══ Ping ═══ */
async function doPing() {
const host = document.getElementById('hostInput').value.trim();
const port = document.getElementById('portInput').value||'25565';
if (!host) { showNotif('❌ أدخل عنوان السيرفر أولاً','err'); return; }
const btn = document.querySelector('.ping-btn');
const res = document.getElementById('pingResult');
const txt = document.getElementById('pingText');
btn.disabled=true; btn.textContent='⏳ جاري...';
res.style.borderColor='var(--border)';
txt.textContent='جاري الاتصال...';
try {
const t=Date.now();
const r=await fetch('/api/ping-server?host='+encodeURIComponent(host)+'&port='+encodeURIComponent(port));
const d=await r.json();
const ms=Date.now()-t;
if (d.online) {
res.style.borderColor='var(--green)';
txt.innerHTML=\`<span style="color:var(--green)">✅ متصل!</span> السيرفر شغّال — Ping: \${d.ping||ms}ms — الإصدار: \${d.version||'غير معروف'}\`;
if (d.version && d.version!=='unknown') {
const vSel=document.getElementById('verSelect');
const opt=[...vSel.options].find(o=>o.value===d.version);
if (opt) { vSel.value=d.version; }
}
} else {
res.style.borderColor='var(--red)';
txt.innerHTML=\`<span style="color:var(--red)">❌ لا يمكن الاتصال</span> — تأكد إن السيرفر شغّال في Aternos\`;
}
} catch(e) {
res.style.borderColor='var(--yellow)';
txt.innerHTML=\`<span style="color:var(--yellow)">⚠️ تعذّر الاختبار</span> — يمكنك المتابعة وسيحاول البوت الاتصال\`;
}
btn.disabled=false; btn.textContent='📡 Ping';
}
/* ═══ Version ═══ */
document.getElementById('verSelect').addEventListener('change', function(){
const w=document.getElementById('verWarn');
w.style.display=(this.value.startsWith('26.'))?'block':'none';
});
/* ═══ Bot Count ═══ */
function selectCount(n){
selectedCount=n;
document.querySelectorAll('.count-card').forEach(c=>{
c.classList.toggle('selected', parseInt(c.dataset.n)===n);
});
document.getElementById('botCountHidden').value=n;
['botRow1','botRow2','botRow3','botRow4'].forEach((id,i)=>{
document.getElementById(id).style.display=(i<n)?'flex':'none';
});
}
selectCount(selectedCount);
/* ═══ Review ═══ */
function fillReview(){
const host=document.getElementById('hostInput').value.trim();
const port=document.getElementById('portInput').value||'25565';
const ver=document.getElementById('verSelect');
const verText=ver.options[ver.selectedIndex].text;
const names=[];
if(selectedCount>=1)names.push(document.getElementById('b1').value||'Alex_miner26');
if(selectedCount>=2)names.push(document.getElementById('b2').value||'Steve_builder');
if(selectedCount>=3)names.push(document.getElementById('b3').value||'NightExplorer');
if(selectedCount>=4)names.push(document.getElementById('b4').value||'RedstoneKing');
document.getElementById('rv-host').textContent=host;
document.getElementById('rv-port').textContent=port;
document.getElementById('rv-ver').textContent=verText;
document.getElementById('rv-bots').textContent=selectedCount+' بوتات';
document.getElementById('rv-names').textContent=names.join(' · ');
}
/* ═══ Launch ═══ */
async function doLaunch(){
const btn=document.getElementById('launchBtn');
btn.disabled=true; btn.textContent='⏳ جاري الإعداد...';
const data={
host:document.getElementById('hostInput').value.trim(),
port:parseInt(document.getElementById('portInput').value)||25565,
version:document.getElementById('verSelect').value,
botCount:selectedCount,
bot1Name:document.getElementById('b1').value||'Alex_miner26',
bot2Name:document.getElementById('b2').value||'Steve_builder',
bot3Name:document.getElementById('b3').value||'NightExplorer',
bot4Name:document.getElementById('b4').value||'RedstoneKing',
};
if(!data.host){showNotif('❌ عنوان السيرفر مطلوب','err');btn.disabled=false;btn.textContent='🚀 ابدأ تشغيل البوتات!';return;}
try{
const r=await fetch('/api/setup',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
});
const d=await r.json();
if(d.ok){
document.getElementById('launchOverlay').classList.add('show');
setTimeout(()=>location.href='/',3000);
} else {
showNotif('❌ '+(d.error||'خطأ غير معروف'),'err');
btn.disabled=false;btn.textContent='🚀 ابدأ تشغيل البوتات!';
}
} catch(e){
showNotif('❌ فشل الاتصال: '+e.message,'err');
btn.disabled=false;btn.textContent='🚀 ابدأ تشغيل البوتات!';
}
}
/* ═══ Notif ═══ */
function showNotif(msg,type){
const el=document.getElementById('notif');
el.textContent=msg; el.className='notif '+type; el.style.display='block';
clearTimeout(showNotif._t);
showNotif._t=setTimeout(()=>el.style.display='none',4000);
}
function hideNotif(){document.getElementById('notif').style.display='none';}
</script>
</body>
</html>`;
}
// ════════════════════════════════════════════════════════════════════════════
// EXPRESS SERVER
// ════════════════════════════════════════════════════════════════════════════
const app = express();
let coord = new TeamCoordinator();
app.use(express.json());
app.use(express.urlencoded({ extended:true }));
// ANTI-01: منع HuggingFace من إيقاف الـ Space
app.use((req, res, next) => {
res.setHeader('Cache-Control','no-store, no-cache, must-revalidate');
res.setHeader('X-Bot-Status','running');
next();
});
// Health Check — ANTI-01 + ANTI-02
app.get('/healthz', (req,res) => {
const online = coord.count();
// ANTI-02: لو كل البوتات في سبات عميق أكتر من 20 دقيقة → شغّل Aternos
const allSleep = coord.slots.length > 0 && coord.slots.every(s => s.deepSleep);
if (allSleep && !aternosRunning) {
const oldest = Math.min(...coord.slots.map(s => s._deepSleepStart || Date.now()));
if (Date.now() - oldest > 20 * 60000) {
sysLog('🔄', 'healthz: كل البوتات في سبات عميق — تشغيل Aternos');
triggerAternos();
}
}
res.json({
status:'ok', botsOnline:online, botsTotal:coord.slots.length,
uptime:Math.floor(process.uptime()), configured:CFG.isConfigured,
host:CFG.host, version:autoVersion||CFG.version,
plugins:detectedPlugins.length,
tps:performanceData.tps, lag:performanceData.lag,
allSleep, aternosRunning,
});
});
// ANTI-01: Ping endpoint
app.get('/ping', (req,res) => res.send('pong'));
// Setup: Test server connection from browser
app.get('/api/ping-server', async (req, res) => {
const host = String(req.query.host||'').trim().slice(0,200);
const port = Math.max(1, Math.min(65535, parseInt(req.query.port)||25565));
if (!host) { res.json({online:false, error:'no host'}); return; }
try {
const mcProto = require('minecraft-protocol');
const t = Date.now();
mcProto.ping({ host, port, closeTimeout:5000 }, (err, data) => {
if (err) { res.json({online:false, error:err.message}); return; }
const ping = Date.now()-t;
const version = data && (data.version?.name || data.version);
res.json({ online:true, ping, version: version||'unknown',
players: data && data.players
? { online: data.players.online, max: data.players.max } : null });
});
} catch(e) {
res.json({online:false, error:e.message});
}
});
// Dashboard
app.get('/', (req,res) => {
if (!CFG.isConfigured) { res.redirect('/setup'); return; }
res.send(getDashboardHTML(coord));
});
// Setup Wizard
app.get('/setup', (req,res) => res.send(getSetupHTML()));
// Save Config & Start Bots
app.post('/api/setup', async (req,res) => {
try {
const { host, port, version, botCount, bot1Name, bot2Name, bot3Name, bot4Name } = req.body;
if (!host || host.trim()==='') { res.json({ok:false,error:'عنوان السيرفر مطلوب'}); return; }
saveConfig({ host:host.trim(), port:parseInt(port)||25565, version:version||'auto',
botCount:parseInt(botCount)||4,
bot1Name:bot1Name||'Alex_miner26', bot2Name:bot2Name||'Steve_builder',
bot3Name:bot3Name||'NightExplorer', bot4Name:bot4Name||'RedstoneKing' });
sysLog('⚙️',`إعداد جديد: ${CFG.host}:${CFG.port} | إصدار: ${CFG.version}`);
addEvent('info',`⚙️ إعداد جديد: ${CFG.host}:${CFG.port}`);
// FIX-2: دمّر الـ coord القديم تماماً حتى لا يتصل البوتات القديمة من جديد
try { coord.destroy(); } catch(_){}
detectedPlugins.length = 0; // صفّر البلاجن للسيرفر الجديد
autoVersion = null;
await sleep(3000);
coord = new TeamCoordinator();
const teamCfg = buildTeamCfg();
teamCfg.forEach(c => coord.add(new BotSlot(c)));
coord.startAll();
res.json({ok:true});
} catch(e) {
res.json({ok:false,error:e.message});
}
});
// Bot Status API
app.get('/api/status', (req,res) => {
res.json({
ok:true, bots:coord.status(), online:coord.count(),
plugins:detectedPlugins,
performance:{ ...performanceData, ecoMode:performanceData.ecoMode },
config:{ host:CFG.host, port:CFG.port, version:CFG.version, autoVersion },
events:EVENT_LOG.slice(0,20),
aternosRunning,
hfSpaceUrl: HF_SPACE_URL || null,
});
});
// SEC: قائمة الأوامر المسموحة فقط من الداشبورد
const ALLOWED_BOT_CMDS = new Set(['stop','restart','plugins','optimize','come','follow','sleep','wake']);
// Bot Command API
app.post('/api/cmd', (req,res) => {
try {
const { botId, cmd } = req.body;
// SEC: تحقق من صحة الأمر قبل تنفيذه
if (!cmd || !ALLOWED_BOT_CMDS.has(String(cmd))) {
res.json({ok:false, error:`أمر غير مسموح: ${String(cmd).slice(0,20)}`}); return;
}
const slot = coord.slots[parseInt(botId)];
if (!slot) { res.json({ok:false,error:'بوت غير موجود'}); return; }
if (cmd==='stop') {
if (slot._reconnTimer) { clearTimeout(slot._reconnTimer); slot._reconnTimer=null; }
if (slot.bot) { try{slot.bot.quit();}catch(_){} }
res.json({ok:true,msg:'تم إيقاف البوت'});
} else if (cmd==='restart') {
if (slot.bot) { try{slot.bot.quit();}catch(_){} }
slot.deepSleep=false;
setTimeout(()=>slot.connect(),3000);
res.json({ok:true,msg:'جاري إعادة التشغيل'});
} else if (cmd==='plugins' && slot.bot) {
slot.scanPlugins(slot.bot);
res.json({ok:true,msg:'جاري فحص البلاجن'});
} else if (cmd==='optimize' && slot.bot && slot.isOp) {
slot.optimizePerformance(slot.bot);
res.json({ok:true,msg:'جاري تحسين الأداء'});
} else {
slot.cmdQueue.push({type:cmd, sender:'dashboard'});
res.json({ok:true,msg:`أمر [${cmd}] أُضيف للقائمة`});
}
} catch(e) { res.json({ok:false,error:e.message}); }
});
// Control All
app.post('/api/control', (req,res) => {
const { action } = req.body;
if (action==='stop') { coord.stopAll(); addEvent('info','⏹ تم إيقاف جميع البوتات'); }
if (action==='restart') { coord.restartAll(); addEvent('info','🔄 جاري إعادة تشغيل جميع البوتات'); }
res.json({ok:true});
});
// Grant OP يدوياً لبوت محدد وفحص البلاجن
app.post('/api/grant-op', (req,res) => {
try {
const { botId } = req.body;
const slot = coord.slots[botId];
if (!slot) { res.json({ok:false,error:'بوت غير موجود'}); return; }
if (!slot.bot) { res.json({ok:false,error:'البوت غير متصل حالياً'}); return; }
slot.isOp = true;
slot.scanPlugins(slot.bot, true);
addEvent('op',`👑 ${slot.cfg.username} مُنح OP يدوياً — جاري فحص البلاجن`);
res.json({ok:true, msg:`${slot.cfg.username} الآن OP — جاري فحص البلاجن (5 ثواني)`});
} catch(e){ res.json({ok:false,error:e.message}); }
});
// Force Scan — يفحص البلاجن بدون شرط OP (المستخدم منح OP يدوياً)
app.post('/api/force-scan', (req,res) => {
let scanned = 0;
for (const slot of coord.slots) {
if (slot.bot) {
slot.isOp = true;
slot.scanPlugins(slot.bot, true);
scanned++;
}
}
if (scanned === 0) {
res.json({ok:false, error:'لا يوجد بوت متصل حالياً'});
} else {
addEvent('plugin', `🔍 بدأ فحص البلاجن على ${scanned} بوت (force)`);
res.json({ok:true, msg:`جاري فحص البلاجن على ${scanned} بوت...`});
}
});
// Plugins API
app.get('/api/plugins', (req,res) => res.json({ok:true,plugins:detectedPlugins}));
// Performance API
app.get('/api/perf', (req,res) => res.json({ok:true,...performanceData}));
// CHAT-01: إرسال رسالة في السيرفر من الداشبورد
app.post('/api/send-chat', (req,res) => {
try {
const { botId, msg } = req.body;
if (!msg || !String(msg).trim()) { res.json({ok:false,error:'الرسالة فارغة'}); return; }
const slot = coord.slots[parseInt(botId) || 0];
if (!slot) { res.json({ok:false,error:'بوت غير موجود'}); return; }
if (!slot.bot) { res.json({ok:false,error:'البوت غير متصل حالياً'}); return; }
// SEC: rate limit — منع الـ spam
const now = Date.now();
const last = _chatRateMap.get(slot.cfg.id) || 0;
if (now - last < 2000) { res.json({ok:false,error:'انتظر ثانيتين بين الرسائل'}); return; }
_chatRateMap.set(slot.cfg.id, now);
// SEC: إزالة الـ control chars ومنع أوامر / المباشرة (إلا لو OP)
let cleanMsg = String(msg).trim()
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,'') // control chars
.slice(0, 200);
if (cleanMsg.startsWith('/') && !slot.isOp) {
res.json({ok:false, error:'أوامر / غير مسموحة بدون OP'}); return;
}
if (!cleanMsg) { res.json({ok:false,error:'رسالة فارغة بعد التنظيف'}); return; }
slot.bot.chat(cleanMsg);
addEvent('chat', `💬 [داشبورد→${slot.cfg.username}]: ${cleanMsg}`);
res.json({ok:true, msg:`تم الإرسال عبر ${slot.cfg.username}`});
} catch(e) { res.json({ok:false,error:e.message}); }
});
// ROLE-01: سجل اللاعبين API
app.get('/api/players', (req,res) => {
const list = [...playerRegistry.entries()].map(([name, p]) => ({
name, ...p, online: p.joinedAt !== null,
}));
res.json({ ok:true, total: playerRegistry.size, players: list });
});
// ANTI-02: تشغيل Aternos يدوياً من الداشبورد
app.post('/api/aternos-start', (req,res) => {
if (aternosRunning) {
res.json({ok:false, error:'Aternos يعمل بالفعل، انتظر حتى ينتهي'});
return;
}
triggerAternos();
res.json({ok:true, msg:'جاري تشغيل Aternos... تحقق من سجل الأحداث'});
});
// HF-ALIVE: إبقاء HuggingFace Space حي — طبقتين
// طبقة 1: ping داخلي يمنع Docker من اعتبار الـ container idle
// طبقة 2: ping خارجي على URL الـ Space (يمنع HF من إيقافه)
// HF_SPACE_URL: يُكتشف تلقائياً من SPACE_HOST (HuggingFace يضبطه تلقائياً)
const HF_SPACE_URL = process.env.HF_SPACE_URL ||
(process.env.SPACE_HOST ? `https://${process.env.SPACE_HOST}` : '') ||
(process.env.SPACE_ID ? `https://${process.env.SPACE_ID.replace('/','-')}.hf.space` : '');
function keepAlive() {
// — طبقة 1: self-ping داخلي ───────────────────────────────────────────
try {
http.get(`http://localhost:${CFG.dashPort}/healthz`, r => r.resume()).on('error',()=>{});
} catch(_){}
// — طبقة 2: ping خارجي على HF Space URL ───────────────────────────────
if (HF_SPACE_URL) {
try {
const https = require('https');
const target = HF_SPACE_URL.replace(/\/$/, '') + '/healthz';
const mod = target.startsWith('https') ? https : http;
const req = mod.get(target, { headers:{'User-Agent':'SayedBotTeam-KeepAlive/1.0'}, timeout:15000 }, r => {
r.resume();
sysLog('🏓', `HF ping → ${r.statusCode}`);
});
req.on('error', () => {});
req.on('timeout', () => req.destroy());
} catch(_){}
}
}
// HF-WAKE: endpoint مخصص للـ external monitors (cron-job.org / UptimeRobot)
app.get('/wake', (_req, res) => {
res.json({ ok:true, ts: Date.now(), bots: coord?.count() || 0 });
});
setInterval(keepAlive, 4*60000); // كل 4 دقائق — يمنع HF pause
// ════════════════════════════════════════════════════════════════════════════
// START
// ════════════════════════════════════════════════════════════════════════════
const server = app.listen(CFG.dashPort, '0.0.0.0', () => {
sysLog('🌐',`Dashboard على http://0.0.0.0:${CFG.dashPort}`);
if (!CFG.isConfigured) {
sysLog('⚙️','لا يوجد إعداد — افتح لوحة التحكم للإعداد');
} else {
sysLog('🚀',`بدء تشغيل ${CFG.botCount} بوت على ${CFG.host}:${CFG.port}`);
const teamCfg = buildTeamCfg();
teamCfg.forEach(c => coord.add(new BotSlot(c)));
coord.startAll().catch(() => {});
addEvent('info',`🚀 تشغيل ${CFG.botCount} بوتات على ${CFG.host}:${CFG.port}`);
}
});
// ANTI-01: keep server alive on socket errors
server.on('error', err => {
if (err.code==='EADDRINUSE') {
sysLog('❌',`البورت ${CFG.dashPort} مشغول!`);
process.exit(1);
}
sysLog('⚠️',`خطأ server: ${err.message}`);
});
// ANTI-01: graceful shutdown
['SIGTERM','SIGINT'].forEach(sig => {
process.on(sig, () => {
sysLog('👋','إيقاف...');
coord.stopAll();
server.close(() => process.exit(0));
});
});
sysLog('✅','Sayed Bot Team v18.0 — جاهز! ECO MODE + TPS Graph + Smart Tasks + HF Auto-URL');