Merhaba 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