chrome / app.py
rafael1994s's picture
Update app.py
a68cdd9 verified
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, StreamingResponse
import asyncio
from playwright.async_api import async_playwright
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def index():
return """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FlashScore Monitor</title>
<style>
body { background:#0d0d0d; color:#eee; font-family:monospace; padding:10px; margin:0; }
h2 { color:#ed1c24; }
.btn { padding:10px 25px; font-size:15px; border:none; cursor:pointer; border-radius:4px; margin-right:8px; }
.btn-start { background:#ed1c24; color:#fff; }
.btn-stop { background:#333; color:#fff; }
#status { margin:10px 0; color:#888; font-size:12px; }
table { width:100%; border-collapse:collapse; margin-top:10px; }
th { background:#1a1a1a; color:#ed1c24; padding:8px; text-align:left; font-size:11px; text-transform:uppercase; }
</style>
</head>
<body>
<h2>🎾 FlashScore Tennis Monitor</h2>
<button class="btn btn-start" onclick="startStream()">▶ Старт</button>
<button class="btn btn-stop" onclick="stopStream()">⏹ Стоп</button>
<div id="status">Нажмите Старт</div>
<table>
<thead>
<tr>
<th style="width:65px">Время</th>
<th style="width:85px">Статус</th>
<th style="width:210px">Игроки</th>
<th>Счёт</th>
</tr>
</thead>
<tbody id="log-body"></tbody>
</table>
<script>
let controller = null;
async function startStream() {
if (controller) controller.abort();
controller = new AbortController();
document.getElementById('status').textContent = '⏳ Подключаюсь...';
document.getElementById('log-body').innerHTML = '';
try {
const response = await fetch('/stream', { signal: controller.signal });
const reader = response.body.getReader();
const decoder = new TextDecoder();
document.getElementById('status').textContent = '🟢 Мониторинг идёт...';
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith('data: ')) {
const html = line.slice(6);
if (html) {
const tbody = document.getElementById('log-body');
tbody.insertAdjacentHTML('afterbegin', html);
if (tbody.children.length > 60) tbody.removeChild(tbody.lastChild);
}
}
}
}
} catch(e) {
if (e.name === 'AbortError') {
document.getElementById('status').textContent = '⏹ Остановлено';
}
}
}
function stopStream() {
if (controller) { controller.abort(); controller = null; }
}
</script>
</body>
</html>
"""
@app.get("/stream")
async def stream():
async def generate():
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
args=["--no-sandbox", "--disable-dev-shm-usage"]
)
page = await browser.new_page()
# Вся логика парсинга — прямо как в Tampermonkey, на JS
await page.add_init_script("""
window.__parsed_rows = [];
const matchStorage = {};
const statusMap = {
'46': '1-й сет', '47': '2-й сет', '48': '3-й сет',
'49': '4-й сет', '50': '5-й сет', '38': 'Тай-брейк',
'13': 'Перерыв', '1': 'Завершен'
};
const dict = {
'WA': 'p1_pts', 'WB': 'p2_pts', 'WC': 'srv',
'BA': 'p1_s1', 'BB': 'p2_s1', 'CA': 'p1_s2', 'CB': 'p2_s2',
'DA': 'p1_s3', 'DB': 'p2_s3', 'AC': 'status_id',
'BD': 'val_d', 'BF': 'val_f'
};
function parseToRow(raw) {
let parts = raw.split('\\u00ac');
let update = {};
let matchId = "";
parts.forEach(part => {
let pair = part.split('\\u00f7');
if (pair.length === 2) {
let key = pair[0].replace(/[^A-Z]/g, '');
if (key === 'AA') matchId = pair[1];
if (dict[key]) update[dict[key]] = pair[1];
}
});
if (!matchId || Object.keys(update).length === 0) return null;
if (!matchStorage[matchId]) {
matchStorage[matchId] = {
p1_pts: '0', p2_pts: '0', srv: '0',
status: null,
p1_s1: '0', p2_s1: '0', p1_s2: '0', p2_s2: '0',
p1_s3: '0', p2_s3: '0',
};
}
const ms = matchStorage[matchId];
if (update.status_id && statusMap[update.status_id]) {
ms.status = statusMap[update.status_id];
} else if (update.val_f === '38' || update.val_d === '38') {
ms.status = 'Тай-брейк';
} else {
if (update.p1_s3 || update.p2_s3) ms.status = '3-й сет';
else if (update.p1_s2 || update.p2_s2) ms.status = '2-й сет';
else if (update.p1_s1 || update.p2_s1) ms.status = '1-й сет';
}
Object.assign(ms, update);
const displayStatus = ms.status || 'НЕ ЗНАЮ';
const t = new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});
const srv1 = ms.srv === '1' ? '🎾 ' : '';
const srv2 = ms.srv === '2' ? '🎾 ' : '';
return `<tr style="border-bottom:1px solid #333;height:42px;background:${update.p1_pts||update.p2_pts?'#1a1a1a':'transparent'}">
<td style="color:#666;width:65px;font-size:10px;text-align:center">${t}</td>
<td style="width:85px;color:${displayStatus==='НЕ ЗНАЮ'?'#555':'#ff4e4e'};font-size:10px;font-weight:bold;text-align:center;text-transform:uppercase">${displayStatus}</td>
<td style="width:210px;padding-left:10px">
<div style="color:${ms.srv==='1'?'#fff':'#999'}">${srv1}${matchId}</div>
</td>
<td style="font-family:monospace;font-size:15px">
<div style="color:#ccc">${ms.p1_s1}|${ms.p1_s2}|${ms.p1_s3} <b style="color:#fff">${ms.p1_pts}</b></div>
<div style="color:#ccc">${ms.p2_s1}|${ms.p2_s2}|${ms.p2_s3} <b style="color:#fff">${ms.p2_pts}</b></div>
</td>
</tr>`;
}
const OriginalWebSocket = window.WebSocket;
window.WebSocket = function(url, protocols) {
const socket = new OriginalWebSocket(url, protocols);
if (url.includes('fsdatacentre.com')) {
socket.binaryType = 'arraybuffer';
socket.addEventListener('message', async function(event) {
let text;
if (event.data instanceof ArrayBuffer) {
text = new TextDecoder('utf-8').decode(new Uint8Array(event.data));
} else if (event.data instanceof Blob) {
text = await event.data.text();
} else {
text = event.data;
}
const row = parseToRow(text);
if (row) window.__parsed_rows.push(row);
});
}
return socket;
};
window.WebSocket.prototype = OriginalWebSocket.prototype;
""")
await page.goto("https://www.flashscore.com/tennis/", wait_until="networkidle")
while True:
await asyncio.sleep(1)
rows = await page.evaluate("""
() => {
const r = window.__parsed_rows || [];
window.__parsed_rows = [];
return r;
}
""")
for row in rows:
yield f"data: {row}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")