initial commit
This commit is contained in:
commit
abcd9ff04d
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
servers
|
||||||
|
target
|
||||||
|
bin
|
||||||
|
.settings
|
||||||
|
.metadata
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
4
Dockerfile
Normal file
4
Dockerfile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM openjdk:11
|
||||||
|
COPY target/*.jar /tmp
|
||||||
|
WORKDIR /tmp
|
||||||
|
CMD ["java", "-jar", "cz-basketball-scraper-1.0-SNAPSHOT.jar"]
|
||||||
37
dependency-reduced-pom.xml
Normal file
37
dependency-reduced-pom.xml
Normal 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
52
pom.xml
Normal 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>
|
||||||
110
src/main/java/cz/kamma/czb/BasketballServer.java
Normal file
110
src/main/java/cz/kamma/czb/BasketballServer.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/main/resources/index.html
Normal file
194
src/main/resources/index.html
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user