SAwww / memory-store.js
SAyyeedd's picture
Upload 30 files
309ec79 verified
Raw
History Blame Contribute Delete
15 kB
'use strict';
const fs = require('fs');
const path = require('path');
const sleep = ms => new Promise(r => setTimeout(r, ms));
const tsNow = () => new Date().toISOString().replace('T', ' ').slice(0, 19);
const log = (tag, msg) => console.log(`[${tsNow()}] [MEMORY] ${tag} ${msg}`);
const _sanitizeName = name => name.replace(/[^a-zA-Z0-9\-_.]/g, '-').replace(/^-+|-+$|\.git$|\.ipynb$/g, '') || 'sayed-bot-data';
const $O = () => process.env.HF_USER || '';
const $N = () => _sanitizeName(process.env.DATASET_NAME || 'sayed-bot-data');
const $T = () => process.env.HF_TOKEN || '';
const $remote = () => !!($O() && $T());
const AUTO_FILES = ['logs.txt', 'bot-state.json', 'memory.json'];
const INITIAL_SETTINGS = { host: '', port: 25565, version: 'auto', botCount: 3, owner: '', autoReconnect: true, language: 'ar', optimizationCycleSec: 30, saveIntervalMin: 5, dashboardPort: 7860 };
const INITIAL_NAMES = { names: ['Alex_miner26', 'Steve_builder', 'NightExplorer', 'RedstoneKing'], count: 3, suffix: '' };
class MemoryStore {
constructor() {
this._cache = {};
this._ready = false;
this._lastWriteTime = null;
this._writtenFiles = new Set();
this._initDone = false;
this._remoteEnsured = false;
this._ensuringDataset = null;
this._datasetReady = false;
this._remoteFailCount = 0;
this._remoteFailTime = 0;
if ($remote()) {
log('☁️', `HF Dataset: ${$O()}/${$N()}`);
} else {
log('📁', 'وضع محلي — حفظ محلي');
}
this._cache.settings = INITIAL_SETTINGS;
this._ready = true;
this._initDone = true;
if ($remote()) {
this._ensureDatasetQuiet().then(ok => {
if (ok) log('✅', 'Dataset جاهز');
});
}
}
get _localMode() { return !$remote(); }
async _ensureDatasetQuiet() {
if (this._datasetReady) return true;
if (this._ensuringDataset) return this._ensuringDataset;
this._ensuringDataset = this._ensureDatasetInternal();
const result = await this._ensuringDataset;
this._ensuringDataset = null;
return result;
}
async _ensureRepoInit(owner, name, token) {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const resp = await fetch(`https://huggingface.co/api/datasets/${owner}/${name}/commit/main`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': 'sayed-bot/1.0' },
body: JSON.stringify({ files: [{ path: '.gitkeep', content: '' }], summary: 'init' }),
signal: AbortSignal.timeout(30000),
});
if (resp.ok) { log('✅', 'تم تهيئة الـ Repo (main branch)'); return true; }
if (resp.status === 404 && attempt < 2) {
await sleep(10000 * (attempt + 1));
continue;
}
if (attempt < 2) { await sleep(5000); continue; }
log('⚠️', `فشل init: HTTP ${resp.status}`);
return false;
} catch (e) {
if (e.name !== 'AbortError') log('⚠️', `init error: ${e.message}`);
if (attempt < 2) { await sleep(5000); continue; }
return false;
}
}
return false;
}
async _ensureDatasetInternal() {
const owner = $O(), name = $N(), token = $T();
if (!owner || !token) return false;
for (let attempt = 0; attempt < 3; attempt++) {
try {
const checkResp = await fetch(`https://huggingface.co/api/datasets/${owner}/${name}`, {
headers: { 'User-Agent': 'sayed-bot/1.0', 'Authorization': `Bearer ${token}` },
signal: AbortSignal.timeout(15000),
});
if (checkResp.ok) {
// Dataset موجود — نتأكد إنه مش فاضي
const data = await checkResp.json().catch(() => ({}));
const siblings = data.siblings || [];
if (siblings.length === 0) {
log('⚠️', 'Dataset موجود لكن فاضي — جاري init...');
await sleep(5000);
const initOk = await this._ensureRepoInit(owner, name, token);
if (!initOk) { if (attempt < 2) { await sleep(5000); continue; } return false; }
}
this._datasetReady = true;
return true;
}
if (checkResp.status !== 404) {
log('⚠️', `التحقق من Dataset: HTTP ${checkResp.status}`);
if (attempt < 2) { await sleep(3000 * (attempt + 1)); continue; }
return false;
}
} catch (e) {
if (e.name === 'AbortError') { log('⚠️', 'مهلة التحقق من Dataset'); if (attempt < 2) { await sleep(3000); continue; } return false; }
log('⚠️', `خطأ في التحقق: ${e.message}`);
if (attempt < 2) { await sleep(3000); continue; }
return false;
}
log('🔄', `إنشاء Dataset: ${owner}/${name}...`);
try {
const createResp = await fetch('https://huggingface.co/api/repos/create', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': 'sayed-bot/1.0' },
body: JSON.stringify({ type: 'dataset', name }),
signal: AbortSignal.timeout(30000),
});
if (createResp.ok || createResp.status === 409) {
log('✅', `تم إنشاء Dataset: ${owner}/${name}`);
await sleep(15000);
const initOk = await this._ensureRepoInit(owner, name, token);
if (initOk) {
this._datasetReady = true;
return true;
}
log('⚠️', 'فشلت تهيئة الـ Dataset بعد 3 محاولات');
if (attempt < 2) { await sleep(10000); continue; }
return false;
}
const errText = await createResp.text().catch(() => '');
log('⚠️', `فشل إنشاء Dataset: HTTP ${createResp.status}: ${errText.slice(0, 200)}`);
if (attempt < 2) { await sleep(5000); continue; }
return false;
} catch (e) {
if (e.name === 'AbortError') { log('⚠️', 'مهلة إنشاء Dataset'); if (attempt < 2) { await sleep(5000); continue; } return false; }
log('⚠️', `خطأ في إنشاء Dataset: ${e.message}`);
if (attempt < 2) { await sleep(5000); continue; }
return false;
}
}
return false;
}
async _getFileList() {
const owner = $O(), name = $N(), token = $T();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const resp = await fetch(`https://huggingface.co/api/datasets/${owner}/${name}`, {
headers: { 'User-Agent': 'sayed-bot/1.0', 'Authorization': `Bearer ${token}` },
signal: AbortSignal.timeout(15000),
});
if (!resp.ok) { if (attempt < 2) { await sleep(3000); continue; } return new Set(); }
const data = await resp.json();
if (!data.siblings || !Array.isArray(data.siblings)) return new Set();
return new Set(data.siblings.map(s => s.rfilename));
} catch (e) {
if (attempt < 2) { await sleep(3000); continue; }
return new Set();
}
}
return new Set();
}
async read(filename) {
if (this._localMode) { return this._readLocal(filename); }
return this._readRemote(filename);
}
async write(filename, data) {
if (this._localMode) { return this._writeLocal(filename, data); }
if (!this._datasetReady) {
const ok = await this._ensureDatasetQuiet();
if (!ok) {
log('⚠️', 'لا يمكن الوصول للـ Dataset — حفظ محلي');
return this._writeLocal(filename, data);
}
}
if (this._remoteFailCount > 5 && (Date.now() - this._remoteFailTime) < 300000) {
return this._writeLocal(filename, data);
}
for (let i = 0; i < 3; i++) {
const ok = await this._writeRemote(filename, data);
if (ok) { this._remoteFailCount = 0; return true; }
if (i < 2) await sleep(3000 * (i + 1));
}
this._remoteFailCount = (this._remoteFailCount || 0) + 1;
this._remoteFailTime = Date.now();
log('⚠️', `فشلت 3 محاولات لرفع ${filename} — حفظ محلي`);
return this._writeLocal(filename, data);
}
async _readLocal(filename) {
try {
const p = path.join(__dirname, filename);
if (!fs.existsSync(p)) return null;
const raw = fs.readFileSync(p, 'utf8');
if (filename.endsWith('.json')) return JSON.parse(raw);
return raw;
} catch(e) {
log('⚠️', `قراءة محلية فاشلة ${filename}: ${e.message}`);
return null;
}
}
async _writeLocal(filename, data) {
try {
const p = path.join(__dirname, filename);
const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
fs.writeFileSync(p, content, 'utf8');
this._lastWriteTime = Date.now();
this._writtenFiles.add(filename);
log('💾', `محلي: ${filename}${content.length}B`);
return true;
} catch(e) {
log('❌', `كتابة محلية فاشلة ${filename}: ${e.message}`);
return false;
}
}
async _readRemote(filename) {
const owner = $O(), name = $N();
const urls = [
`https://huggingface.co/datasets/${owner}/${name}/raw/main/${filename}`,
`https://huggingface.co/api/datasets/${owner}/${name}/raw/main/${filename}`,
];
for (const url of urls) {
try {
const resp = await fetch(url, {
headers: { 'User-Agent': 'sayed-bot/1.0' },
signal: AbortSignal.timeout(15000),
});
if (resp.ok) {
const text = await resp.text();
if (filename.endsWith('.json')) return JSON.parse(text);
return text;
}
} catch (_) {}
}
return null;
}
async _writeRemote(filename, data) {
const token = $T(), owner = $O(), name = $N();
if (!token) {
log('⚠️', 'HF_TOKEN غير موجود — حفظ محلي');
return this._writeLocal(filename, data);
}
const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
// === المحاولة 1: Commit API ===
{
const commitBody = {
files: [{ path: filename, content: Buffer.from(content, 'utf8').toString('base64') }],
summary: `Auto-save ${filename} by Sayed Bot`,
};
try {
const resp = await fetch(`https://huggingface.co/api/datasets/${owner}/${name}/commit/main`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': 'sayed-bot/1.0' },
body: JSON.stringify(commitBody),
signal: AbortSignal.timeout(60000),
});
if (resp.ok) {
log('☁️', `${filename}${content.length}B (commit)`);
this._lastWriteTime = Date.now(); this._writtenFiles.add(filename);
return true;
}
if (resp.status === 401) {
log('❌', `رفع فاشل ${filename}: HTTP 401 — تأكد من HF_TOKEN`);
return false;
}
if (resp.status !== 404 && resp.status !== 400) {
log('❌', `رفع فاشل ${filename}: HTTP ${resp.status}`);
return false;
}
} catch (e) {
if (e.name !== 'AbortError') log('⚠️', `commit: ${e.message}`);
}
}
// === المحاولة 2: Raw PUT (مسارات مختلفة) ===
{
const putUrls = [
`https://huggingface.co/api/datasets/${owner}/${name}/raw/main/${filename}`,
`https://huggingface.co/datasets/${owner}/${name}/raw/main/${filename}`,
];
for (const putUrl of putUrls) {
try {
const rawResp = await fetch(putUrl, {
method: 'PUT',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/octet-stream', 'User-Agent': 'sayed-bot/1.0' },
body: Buffer.from(content, 'utf8'),
signal: AbortSignal.timeout(60000),
});
if (rawResp.ok) {
log('☁️', `${filename}${content.length}B (raw PUT)`);
this._lastWriteTime = Date.now(); this._writtenFiles.add(filename);
return true;
}
if (rawResp.status !== 404 && rawResp.status !== 400) {
log('❌', `رفع raw فاشل ${filename}: HTTP ${rawResp.status}`);
return false;
}
} catch (e) {
if (e.name !== 'AbortError') log('⚠️', `raw PUT: ${e.message}`);
}
}
}
// === المحاولة 3: إعادة init الـ repo و commit ===
{
log('🔄', `محاولة إعادة init الـ repo لـ ${filename}...`);
const initOk = await this._ensureRepoInit(owner, name, token);
if (initOk) {
try {
const resp = await fetch(`https://huggingface.co/api/datasets/${owner}/${name}/commit/main`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': 'sayed-bot/1.0' },
body: JSON.stringify({
files: [{ path: filename, content: Buffer.from(content, 'utf8').toString('base64') }],
summary: `Auto-save ${filename} by Sayed Bot`,
}),
signal: AbortSignal.timeout(60000),
});
if (resp.ok) {
log('☁️', `${filename}${content.length}B (after init)`);
this._lastWriteTime = Date.now(); this._writtenFiles.add(filename);
return true;
}
} catch (_) {}
}
}
log('❌', `جميع طرق الرفع فشلت لـ ${filename} — حفظ محلي`);
return false;
}
isReady() { return this._ready; }
isLocal() { return this._localMode; }
getStatus() {
return {
connected: $remote() && this._datasetReady,
local: this._localMode,
ready: this._ready,
lastWrite: this._lastWriteTime,
files: Array.from(this._writtenFiles),
datasetOwner: $O(),
datasetName: $N(),
hasToken: !!$T(),
};
}
getSettings() { return this._cache.settings || {}; }
async saveBotState(state) { this._enqueueWrite('bot-state.json', state); }
async saveMemory(memory) { this._enqueueWrite('memory.json', memory); }
async appendLog(line) {
const existing = (await this.read('logs.txt')) || '';
const updated = (existing + '\n' + line).slice(-50000);
this._enqueueWrite('logs.txt', updated);
}
_enqueueWrite(filename, data) {
if (!this._writeQueue) this._writeQueue = {};
if (!this._flushTimer) {
this._flushTimer = setTimeout(() => this._flushQueue(), 30000);
}
this._writeQueue[filename] = data;
}
async _flushQueue() {
this._flushTimer = null;
const queue = this._writeQueue || {};
this._writeQueue = {};
for (const [filename, data] of Object.entries(queue)) {
await this.write(filename, data);
}
}
}
module.exports = { MemoryStore, INITIAL_SETTINGS, INITIAL_NAMES };