Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MM2 Auto-Seller</title>
<style>
:root{
--bg:#0d1220; --panel:#151b2e; --panel2:#1b2338; --line:#27324d;
--txt:#dfe6f5; --mut:#8b96ad; --blue:#4da3ff; --green:#28c76f;
--warn:#ffb400; --red:#ff5c5c;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1200px 600px at 20% -10%,#1a2440 0%,var(--bg) 55%);
color:var(--txt);font-family:'Segoe UI',system-ui,-apple-system,sans-serif;min-height:100vh}
.wrap{max-width:1180px;margin:0 auto;padding:22px}
header{display:flex;align-items:center;gap:14px;margin-bottom:20px}
.logo{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#4da3ff,#8b5cff);
display:grid;place-items:center;font-weight:800;font-size:20px;color:#fff}
header h1{font-size:20px;margin:0;font-weight:700}
header p{margin:2px 0 0;color:var(--mut);font-size:13px}
.badge{margin-left:auto;font-size:12px;padding:6px 12px;border-radius:999px;
background:#1b2338;border:1px solid var(--line);color:var(--mut)}
.badge.on{color:#0c1320;background:var(--green);border-color:var(--green);font-weight:700}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
@media(max-width:900px){.grid{grid-template-columns:1fr}}
.card{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:18px}
.card h2{font-size:14px;margin:0 0 14px;color:var(--mut);text-transform:uppercase;letter-spacing:.06em}
textarea{width:100%;height:210px;background:var(--panel2);border:1px solid var(--line);border-radius:12px;
color:var(--txt);padding:12px;font-family:'Consolas','Menlo',monospace;font-size:12px;resize:vertical}
textarea:focus{outline:none;border-color:var(--blue)}
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:12px}
button{border:none;cursor:pointer;border-radius:10px;padding:11px 18px;font-size:14px;font-weight:600;
color:#fff;transition:.15s;font-family:inherit}
button:disabled{opacity:.45;cursor:not-allowed}
.btn-blue{background:linear-gradient(135deg,#3d8bff,#7a5cff)}
.btn-blue:hover:not(:disabled){filter:brightness(1.12)}
.btn-gray{background:#232c44}
.btn-gray:hover:not(:disabled){background:#2c3755}
.meta{display:flex;gap:14px;flex-wrap:wrap;margin-top:14px}
.chip{font-size:12px;background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:6px 10px;color:var(--mut)}
.chip b{color:var(--txt)}
/* log */
.logbox{background:#0a0f1d;border:1px solid var(--line);border-radius:12px;height:260px;overflow:auto;
padding:12px;font-family:'Consolas','Menlo',monospace;font-size:12px;white-space:pre-wrap}
.logbox::-webkit-scrollbar,.tbl::-webkit-scrollbar{width:9px;height:9px}
.logbox::-webkit-scrollbar-thumb,.tbl::-webkit-scrollbar-thumb{background:#2c3755;border-radius:6px}
.l-info{color:var(--mut)} .l-ok{color:var(--green)} .l-warn{color:var(--warn)}
.l-err{color:var(--red)} .l-sys{color:#6b7690} .l-title{color:var(--blue);font-weight:700}
.colhead{display:flex;align-items:center;margin:16px 0 10px}
.colhead h3{font-size:14px;margin:0;color:var(--txt)}
.count{margin-left:auto;font-size:12px;color:var(--mut)}
/* stat cards */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:18px 0 4px}
@media(max-width:600px){.stats{grid-template-columns:1fr}}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px}
.stat .lab{font-size:12px;color:var(--mut);text-transform:uppercase;letter-spacing:.06em}
.stat .val{font-size:26px;font-weight:800;margin-top:6px;color:var(--txt)}
.stat .sub{font-size:12px;color:var(--mut);margin-top:2px}
.stat.green .val{color:var(--green)}
.stat.blue .val{color:var(--blue)}
.tbl{background:var(--panel2);border:1px solid var(--line);border-radius:12px;height:300px;overflow:auto}
table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:680px}
th,td{padding:9px 12px;text-align:left;border-bottom:1px solid #222b44;white-space:nowrap}
th{position:sticky;top:0;background:#1b2338;color:var(--mut);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.05em;z-index:2}
td img{width:30px;height:30px;border-radius:6px;object-fit:cover;background:#0a0f1d}
.status{display:inline-flex;align-items:center;gap:6px;padding:4px 9px;border-radius:999px;font-size:11px;font-weight:600}
.st-pending{background:#2a3550;color:#c3cbe0}
.st-success{background:rgba(40,199,111,.16);color:var(--green)}
.st-error{background:rgba(255,92,92,.16);color:var(--red)}
.dot{width:7px;height:7px;border-radius:50%;background:currentColor}
.empty{padding:40px;text-align:center;color:var(--mut)}
.foot{color:var(--mut);font-size:12px;margin-top:16px;text-align:center}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="logo">MM</div>
<div>
<h1>MM2 Market — Автовыставление</h1>
<p>Вставь cURL инвентаря — софт сам найдёт onSale=false и выставит на продажу по цене −20%</p>
</div>
<div class="badge" id="badge">Ожидание</div>
</header>
<div class="grid">
<div class="card">
<h2>1. Вставь cURL запрос инвентаря</h2>
<textarea id="curl" placeholder="curl --url 'https://mm2-market.apineural.com/api/inventory/items/fs/139373/c/rub' \ ..."></textarea>
<div class="row">
<button class="btn-blue" id="btnStart">▶ Запустить</button>
<button class="btn-gray" id="btnExample">Пример</button>
<button class="btn-gray" id="btnClear">Очистить</button>
</div>
<div class="meta" id="meta" style="display:none">
<span class="chip">Roblox ID: <b id="mAcc">—</b></span>
<span class="chip">Валюта: <b id="mCur">—</b></span>
<span class="chip">Заголовков: <b id="mHdr">—</b></span>
<span class="chip">Токен: <b id="mTok">—</b></span>
</div>
</div>
<div class="card">
<h2>2. Лог запуска</h2>
<div class="logbox" id="log"></div>
</div>
</div>
<div class="stats">
<div class="stat">
<div class="lab">Найдено (onSale=false)</div>
<div class="val" id="stFound">0</div>
<div class="sub">заблокировано для продажи</div>
</div>
<div class="stat blue">
<div class="lab">Выставлено предметов</div>
<div class="val" id="stListed">0</div>
<div class="sub">успешно добавлено на продажу</div>
</div>
<div class="stat green">
<div class="lab">Общая сумма выставления</div>
<div class="val" id="stTotal">0 ₽</div>
<div class="sub">по цене −20%</div>
</div>
</div>
<div class="colhead">
<h3>Найденные для продажи</h3>
<div class="count" id="cnt">0 предметов</div>
</div>
<div class="tbl">
<table>
<thead>
<tr>
<th>Иконка</th><th>Название</th><th>Product ID</th><th>ID предмета</th>
<th>База (₽)</th><th>Цена выст. (₽)</th><th>Статус</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="empty" id="empty" style="display:none">Тут появятся предметы, у которых onSale = false</div>
</div>
<div class="foot">Пауза между запросами 1 сек · выставление пачками по 10 предметов · лог дублируется в консоль</div>
</div>
<script>
const $ = s => document.querySelector(s);
let state = {logs:[], jobs:[], running:false};
const lvlClass = {info:'l-info',ok:'l-ok',warn:'l-warn',err:'l-err',sys:'l-sys',title:'l-title'};
function renderLog(){
const el = $('#log');
const auto = el.scrollHeight - el.scrollTop - el.clientHeight < 60;
el.innerHTML = state.logs.map(l=>`<div class="${lvlClass[l.lvl]||'l-info'}">${l.t} ${l.msg}</div>`).join('');
if(auto) el.scrollTop = el.scrollHeight;
}
function stBadge(st){
if(st==='success') return '<span class="status st-success"><span class="dot"></span>Выставлен</span>';
if(st==='error') return '<span class="status st-error"><span class="dot"></span>Ошибка</span>';
return '<span class="status st-pending"><span class="dot"></span>В очереди</span>';
}
function renderJobs(){
const tb = $('#tbody');
const empty = $('#empty');
const sum = state.summary || {found:0, listed:0, total:0};
$('#stFound').textContent = sum.found;
$('#stListed').textContent = sum.listed;
const tot = (sum.total||0);
$('#stTotal').textContent = (Number.isInteger(tot) ? tot : tot.toFixed(2)) + ' ₽';
$('#cnt').textContent = state.jobs.length + ' предметов';
if(!state.jobs.length){ tb.innerHTML=''; empty.style.display='block'; return; }
empty.style.display='none';
tb.innerHTML = state.jobs.map(j=>`
<tr>
<td><img src="${j.img||''}" onerror="this.style.visibility='hidden'"></td>
<td><b>${j.name||''}</b></td>
<td>${j.productId}</td>
<td>${j.id}</td>
<td>${j.base!=null ? j.base : '—'}</td>
<td>${j.price!=null ? j.price : '—'}</td>
<td>${stBadge(j.status)}</td>
</tr>`).join('');
}
async function poll(){
try{
const r = await fetch('/api/state');
const s = await r.json();
state = s;
renderLog(); renderJobs();
const b = $('#badge');
if(s.running){ b.className='badge on'; b.textContent='Работает…'; }
else { b.className='badge'; b.textContent='Ожидание'; }
}catch(e){}
}
setInterval(poll, 500); poll();
$('#btnExample').onclick = ()=>{
$('#curl').value = `curl --url 'https://mm2-market.apineural.com/api/inventory/items/fs/139373/c/rub' \\
-H 'accept: */*' \\
-H 'accept-language: ru,en;q=0.9,en-GB;q=0.8,en-US;q=0.7' \\
-H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2YTgxNzI0ZDNhOGJhYTc1Y2RiYjhiZjUifSwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc4ODg4NTMwNCwiZXhwIjoxNzg4ODg2MjA0fQ.0QQ9mk6_nYL7NC11XLfg_4b6u36Uk6yutd0z828UD2Y' \\
-H 'content-type: application/json' \\
-H 'origin: https://starpets.gg' \\
-H 'referer: https://starpets.gg/' \\
-H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/152.0.0.0'`;
};
$('#btnClear').onclick = ()=>{ $('#curl').value=''; $('#meta').style.display='none'; };
$('#btnStart').onclick = async ()=>{
const curl = $('#curl').value.trim();
if(!curl){ alert('Вставь cURL запрос'); return; }
// парсинг для предпросмотра
const p = await (await fetch('/api/parse',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({curl})})).json();
if(p.url){
$('#mAcc').textContent = p.account_id || '—';
$('#mCur').textContent = p.currency || '—';
$('#mHdr').textContent = p.headers || '—';
$('#mTok').textContent = p.token;
$('#meta').style.display='flex';
}
const res = await (await fetch('/api/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({curl})})).json();
if(!res.ok) alert(res.error||'Ошибка запуска');
};
</script>
<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a37fbbacda478fe2',t:'MTc4ODg4OTE3MQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>