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 """ FlashScore Monitor

🎾 FlashScore Tennis Monitor

Нажмите Старт
Время Статус Игроки Счёт
""" @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 ` ${t} ${displayStatus}
${srv1}${matchId}
${ms.p1_s1}|${ms.p1_s2}|${ms.p1_s3} ${ms.p1_pts}
${ms.p2_s1}|${ms.p2_s2}|${ms.p2_s3} ${ms.p2_pts}
`; } 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")