initial commit

This commit is contained in:
Radek Davidek 2025-11-01 15:51:37 +01:00
commit abcd9ff04d
6 changed files with 405 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
servers
target
bin
.settings
.metadata
.classpath
.project

4
Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM openjdk:11
COPY target/*.jar /tmp
WORKDIR /tmp
CMD ["java", "-jar", "cz-basketball-scraper-1.0-SNAPSHOT.jar"]

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.kamma.czb</groupId>
<artifactId>cz-basketball-scraper</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>cz.kamma.czb.BasketballServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
</project>

52
pom.xml Normal file
View File

@ -0,0 +1,52 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.kamma.czb</groupId>
<artifactId>cz-basketball-scraper</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- Jackson pro JSON parsing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven compiler plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<!-- Maven shade plugin pro spustitelný JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>cz.kamma.czb.BasketballServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,110 @@
package cz.kamma.czb;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class BasketballServer {
private static JsonNode cachedData;
private static final Object lock = new Object();
public static void main(String[] args) throws Exception {
fetchDataForDate("Sat Nov 01 2025 01:00:00 GMT+0100 (Central European Standard Time)"); // inicialní fetch
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> fetchDataForDate("Sat Nov 01 2025 01:00:00 GMT+0100 (Central European Standard Time)"),
5, 5, TimeUnit.MINUTES);
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/api/matches", new ApiHandler());
server.createContext("/", new WebHandler("src/main/resources/index.html"));
server.setExecutor(Executors.newFixedThreadPool(4));
server.start();
System.out.println("Server running at http://localhost:8000/");
}
// --- fetch dat pro určité datum ---
private static JsonNode fetchDataForDate(String dateParam) {
try {
String urlString = "https://cz.basketball/?do=customHomepage-getLeagues&date="
+ URLEncoder.encode(dateParam, StandardCharsets.UTF_8) + "&categories=";
URL url = new URL(urlString);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Cookie", "cc_cookie={\"level\":[\"necessary\",\"analytics\",\"targeting\",\"social\"],\"revision\":0,\"data\":null,\"rfc_cookie\":false}; _ga_YVJ6WB27SJ=GS2.2.s1746868725$o1$g1$t1746868780$j0$l0$h0; _ga_QHKEFEZRL5=GS2.1.s1761514243$o7$g1$t1761514385$j60$l0$h0; _nss=1; PHPSESSID=0dmq2ps6c0dv5bhdg5ukjjl6e7; _gid=GA1.2.1121240707.1762001487; _gat_UA-12082319-2=1; _ga=GA1.2.1277704385.1746363814; _ga_JYB7G0MLMT=GS2.1.s1762001486$o30$g1$t1762001571$j60$l0$h0"); // nahraď platnou cookie
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8));
StringBuilder content = new StringBuilder();
String line;
while ((line = in.readLine()) != null) content.append(line);
in.close();
con.disconnect();
ObjectMapper mapper = new ObjectMapper();
JsonNode data = mapper.readTree(content.toString());
synchronized (lock) { cachedData = data; }
System.out.println("Data fetched successfully for date: " + dateParam);
return data;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// --- REST API handler ---
static class ApiHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getQuery();
String dateParam = "Sat Nov 01 2025 01:00:00 GMT+0100 (Central European Standard Time)";
if (query != null && query.contains("date=")) {
dateParam = java.net.URLDecoder.decode(query.split("date=")[1], StandardCharsets.UTF_8);
}
JsonNode data = fetchDataForDate(dateParam);
String response = data != null ? data.toString() : "{}";
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
exchange.sendResponseHeaders(200, response.getBytes(StandardCharsets.UTF_8).length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes(StandardCharsets.UTF_8));
os.close();
}
}
// --- Web UI handler ---
static class WebHandler implements HttpHandler {
private final String htmlFile;
public WebHandler(String htmlFile) { this.htmlFile = htmlFile; }
@Override
public void handle(HttpExchange exchange) throws IOException {
File file = new File(htmlFile);
if (!file.exists()) { exchange.sendResponseHeaders(404, -1); return; }
byte[] bytes = java.nio.file.Files.readAllBytes(file.toPath());
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");
exchange.sendResponseHeaders(200, bytes.length);
OutputStream os = exchange.getResponseBody();
os.write(bytes);
os.close();
}
}
}

View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html>
<head>
<title>CZ.Basketball - zápasy</title>
<link rel="icon" type="image/png" href="https://cz.basketball/img/logo-icon.png">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto', Arial, sans-serif;
background: #f4f7f9;
color: #333;
margin: 20px;
}
h1 { color: #2c3e50; }
label { margin-right: 8px; font-weight: 500; }
input, select, button {
padding: 6px 10px;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
button {
background-color: #3498db;
color: white;
cursor: pointer;
transition: background 0.3s;
}
button:hover { background-color: #2980b9; }
table {
border-collapse: collapse;
width: 100%;
background-color: white;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
th, td {
border-bottom: 1px solid #eee;
padding: 10px;
text-align: left;
}
th {
background-color: #3498db;
color: white;
font-weight: 500;
}
tr.highlight {
background-color: #ffeb3b !important;
font-weight: bold;
}
tr.nymburk {
background-color: #90caf9;
}
tr:nth-child(even) { background-color: #f9f9f9; }
a { color: #2980b9; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>CZ.Basketball - zápasy</h1>
<label for="date">Vyber datum:</label>
<input type="date" id="date" onchange="loadMatches()"/>
<button onclick="loadMatches()">Načíst zápasy</button>
<label for="league">Vyber ligu:</label>
<select id="league" onchange="filterMatches()">
<option value="">Všechny ligy</option>
</select>
<table id="matches">
<thead>
<tr>
<th>Čas</th>
<th>Liga</th>
<th>Domácí</th>
<th>Hosté</th>
<th>Preview</th>
<th>Live</th>
<th>TV</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
// --- Nastavit kalendář na aktuální den při načtení stránky ---
window.addEventListener('DOMContentLoaded', () => {
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
document.getElementById('date').value = `${yyyy}-${mm}-${dd}`;
loadMatches(); // načíst zápasy pro dnešní datum
});
// --- Načíst zápasy z API ---
async function loadMatches() {
const dateInput = document.getElementById('date').value;
let url = '/api/matches';
if (dateInput) url += '?date=' + encodeURIComponent(dateInput);
const res = await fetch(url);
const data = await res.json();
const select = document.getElementById('league');
const tbody = document.querySelector('#matches tbody');
tbody.innerHTML = '';
select.innerHTML = '<option value="">Všechny ligy</option>';
if (!data || !data.leagues) return;
let closestTimeDiff = Infinity;
let closestRow = null;
const now = new Date();
data.leagues.forEach(league => {
// --- kontrola, zda liga má alespoň jeden zápas s TV odkazem ---
const hasTV = league.matches.some(m => m.links.tvcom && m.links.tvcom.url);
if (!hasTV) return; // přeskočit ligu bez TV zápasů
// přidat do select boxu
const option = document.createElement('option');
option.value = league.name;
option.textContent = league.name;
select.appendChild(option);
league.matches.forEach(match => {
if (!match.links.live && !match.links.tvcom) return; // přeskočit zápasy bez Live a TV
const tr = document.createElement('tr');
tr.dataset.league = league.name;
// doplnit https://cz.basketball pokud preview.url nezačíná http/https
let previewUrl = '';
if (match.links.preview && match.links.preview.url) {
previewUrl = match.links.preview.url;
if (!previewUrl.startsWith('http://') && !previewUrl.startsWith('https://')) {
previewUrl = 'https://cz.basketball' + previewUrl;
}
}
tr.innerHTML = `
<td>${match.status}</td>
<td>${league.name}</td>
<td>${match.home.name}</td>
<td>${match.away.name}</td>
<td>${previewUrl ? '<a href="'+previewUrl+'">Preview</a>' : ''}</td>
<td>${match.links.live && match.links.live.url ? '<a href="'+match.links.live.url+'" target="_blank">Live</a>' : ''}</td>
<td>${match.links.tvcom && match.links.tvcom.url ? '<a href="'+match.links.tvcom.url+'" target="_blank">TV</a>' : ''}</td>
`;
// zvýraznit zápasy s Nymburkem
if ((match.home.name && match.home.name.includes('Nymburk')) ||
(match.away.name && match.away.name.includes('Nymburk'))) {
tr.classList.add('nymburk');
}
// zvýraznit nejbližší zápas (Live zápasy)
if (match.links.live && match.links.live.url) {
const [hours, minutes] = match.status.split(':').map(Number);
if (!isNaN(hours) && !isNaN(minutes)) {
const matchDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes);
const diff = matchDate - now;
if (diff >= 0 && diff < closestTimeDiff) {
closestTimeDiff = diff;
closestRow = tr;
}
}
}
tbody.appendChild(tr);
});
});
if (closestRow) closestRow.classList.add('highlight');
}
// --- Filtrovat podle ligy ---
function filterMatches() {
const league = document.getElementById('league').value;
document.querySelectorAll('#matches tbody tr').forEach(row => {
row.style.display = (!league || row.dataset.league === league) ? '' : 'none';
});
}
</script>
</body>
</html>