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