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")