Расскажу, и сразу важное уточнение: для headless-наблюдателя вручную JWT доставать не нужно. В .env кладём DICECHESS_USERNAME + DICECHESS_PASSWORD, а Playwright сам логинится и читает JWT из storage (ровно тем же способом, что ниже, только автоматически). Токен живёт недолго — хардкодить его в .env смысла нет, он протухнет. Но посмотреть его глазами полезно (понять формат и срок жизни), так что вот как.

Где лежит JWT после логина

Способ A — Network (самый надёжный: видно, что реально шлёт сайт).

  1. Залогинься на dicechess.com.
  2. F12 → вкладка Network → фильтр api.
  3. Кликни любой запрос /api/...HeadersRequest Headers → строка authorization: Bearer eyJ…. То, что после Bearer — и есть JWT.

Способ B — Application/Storage (где он хранится). F12 → Application (Chrome) → Local Storage и Session Storagehttps://dicechess.com. Ищи значение, которое начинается с eyJ и содержит две точки (header.payload.signature). Имя ключа обычно token/jwt/accessToken/authToken/Authorization (иногда зашит внутри JSON-объекта).

Способ C — консоль (найдёт сам и напечатает). Вставь в DevTools-консоль на dicechess.com:

(() => {
  const isJwt = v => typeof v === 'string' && /^(Bearer\s+)?eyJ[\w-]+\.[\w-]+\.[\w-]+$/.test(v.trim());
  const hits = [];
  for (const [name, store] of [['localStorage', localStorage], ['sessionStorage', sessionStorage]]) {
    for (let i = 0; i < store.length; i++) {
      const key = store.key(i), val = store.getItem(key);
      if (isJwt(val)) hits.push([`${name}.${key}`, val.trim()]);
      else if (val && val[0] === '{') {
        try { for (const [k, x] of Object.entries(JSON.parse(val))) if (isJwt(x)) hits.push([`${name}.${key} → ${k}`, String(x).trim()]); } catch {}
      }
    }
  }
  for (const p of document.cookie.split(';')) { const v = decodeURIComponent(p.split('=').slice(1).join('=').trim()); if (isJwt(v)) hits.push(['cookie', v]); }
  hits.length ? hits.forEach(([w, t]) => console.log(w, '\n', t, '\n')) : console.log('JWT не найден — залогинься и повтори');
})();

Хочешь узнать срок жизни токена — вставь его на jwt.io (или раскодируй middle-часть) и посмотри claim exp (Unix-время). Это подскажет, как часто наблюдателю перелогиниваться.

Что реально нужно для сборки

Поскольку логин за Cloudflare, авто-логин я сделаю проще всего так: Playwright открывает сайт, заполняет форму логина в UI (твои креды из .env), жмёт вход, потом читает JWT из storage — селекторы подберу вживую. То есть от тебя нужен только заполненный .env с username/password, и я добью login-модуль и прогоню сквозняк.

Кстати — ты зашарил репо на GitHub: хочешь, привяжу remote и запушу текущий коммит e894e73? Если да — кинь URL репозитория (или подтверди имя rabestro/dicechess-observer), проверю и сделаю git push -u.


Вот сниппет — вставь в консоль на dicechess.com (залогиненным): он сам находит JWT, декодирует payload и печатает iat/exp, срок жизни и сколько осталось.

(() => {
  const isJwt = v => typeof v === 'string' && /^(Bearer\s+)?eyJ[\w-]+\.[\w-]+\.[\w-]+$/.test(v.trim());
  let tok = null;
  for (const s of [localStorage, sessionStorage]) for (let i = 0; i < s.length && !tok; i++) {
    const v = s.getItem(s.key(i));
    if (isJwt(v)) tok = v.trim();
    else if (v && v[0] === '{') { try { for (const x of Object.values(JSON.parse(v))) if (isJwt(x)) { tok = String(x).trim(); break; } } catch {} }
  }
  if (!tok) { console.log('JWT не найден — залогинься и повтори'); return; }
  tok = tok.replace(/^Bearer\s+/i, '');
  let b = tok.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
  b += '='.repeat((4 - b.length % 4) % 4);
  const p = JSON.parse(atob(b));
  const fmt = s => s ? new Date(s * 1000).toLocaleString() : '—';
  console.log('payload:', p);
  console.log('issued  (iat):', fmt(p.iat));
  console.log('expires (exp):', fmt(p.exp));
  if (p.iat && p.exp) console.log('срок жизни :', ((p.exp - p.iat) / 3600).toFixed(1), 'ч  (', ((p.exp - p.iat) / 86400).toFixed(1), 'дней )');
  if (p.exp) console.log('осталось   :', ((p.exp - Date.now() / 1000) / 3600).toFixed(1), 'ч');
})();

На что смотреть:

  • exp — момент истечения; срок жизни = exp − iat. Это и есть, как часто придётся перелогиниваться вручную (Turnstile).
  • Если в payload есть что-то вроде refresh/rt, или в Local Storage рядом лежит отдельный refresh-токен — скажи: тогда сессию можно продлевать автоматически без капчи, и ручной логин будет почти никогда.

Альтернатива вручную: скопировать токен и вставить на jwt.io — там exp/iat видно в разобранном payload.

Кинь, что покажет (срок жизни и есть ли refresh) — и подберём стратегию обновления сессии.