[ÜCRETSİZ] R10'a HitBot Bırakıyorum 🚀 Proxy + Browser Worker + Canlı Panel + Raporlama Açık Kaynak
14
●706
- 19-09-2026, 16:07:00Merhaba eline sağlık hocam, sistemi kurdum ve bir kaç sorun gördüm ve düzelttim. Kullanacak arkadaşlar faydalanabilirler,
Senaryo ekleniyor fakat panelde gözükmüyordu. Yapılan işlemlerde senaryo işlenmiyordu
- Kaydedilen senaryonun görünmeme sorununu düzeltir.
- Test Merkezi'ne Senaryo seçimi ekler.
- Senaryoyu backend → Queue → Worker sistemine bağlar.
- Senaryodaki URL'leri gerçekten ziyaret ettirir.
- Her oturumda URL sırasını rastgele karıştırır.
- Her sayfaya rastgele bekleme süresi verir.
- Senaryo seçilmişse Contextual Click yerine yalnızca senaryo sayfalarını kullanır.
- Senaryo seçilmemişse eski çalışma şekline dokunmaz.
- Değişikliklerden önce otomatik yedek alır.
- İşlem sonunda Docker container'ını yeniden build eder.
cd /opt/trafficbot || exit 1 bash <<'PATCH' set -euo pipefail cd /opt/trafficbot echo "==========================================" echo " ALPDAGTAS Senaryo Sistemi Patch" echo "==========================================" BACKUP="/opt/trafficbot/backup-scenario-$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP" cp src/web/WebPanelServer.ts "$BACKUP/" cp src/infrastructure/queue/QueueService.ts "$BACKUP/" cp src/application/traffic/TrafficOrchestrator.ts "$BACKUP/" cp public/app.js "$BACKUP/" echo "[1/3] Yedek alındı:" echo "$BACKUP" python3 - <<'PY' from pathlib import Path import re root = Path("/opt/trafficbot") # ========================================================== # QueueService.ts # Senaryo URL'lerini BullMQ job verisine ekle # ========================================================== p = root / "src/infrastructure/queue/QueueService.ts" s = p.read_text() if "scenarioUrls?: string[];" not in s: needle = """ intensity: 'low' | 'medium' | 'high'; """ if needle not in s: raise SystemExit( "HATA: QueueService.ts içerisinde intensity alanı bulunamadı." ) s = s.replace( needle, needle + """ scenarioUrls?: string[]; """, 1 ) p.write_text(s) print("OK: QueueService scenarioUrls eklendi.") else: print("SKIP: QueueService zaten güncel.") # ========================================================== # WebPanelServer.ts # Senaryoyu test job'una bağla # ========================================================== p = root / "src/web/WebPanelServer.ts" s = p.read_text() if "scenarioUrls=(scenario?.paths||[])" not in s: match = re.search( r" private async queueTest\(site:any,requested:number,duration:number,ownerId:string\)\{.*?\}\n private visible", s, re.S ) if not match: raise SystemExit( "HATA: WebPanelServer queueTest fonksiyonu bulunamadı." ) new_queue = """ private async queueTest(site:any,requested:number,duration:number,ownerId:string,scenario?:any){const d=this.store.get(),n=Math.max(1,Math.min(5000,requested)),mins=Math.max(1,Math.min(1440,duration)),base=new URL(site.url),scenarioUrls=(scenario?.paths||[]).map((x:any)=>{try{const u=new URL(String(x),base);return u.origin===base.origin?u.toString():null}catch{return null}}).filter(Boolean) as string[],test:any={id:'test_'+Date.now().toString(36),siteId:site.id,siteName:site.name,url:site.url,requested:n,durationMinutes:mins,status:'queued',createdAt:new Date().toISOString(),jobIds:[],ownerId,scenarioId:scenario?.id||'',scenarioName:scenario?.name||''};this.store.addTest(test);const concurrency=Math.max(1,Math.min(500,Number(d.settings.maxConcurrency||5)));QueueService.setConcurrency(concurrency);const p=d.settings.proxy;const proxy=p?.enabled&&p.host&&p.port?{host:p.host,port:Number(p.port),username:p.username,password:p.password}:undefined;const ids:string[]=[];for(let i=0;i<n;i++){const id=await QueueService.addSession({url:site.url,durationMinutes:mins,intensity:'medium',proxy,scenarioUrls});if(id)ids.push(id)}this.store.updateTest(test.id,{jobIds:ids,status:ids.length?'running':'stopped'});return{testId:test.id,queued:ids.length,requested:n,scenarioId:scenario?.id||null,scenarioName:scenario?.name||null}} private visible""" s = ( s[:match.start()] + new_queue + s[match.end():] ) print("OK: queueTest senaryo desteği eklendi.") else: print("SKIP: queueTest zaten güncel.") # ---------------------------------------------------------- # /api/tests POST içerisinde scenarioId desteği # ---------------------------------------------------------- if "const scenarioId=String(b.scenarioId||'')" not in s: old = """if(p==='/api/tests'&&m==='POST'){if(!QueueService.isEnabled())return json(res,409,{error:'Bot kapalı. Sunucu bölümünden botu başlatın.'});const b=await body(req),d:any=this.visible(this.store.get(),me),site=d.sites.find((x:any)=>x.id===b.siteId);if(!site)return json(res,404,{error:'Site bulunamadı'});const n=Math.max(1,Math.min(5000,Number(b.requested||site.defaultConcurrency)));if(!this.store.consumeCredits(me.id,n))return json(res,402,{error:`Yetersiz kredi. ${n} kredi gerekli.`});return json(res,202,{ok:true,...await this.queueTest(site,n,Number(b.durationMinutes||site.durationMinutes),me.id)})}""" new = """if(p==='/api/tests'&&m==='POST'){if(!QueueService.isEnabled())return json(res,409,{error:'Bot kapalı. Sunucu bölümünden botu başlatın.'});const b=await body(req),d:any=this.visible(this.store.get(),me),site=d.sites.find((x:any)=>x.id===b.siteId);if(!site)return json(res,404,{error:'Site bulunamadı'});const scenarioId=String(b.scenarioId||''),scenario=scenarioId?d.scenarios.find((x:any)=>x.id===scenarioId&&x.siteId===site.id):undefined;if(scenarioId&&!scenario)return json(res,400,{error:'Seçilen senaryo bu siteye ait değil veya bulunamadı'});const n=Math.max(1,Math.min(5000,Number(b.requested||site.defaultConcurrency)));if(!this.store.consumeCredits(me.id,n))return json(res,402,{error:`Yetersiz kredi. ${n} kredi gerekli.`});return json(res,202,{ok:true,...await this.queueTest(site,n,Number(b.durationMinutes||site.durationMinutes),me.id,scenario)})}""" if old not in s: raise SystemExit( "HATA: /api/tests POST bölümü bulunamadı." ) s = s.replace(old, new, 1) print("OK: API scenarioId desteği eklendi.") else: print("SKIP: API scenarioId zaten mevcut.") p.write_text(s) # ========================================================== # public/app.js # Senaryo listeleme + Test Merkezi seçimi # ========================================================== p = root / "public/app.js" s = p.read_text() # Senaryolar kaydoluyor ama görünmüyor hatası if "function scenarios(){let list=state.settings.scenarios||[];" in s: s = s.replace( "function scenarios(){let list=state.settings.scenarios||[];", "function scenarios(){let list=state.scenarios||[];", 1 ) print("OK: Senaryo listeleme hatası düzeltildi.") else: print("SKIP: Senaryo listeleme zaten güncel.") # ---------------------------------------------------------- # Test Merkezi'ne Senaryo dropdown # ---------------------------------------------------------- if "Senaryo yok (rastgele gezinme)" not in s: new_tests = """function tests(){let scenarios=state.scenarios||[];return `<div class="card"><h2>Test Merkezi</h2><form class="form" onsubmit="startTest(event)"><label>Site<select name="siteId">${state.sites.map(s=>`<option value="${s.id}">${esc(s.name)}</option>`)}</select></label><label>Senaryo<select name="scenarioId"><option value="">Senaryo yok (rastgele gezinme)</option>${scenarios.map(x=>{let s=state.sites.find(z=>z.id===x.siteId);return `<option value="${x.id}">${esc(x.name)} · ${esc(s?.name||'')}</option>`}).join('')}</select></label><label>Toplam oturum<input name="requested" type="number" value="5" min="1" max="5000"></label><label>Süre (dk)<input name="duration" type="number" value="3" min="1"></label><button class="btn full">Testi Başlat</button></form></div><br><div class="card">${testTable(state.tests)}</div>`}""" s, count = re.subn( r"function tests\(\)\{.*?\}(?=function testTable)", new_tests, s, count=1, flags=re.S ) if count != 1: raise SystemExit( "HATA: app.js tests() fonksiyonu bulunamadı." ) print("OK: Test Merkezi'ne Senaryo seçimi eklendi.") else: print("SKIP: Senaryo seçimi zaten mevcut.") # ---------------------------------------------------------- # startTest içerisine scenarioId # ---------------------------------------------------------- if "scenarioId:f.scenarioId.value||''" not in s: old = """async function startTest(e){e.preventDefault();let f=e.target;try{let r=await api('/api/tests',{method:'POST',body:{siteId:f.siteId.value,requested:+f.requested.value,durationMinutes:+f.duration.value}});""" new = """async function startTest(e){e.preventDefault();let f=e.target;try{let r=await api('/api/tests',{method:'POST',body:{siteId:f.siteId.value,scenarioId:f.scenarioId.value||'',requested:+f.requested.value,durationMinutes:+f.duration.value}});""" if old not in s: raise SystemExit( "HATA: startTest() fonksiyonu bulunamadı." ) s = s.replace(old, new, 1) print("OK: startTest scenarioId gönderecek.") else: print("SKIP: startTest zaten güncel.") p.write_text(s) # ========================================================== # TrafficOrchestrator.ts # Senaryoyu gerçekten çalıştır # ========================================================== p = root / "src/application/traffic/TrafficOrchestrator.ts" s = p.read_text() # run metoduna scenarioUrls parametresi old_signature = """ async run(session: Session, options: Partial<BrowserOptions> = {}): Promise<void> {""" new_signature = """ async run(session: Session, options: Partial<BrowserOptions> = {}, scenarioUrls: string[] = []): Promise<void> {""" if old_signature in s: s = s.replace( old_signature, new_signature, 1 ) print("OK: TrafficOrchestrator scenarioUrls parametresi eklendi.") elif new_signature in s: print("SKIP: TrafficOrchestrator parametresi zaten mevcut.") else: raise SystemExit( "HATA: TrafficOrchestrator run() bulunamadı." ) # ---------------------------------------------------------- # Normal gezinme bloğunu senaryo destekli hale getir # ---------------------------------------------------------- if "Scenario plan generated" not in s: block = re.search( r' // Execute 4 steps with randomized "Thinking Heatmaps" \(non-linear stay times\).*? await this\.performContextualClick\(\);\n \}', s, re.S ) if not block: raise SystemExit( "HATA: TrafficOrchestrator gezinme bloğu bulunamadı." ) new_block = """ // Senaryo seçildiyse senaryo yolları kullanılır. // Senaryo seçilmediyse eski Contextual Click sistemi devam eder. const plannedScenarioUrls = Array.isArray(scenarioUrls) ? [...scenarioUrls.filter(Boolean)] : []; // Her oturumda URL sırasını ayrı ayrı karıştır. for (let i = plannedScenarioUrls.length - 1; i > 0; i--) { const j = Math.floor( Math.random() * (i + 1) ); [ plannedScenarioUrls[i], plannedScenarioUrls[j] ] = [ plannedScenarioUrls[j], plannedScenarioUrls[i] ]; } const numSteps = plannedScenarioUrls.length > 0 ? plannedScenarioUrls.length : 4; // Oturum süresinin yaklaşık %80'i sayfalara dağıtılır. const totalLoopTime = Math.floor(config.durationMs * 0.8); // Her sayfaya farklı random ağırlık oluştur. const stayWeights = Array.from( { length: numSteps }, () => 0.25 + (Math.random() * 1.75) ); const totalWeight = stayWeights.reduce( (a, b) => a + b, 0 ); // Minimum 3 saniye. const stayDurations = stayWeights.map( w => Math.max( 3000, Math.floor( (w / totalWeight) * totalLoopTime ) ) ); logger.info( 'Scenario plan generated', { scenarioUrls: plannedScenarioUrls, stayDurations } ); for ( let i = 0; i < numSteps; i++ ) { if ( plannedScenarioUrls.length > 0 ) { const targetUrl = plannedScenarioUrls[i]; logger.info( `Scenario navigation ${i + 1}/${numSteps}`, { url: targetUrl } ); await this.engine.navigate( targetUrl ); await this.engine.waitForNetworkIdle(); } const currentStay = stayDurations[i]; logger.info( `Step ${i+1}/${numSteps}: Staying for ${currentStay}ms...` ); if (Config.HUMAN_BEHAVIOR) { const stepStart = Date.now(); while ( Date.now() - stepStart < currentStay ) { await BehaviorService.simulateRandomAction( this.engine, config.viewport, { intensity: Config.BEHAVIOR_INTENSITY } ); } } else { await this.engine.wait( currentStay ); } // Senaryo seçilmemişse eski sistem. if ( plannedScenarioUrls.length === 0 ) { await this.performContextualClick(); } }""" s = ( s[:block.start()] + new_block + s[block.end():] ) print("OK: Random sıra ve random süreli senaryo motoru eklendi.") else: print("SKIP: Senaryo motoru zaten mevcut.") # ---------------------------------------------------------- # Job'daki scenarioUrls'i run() metoduna aktar # ---------------------------------------------------------- old_call = """ await this.run(session, { headless: Config.HEADLESS, platform: fingerprint.platform, fingerprintScript: FingerprintService.getInjectionScript(fingerprint) });""" new_call = """ await this.run(session, { headless: Config.HEADLESS, platform: fingerprint.platform, fingerprintScript: FingerprintService.getInjectionScript(fingerprint) }, Array.isArray(data.scenarioUrls) ? data.scenarioUrls : []);""" if old_call in s: s = s.replace( old_call, new_call, 1 ) print("OK: Worker senaryo URL'lerini TrafficOrchestrator'a aktaracak.") elif new_call in s: print("SKIP: Worker bağlantısı zaten mevcut.") else: raise SystemExit( "HATA: runFromJob çağrısı bulunamadı." ) p.write_text(s) print("") print("==========================================") print(" SENARYO PATCH BAŞARIYLA UYGULANDI") print("==========================================") PY echo "" echo "[2/3] Docker yeniden build ediliyor..." docker compose up -d --build traffic-bot echo "" echo "[3/3] Container durumu:" docker compose ps echo "" echo "==========================================" echo " PATCH TAMAMLANDI" echo "==========================================" echo "" echo "Paneli Ctrl + F5 ile yenileyin." echo "" echo "Yedek dizini:" echo "$BACKUP" echo "" PATCH