| 'use strict'; |
| try { require('dotenv').config(); } catch (_) {} |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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'); |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| 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 (_) {} |
| } |
|
|
| |
| |
| |
| 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'); |
| |
| |
| setTimeout(() => { |
| aternosRunning = false; |
| sysLog('🔄', 'ANTI-02: إعادة محاولة الاتصال...'); |
| }, 3 * 60000); |
| } |
|
|
| |
| |
| |
| 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); } |
| }); |
| } |
|
|
| |
| |
| |
| 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 }, |
|
|
| |
| '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 }; |
| } |
|
|
| |
| |
| |
| 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}`); |
|
|
| |
| 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); |
|
|
| |
| |
| |
| 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:'يرحّب باللاعبين ويتواصل معهم' }, |
| ]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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']], |
| }; |
|
|
| |
| |
| |
| const playerRegistry = new Map(); |
| const _chatRateMap = new Map(); |
|
|
| |
| const teamState = { lastReport: null, eventPending: false }; |
|
|
| |
| |
| |
| 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', |
| ]; |
|
|
| |
| |
| |
| 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!!', |
| ]; |
|
|
| |
| |
| |
| function buildTeamCfg() { |
| const names = [CFG.bot1Name, CFG.bot2Name, CFG.bot3Name, CFG.bot4Name]; |
| const count = Math.min(CFG.botCount, 4); |
| |
| const lightMode = count <= 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; |
| } |
|
|
| |
| |
| |
| 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; } |
| } |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| 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; }, |
| }; |
|
|
| |
| |
| |
| 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 = []; |
| 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; } |
|
|
| |
| async scanPlugins(bot, force=false) { |
| if (force) this.isOp = true; |
| this.log('🔍','فحص البلاجن...'); |
| addEvent('plugin','🔍 بدأ فحص البلاجن على السيرفر'); |
| if (this.isOp) { |
| |
| const commands = ['/plugins','/pl','/pluginlist']; |
| for (const cmd of commands) { |
| try { bot.chat(cmd); await sleep(3000); break; } catch(_) {} |
| } |
| } else { |
| |
| try { bot.chat('/version'); await sleep(2000); } catch(_){} |
| try { bot.chat('/help'); await sleep(2000); } catch(_){} |
| |
| 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'); |
| } |
| } |
|
|
| |
| async monitorPerformance(bot, gen) { |
| while (this.alive(bot, gen)) { |
| try { |
| await sleep(60000); |
| if (!this.alive(bot, gen)) continue; |
| performanceData.lastCheck = tsNow(); |
|
|
| |
| if (this.isOp) { |
| try { bot.chat('/tps'); } catch(_){} |
| await sleep(2000); |
| } |
|
|
| |
| const ping = this.avgPing || 0; |
| if (ping > 500 || performanceData.lag) { |
| this.log('⚠️','اكتُشف lag عالي (ping: ' + ping + 'ms)'); |
| addEvent('perf',`⚠️ Ping عالي: ${ping}ms — السيرفر يعاني lag`); |
| |
| if (this.isOp) await this.optimizePerformance(bot); |
| |
| } |
| } 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(); |
|
|
| |
| 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)} — عودة للنشاط الكامل`); |
| } |
| } |
| } |
| } |
| } |
|
|
| |
| 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 }; |
| } |
|
|
| |
| scheduleReconnect(delay) { |
| if (this.deepSleep) return; |
| 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); |
| } |
|
|
| |
| async connect() { |
| if (this.deepSleep) return; |
| if (this.bot || this.connecting) return; |
| |
| 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; |
|
|
| |
| 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); |
| |
| 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); |
| } |
|
|
| |
| if (username==='') { |
| this.parseTpsFromChat(message); |
| } |
| }); |
|
|
| |
| const detectOpText = (raw) => { |
| if (this.isOp) return; |
| 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); |
| } |
| }; |
|
|
| bot.on('message', (jsonMsg) => { |
| try { |
| |
| const t1 = jsonMsg.toString(); |
| detectOpText(t1); |
| this.parsePluginsFromChat(t1.toLowerCase()); |
| this.parseTpsFromChat(t1); |
| |
| try { |
| const j = jsonMsg.json || jsonMsg; |
| if (j && j.text) detectOpText(j.text); |
| if (j && j.translate) detectOpText(j.translate); |
| |
| if (j && Array.isArray(j.extra)) { |
| j.extra.forEach(ex => { if(ex && ex.text) detectOpText(ex.text); }); |
| } |
| } catch(_){} |
| } catch(_){} |
| }); |
|
|
| |
| try { |
| bot._client.on('chat', (packet) => { |
| try { |
| const raw = typeof packet.message==='string' |
| ? packet.message |
| : JSON.stringify(packet.message||''); |
| detectOpText(raw); |
| |
| 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; |
| |
| 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(); |
| }); |
|
|
| |
| 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}`); |
| |
| |
| 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 — السيرفر غير متاح`); |
| |
| if (!aternosRunning) triggerAternos(); |
| setTimeout(()=>{ |
| this.deepSleep=false; |
| this._deepSleepStart=null; |
| this.connRefused=0; |
| this.scheduleReconnect(5000); |
| }, d); |
| return; |
| } |
| } |
| this.log('❌',`خطأ: ${err.message}`); |
| }); |
|
|
| |
| 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(); |
| }); |
|
|
| |
| 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; |
| this.opActionsRun=false; |
| 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}`); |
| }); |
| } |
|
|
| |
| |
| 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(); |
|
|
| |
| 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); |
| |
| await sleep(randInt(5000, 12000)); |
| try { bot.pathfinder.setGoal(null); } catch(_){} |
| |
| 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'); } |
| } |
|
|
| |
| else if (r < 0.52 && bot.entity) { |
| if (!self.tasks.tryAcquire('keep_mine',0,18000)) return; |
| try { |
| const yaw = bot.entity.yaw; |
| |
| 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'); } |
| } |
|
|
| |
| 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(); } |
| } |
| } |
|
|
| |
| 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)); |
| } |
| } |
| |
| try { await bot.look(bot.entity?.yaw||0, 0.8, false); } catch(_){} |
| await sleep(randInt(800, 2000)); |
| } |
|
|
| |
| 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)); |
| |
| bot.setControlState('jump', true); |
| await sleep(250); |
| bot.setControlState('jump', false); |
| self.clearCtrl(); |
| } catch(_){ self.clearCtrl(); } |
| finally { self.tasks.release('keep_sneak'); } |
| } |
| } |
|
|
| |
| const interval = this.cfg.lightBot ? randInt(150000, 220000) |
| : performanceData.ecoMode ? randInt(120000, 180000) |
| : randInt(50000, 100000); |
| this._keepTimer = setInterval(() => { |
| |
| 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); |
| } |
|
|
| |
| async personalityLoop(bot, gen) { |
| await humanSleep(randInt(5000,20000), randInt(20000,40000)); |
| while (this.alive(bot, gen)) { |
| try { |
| |
| 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; } |
| |
| 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); } |
| } |
| } |
|
|
| |
| async chatLoop(bot, gen) { |
| await humanSleep(randInt(60000,180000), randInt(180000,360000)); |
| while (this.alive(bot,gen)) { |
| try { |
| |
| 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); } |
| } |
| } |
|
|
| |
| 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; |
|
|
| |
| if (food < 8 && this.isOp && !this.eating) { |
| try { |
| bot.chat('/give @s cooked_beef 32'); |
| addEvent('info', `🥩 ${this.cfg.username} أعطى نفسه أكل (جوع حاد)`); |
| await sleep(2500); |
| } catch(_) {} |
| } |
|
|
| |
| 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) { |
| |
| 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(_) { |
| |
| } finally { |
| this.eating = false; |
| } |
| } |
| } catch(_) { await sleep(15000); } |
| } |
| } |
|
|
| |
| 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);} |
| } |
| } |
|
|
| |
| 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);} |
| } |
| } |
|
|
| |
| 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);} |
| } |
| } |
|
|
| |
| 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`); |
| |
| 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(_) {} |
| } |
| } |
|
|
| |
| 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; |
|
|
| |
| |
| |
| const tasks = this.assignedTasks.length > 0 |
| ? this.assignedTasks |
| : (this.role ? [this.role.id] : []); |
|
|
| const realPlayers = () => Object.keys(bot.players || {}).filter(n => |
| coord?.slots.every(s => s.cfg.username !== n) |
| ); |
|
|
| |
| |
| |
| 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}`); |
| |
| 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; |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| 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(_) {} |
| } |
| } |
|
|
| |
| |
| |
| 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); |
| } |
| } |
|
|
| |
| |
| |
| 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(_) {} |
| } |
| } |
|
|
| |
| |
| |
| 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 { |
| |
| try { bot.chat(`⚠️ TPS=${tps.toFixed(1)} — السيرفر شايل شوية، خليكوا light!`); } catch(_) {} |
| } |
| } else if (tps != null && tps >= 18 && performanceData.ecoMode) { |
| |
| addEvent('perf', `✅ Optimizer: TPS عادل (${tps.toFixed(1)}) — ECO mode سيُخفَّف`); |
| } |
| } |
|
|
| } catch(_) { await sleep(5000); } |
| } |
| } |
|
|
| |
| handleChat(sender, message, bot, gen) { |
| |
| const pReg = playerRegistry.get(sender); |
| if (pReg) pReg.lastChat = Date.now(); |
|
|
| |
| this.parsePluginsFromChat(message); |
| |
| 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, |
| }; |
| } |
| } |
|
|
| |
| |
| |
| 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) => { |
| |
| 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); } |
| } |
|
|
| |
| |
| |
| setInterval(() => { |
| if (EVENT_LOG.length > 100) EVENT_LOG.length = 100; |
| if (performanceData.history.length > 20) performanceData.history.length = 20; |
|
|
| |
| if (playerRegistry.size > 200) { |
| const toDelete = [...playerRegistry.entries()] |
| .filter(([,p]) => p.joinedAt === null) |
| .slice(0, playerRegistry.size - 200); |
| toDelete.forEach(([k]) => playerRegistry.delete(k)); |
| } |
|
|
| |
| 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); |
|
|
| |
| |
| |
| 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(' '); |
| |
| 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>`; |
| } |
|
|
| |
| |
| |
| 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>`; |
|
|
| } |
|
|
| |
| |
| |
| 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 | 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>`; |
| } |
|
|
| |
| |
| |
| const app = express(); |
| let coord = new TeamCoordinator(); |
|
|
| app.use(express.json()); |
| app.use(express.urlencoded({ extended:true })); |
|
|
| |
| app.use((req, res, next) => { |
| res.setHeader('Cache-Control','no-store, no-cache, must-revalidate'); |
| res.setHeader('X-Bot-Status','running'); |
| next(); |
| }); |
|
|
| |
| app.get('/healthz', (req,res) => { |
| const online = coord.count(); |
| |
| 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, |
| }); |
| }); |
|
|
| |
| app.get('/ping', (req,res) => res.send('pong')); |
|
|
| |
| 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}); |
| } |
| }); |
|
|
| |
| app.get('/', (req,res) => { |
| if (!CFG.isConfigured) { res.redirect('/setup'); return; } |
| res.send(getDashboardHTML(coord)); |
| }); |
|
|
| |
| app.get('/setup', (req,res) => res.send(getSetupHTML())); |
|
|
| |
| 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}`); |
| |
| 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}); |
| } |
| }); |
|
|
| |
| 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, |
| }); |
| }); |
|
|
| |
| const ALLOWED_BOT_CMDS = new Set(['stop','restart','plugins','optimize','come','follow','sleep','wake']); |
|
|
| |
| app.post('/api/cmd', (req,res) => { |
| try { |
| const { botId, cmd } = req.body; |
| |
| 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}); } |
| }); |
|
|
| |
| 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}); |
| }); |
|
|
| |
| 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}); } |
| }); |
|
|
| |
| 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} بوت...`}); |
| } |
| }); |
|
|
| |
| app.get('/api/plugins', (req,res) => res.json({ok:true,plugins:detectedPlugins})); |
|
|
| |
| app.get('/api/perf', (req,res) => res.json({ok:true,...performanceData})); |
|
|
| |
| 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; } |
|
|
| |
| 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); |
|
|
| |
| let cleanMsg = String(msg).trim() |
| .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,'') |
| .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}); } |
| }); |
|
|
| |
| 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 }); |
| }); |
|
|
| |
| app.post('/api/aternos-start', (req,res) => { |
| if (aternosRunning) { |
| res.json({ok:false, error:'Aternos يعمل بالفعل، انتظر حتى ينتهي'}); |
| return; |
| } |
| triggerAternos(); |
| res.json({ok:true, msg:'جاري تشغيل Aternos... تحقق من سجل الأحداث'}); |
| }); |
|
|
| |
| |
| |
| |
| 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() { |
| |
| try { |
| http.get(`http://localhost:${CFG.dashPort}/healthz`, r => r.resume()).on('error',()=>{}); |
| } catch(_){} |
|
|
| |
| 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(_){} |
| } |
| } |
|
|
| |
| app.get('/wake', (_req, res) => { |
| res.json({ ok:true, ts: Date.now(), bots: coord?.count() || 0 }); |
| }); |
|
|
| setInterval(keepAlive, 4*60000); |
|
|
| |
| |
| |
| 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}`); |
| } |
| }); |
|
|
| |
| server.on('error', err => { |
| if (err.code==='EADDRINUSE') { |
| sysLog('❌',`البورت ${CFG.dashPort} مشغول!`); |
| process.exit(1); |
| } |
| sysLog('⚠️',`خطأ server: ${err.message}`); |
| }); |
|
|
| |
| ['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'); |
|
|