// ==UserScript==
// @name [AlGzawy CLOUD] ربط السكربتات بالموقع
// @namespace https://github.com/TW-AlGzawy/AlGzawy-Scripts-Distribution
// @version 2.0.4
// @description Pair once with AlGzawy Cloud, then synchronize script ON/OFF and supported settings
// @include https://*.tribalwars.*/game.php*
// @run-at document-start
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @connect *
// @updateURL https://raw.githubusercontent.com/TW-AlGzawy/AlGzawy-Scripts-Distribution/main/loaders/algzawy-cloud-bridge.user.js
// @downloadURL https://raw.githubusercontent.com/TW-AlGzawy/AlGzawy-Scripts-Distribution/main/loaders/algzawy-cloud-bridge.user.js
// ==/UserScript==
(function () {
'use strict';
if (window.__algzawyCloudBridge) return;
window.__algzawyCloudBridge=true;
const keys={origin:'algzawy_cloud_portal_origin',device:'algzawy_cloud_device_token',last:'algzawy_cloud_last_success'};
const known=[
'mass-scavenge-v1','bot-farm-2','alg-balance','auto-builder-pro-algzawy-v3-3-fixed-queue','auto-builder-ar-v061',
'professional-training-v2','send-automation','attack-renamer','alg-attacks','warehouse-alert-safe',
'polish-all-in-one','alg-coins','market-bot-v7','auto-builder-ar-v054',
'auto-builder-pro-algzawy-v3-0-free-finish','training-edition','professional-training','light',
'train-master-b','reserve-smart-v5','reserve-smart-v4','reserve-correct-v2','auto-reserve-barbarians',
'holy-grail','tribal-wars-helper','source-copier'
];
const storageKey=id=>'algzawy_local_enabled_'+id;
const connection={
origin:String(GM_getValue(keys.origin,'')||''),
device:String(GM_getValue(keys.device,'')||''),
last:Number(GM_getValue(keys.last,0)||0)
};
let pending=false,lastRequest=0, timer=null, card=null;
const LICENSE_UNTIL='algzawy_portal_device_authorized_until';
const LICENSE_ID='algzawy_portal_approved_device';
const GRANT_PREFIX='algzawy_portal_grant_';
function normalizeOrigin(raw){
try{
const url=new URL(raw.trim());
if(url.protocol!=='https:'||url.username||url.password||url.search||url.hash||url.pathname!=='/')return '';
// Cloudflare Free Pages domains by default. Custom domains can be adopted
// later with an explicit allowlist instead of sending device tokens to arbitrary hosts.
if(!/^[a-z0-9-]+\.pages\.dev$/i.test(url.hostname))return '';
return url.origin;
}catch(_){return '';}
}
// IndexedDB stores a structured-cloned non-exportable CryptoKey. Tampermonkey
// export/GM storage contains only a public UUID, not the signing private key.
// A copied whole browser profile may still contain IndexedDB; this is not
// tamper-proof hardware attestation.
async function signingKey(){
if(!window.crypto?.subtle||!window.indexedDB)throw Error('متصفحك لا يدعم التخزين الآمن لمفتاح الجهاز');
const db=await new Promise((resolve,reject)=>{
const request=indexedDB.open('algzawy-device-identity-v2',1);
request.onupgradeneeded=()=>{if(!request.result.objectStoreNames.contains('keys'))request.result.createObjectStore('keys');};
request.onsuccess=()=>resolve(request.result);
request.onerror=()=>reject(Error('تعذر فتح مخزن هوية الجهاز'));
});
const existing=await new Promise((resolve,reject)=>{
const tx=db.transaction('keys','readonly');
const r=tx.objectStore('keys').get('primary');
r.onsuccess=()=>resolve(r.result);
r.onerror=()=>reject(Error('تعذر قراءة مفتاح الجهاز'));
});
if(existing?.privateKey && existing?.publicKey)return existing;
const pair=await crypto.subtle.generateKey({name:'ECDSA',namedCurve:'P-256'},false,['sign','verify']);
const result={privateKey:pair.privateKey,publicKey:await crypto.subtle.exportKey('jwk',pair.publicKey)};
await new Promise((resolve,reject)=>{
const tx=db.transaction('keys','readwrite');
tx.objectStore('keys').put(result,'primary');
tx.oncomplete=()=>resolve();
tx.onerror=()=>reject(Error('تعذر حفظ مفتاح الجهاز'));
});
return result;
}
async function deviceProof(){
const identity=await signingKey();
const timestamp=Date.now();
const counter=Math.max(timestamp,Number(GM_getValue('algzawy_cloud_counter',0)||0)+1);
const statement=connection.device+'|'+timestamp+'|'+counter+'|/api/device/state';
const signature=new Uint8Array(await crypto.subtle.sign({name:'ECDSA',hash:'SHA-256'},identity.privateKey,new TextEncoder().encode(statement)));
const binary=Array.from(signature,b=>String.fromCharCode(b)).join('');
const sig=btoa(binary).replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_');
GM_setValue('algzawy_cloud_counter',counter);
return {'X-ALG-DEVICE-ID':connection.device,'X-ALG-TIMESTAMP':String(timestamp),'X-ALG-COUNTER':String(counter),'X-ALG-SIGNATURE':sig};
}
function request(path,opts={}){
return new Promise((resolve,reject)=>{
const origin=normalizeOrigin(connection.origin);
if(!origin)return reject(Error('عنوان البوابة يجب أن يكون https://NAME.pages.dev'));
GM_xmlhttpRequest({
method:opts.method||'GET',url:origin+'/api/'+path,timeout:12000,
headers:{'Accept':'application/json',...(opts.headers||{}),...(opts.data?{'Content-Type':'application/json'}:{})},
data:opts.data?JSON.stringify(opts.data):undefined,
onload:response=>{
let data;try{data=JSON.parse(response.responseText);}catch(_){return reject(Error('تعذر قراءة رد Cloudflare'));}
if(response.status<200||response.status>=300||!data?.ok)return reject(Error(data?.error||'HTTP '+response.status));
resolve(data);
},
onerror:()=>reject(Error('مشكلة اتصال بالبوابة')),
ontimeout:()=>reject(Error('انتهت مهلة الاتصال'))
});
});
}
function showMessage(msg,good=false){
const el=document.getElementById('algzawy-cloud-status');
if(el){el.textContent=msg;el.style.color=good?'#46e8ae':'#ffcfcf';}
}
function applySettings(id,opts,enabled){
if(id==='bot-farm-2'){
const key='algzawy_bot2_settings_v02';
let prev={};try{prev=JSON.parse(localStorage.getItem(key)||'{}');}catch(_){}
const next={...prev,enabled};
if(opts && ['A','B','C'].includes(opts.template)){
next.template=opts.template;next.minDelay=opts.minDelay;
next.maxDelay=opts.maxDelay;next.switchVillages=opts.switchVillages;
}
if(JSON.stringify(prev)!==JSON.stringify(next))localStorage.setItem(key,JSON.stringify(next));
}
if(id==='warehouse-alert-safe'){
const key='algzawy_warehouse_alert_settings_v01';
let prev={};try{prev=JSON.parse(localStorage.getItem(key)||'{}');}catch(_){}
const next={...prev,enabled};
if(Number.isInteger(opts?.percent))next.percent=opts.percent;
if(JSON.stringify(prev)!==JSON.stringify(next))localStorage.setItem(key,JSON.stringify(next));
}
if(id==='professional-training-v2'){
let village='';
try{village=String(unsafeWindow.game_data?.village?.id||'');}catch(_){}
if(village){
const key='algzawy_training_v22_user_settings_'+village;
let prev={};try{prev=JSON.parse(localStorage.getItem(key)||'{}');}catch(_){}
const next={...prev,enabled};
if(JSON.stringify(prev)!==JSON.stringify(next))localStorage.setItem(key,JSON.stringify(next));
}
}
}
function setLocal(id,value){
const key=storageKey(id),newVal=value?'true':'false';
if(localStorage.getItem(key)===newVal)return false;
localStorage.setItem(key,newVal);return true;
}
function stopAll(){
localStorage.removeItem(LICENSE_UNTIL);
localStorage.removeItem(LICENSE_ID);
let changed=false;
for(const id of known){localStorage.setItem(GRANT_PREFIX+id,'0');changed=setLocal(id,false)||changed;}
if(changed)document.dispatchEvent(new Event('algzawy-local-control-changed'));
}
// One coordinator polls the publisher switch for all script engines on this
// game tab. This protects the Cloudflare Free request budget.
const PUBLISHER_STATUS='https://algzawy-control-api.karm-saleh-usa2.workers.dev/status';
let publisherPending=false,publisherPreviouslyOn=null;
function checkPublisher(){
if(!connection.device||publisherPending||document.visibilityState==='hidden')return;
publisherPending=true;
GM_xmlhttpRequest({
method:'GET',url:PUBLISHER_STATUS+'?t='+Date.now(),timeout:6000,
headers:{'Cache-Control':'no-store'},
onload:r=>{
publisherPending=false;
let enabled=false;
try{const data=JSON.parse(r.responseText);enabled=r.status===200&&data.schema===1&&data.enabled===true&&Number.isInteger(data.revision);}catch(_){}
if(!enabled){
publisherPreviouslyOn=false;
stopAll();
showMessage('الإدارة أوقفت المنظومة أو تعذر التحقق من Cloudflare');
}else{
if(publisherPreviouslyOn===false)void synchronize(true);
publisherPreviouslyOn=true;
}
},
onerror:()=>{publisherPending=false;publisherPreviouslyOn=false;stopAll();showMessage('تعذر الاتصال بالتحكم المركزي؛ تم الإيقاف احترازيًا');},
ontimeout:()=>{publisherPending=false;publisherPreviouslyOn=false;stopAll();showMessage('انتهت مهلة التحكم المركزي؛ تم الإيقاف احترازيًا');}
});
}
// Fail closed if cached authorization is stale. A live worker response is
// always needed at least every 180s, while loaders independently enforce
// the publisher's existing Cloudflare global switch.
// Fail closed immediately on page load, until signed device verification.
// Never reuse an authorization left in page storage at browser startup.
// Every new game page must get a fresh signed server decision first.
stopAll();
async function synchronize(force=false){
if(pending||!connection.device)return;
if(!force&&Date.now()-lastRequest<20000)return;
pending=true;lastRequest=Date.now();
try{
const r=await request('device/state',{headers:await deviceProof()});
if(!r.global||typeof r.global.enabled!=='boolean'||!r.scripts||typeof r.scripts!=='object')throw Error('استجابة مزامنة غير مكتملة');
localStorage.setItem(LICENSE_UNTIL,String(Date.now()+120000));
localStorage.setItem(LICENSE_ID,connection.device);
let changed=false;
for(const id of known){
const script=r.scripts[id];
const enabled=!!(r.global.enabled&&script&&script.allowed===true&&script.enabled===true);
localStorage.setItem(GRANT_PREFIX+id,enabled?'1':'0');
changed=setLocal(id,enabled)||changed;
applySettings(id,script?.settings||{},enabled);
}
connection.last=Date.now();GM_setValue(keys.last,connection.last);
if(changed)document.dispatchEvent(new Event('algzawy-local-control-changed'));
showMessage((r.global.enabled?'متصل — ': 'الإدارة أوقفت جميع السكربتات — ')+(r.user?.label||'الحساب'),r.global.enabled);
}catch(e){
if(e.message==='device_pending_approval'){stopAll();showMessage('الجهاز بانتظار موافقة الإدارة؛ لا تعمل السكربتات قبل اعتماده.');}
else if(['device_proof_invalid','unauthorized','subscription_inactive'].includes(e.message)){stopAll();showMessage('تم رفض ترخيص الجهاز: '+e.message);}
else {if(Date.now()-connection.last>120000)stopAll();showMessage('تعذرت المزامنة: '+e.message);}
}finally{pending=false;}
}
function render(){
if(card||!document.body)return;
card=document.createElement('section');
card.id='algzawy-cloud-pair-panel';card.dir='rtl';
card.style.cssText='position:fixed;bottom:16px;left:15px;z-index:2147482010;width:245px;max-width:93vw;background:#101d2c;color:#e4f4ff;border:1px solid #2d8576;border-radius:11px;box-shadow:0 10px 28px #0007;font:11px/1.65 Tahoma,Arial;overflow:hidden';
card.innerHTML='
يلزم تثبيت سكربتات اللعبة المختارة إلى جانب هذا الجسر.