commit 7f971b04de105ae9fa483c45f747264d00cf6149 Author: Radek Davidek Date: Fri Oct 31 23:51:47 2025 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebd831b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +servers +target +bin +.settings +.metadata +.classpath +.project + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..363aa6d --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + + cz.tvcom + tvcom-scraper + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + + org.jsoup + jsoup + 1.17.2 + + + + com.fasterxml.jackson.core + jackson-databind + 2.18.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + cz.kamma.tvcom.HttpServerApp + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + false + + + cz.kamma.tvcom.HttpServerApp + + + + + + + + + diff --git a/src/main/java/cz/kamma/tvcom/HttpServerApp.java b/src/main/java/cz/kamma/tvcom/HttpServerApp.java new file mode 100644 index 0000000..e1915f2 --- /dev/null +++ b/src/main/java/cz/kamma/tvcom/HttpServerApp.java @@ -0,0 +1,153 @@ +package cz.kamma.tvcom; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; + +import java.io.*; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class HttpServerApp { + public static void main(String[] args) throws Exception { + TransmissionService service = new TransmissionService(); + service.loadNextDays(10); + + HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); + ObjectMapper mapper = new ObjectMapper(); + + // /transmissions -> all data + server.createContext("/transmissions", exchange -> { + try { + setCors(exchange); + List all = service.getAll(); + respondJson(exchange, mapper.writeValueAsString(all)); + } catch (Exception e) { + e.printStackTrace(); + sendError(exchange, 500, e.getMessage()); + } + }); + + // /search?q=... + server.createContext("/search", exchange -> { + try { + setCors(exchange); + URI uri = exchange.getRequestURI(); + String query = null; + if (uri.getQuery() != null) { + String fullq = uri.getQuery(); + for (String part : fullq.split("&")) { + if (part.startsWith("q=")) { + query = java.net.URLDecoder.decode(part.substring(2), StandardCharsets.UTF_8.name()); + } + } + } + List results = service.search(query); + respondJson(exchange, mapper.writeValueAsString(results)); + } catch (Exception e) { + e.printStackTrace(); + sendError(exchange, 500, e.getMessage()); + } + }); + + // ✅ NEW: /refresh endpoint + server.createContext("/refresh", exchange -> { + try { + setCors(exchange); + if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) { + service.reloadDataAsync(); + respondJson(exchange, "{\"status\":\"ok\",\"message\":\"Data se obnovují na pozadí.\"}"); + } else { + exchange.sendResponseHeaders(405, -1); + } + } catch (Exception e) { + sendError(exchange, 500, e.getMessage()); + } + }); + + // serve /index.html + server.createContext("/", exchange -> { + try { + setCors(exchange); + String path = exchange.getRequestURI().getPath(); + if ("/".equals(path) || path.isEmpty() || path.equals("/index.html")) { + String html = readResource("/index.html"); + if (html == null) { + sendError(exchange, 404, "index.html not found in resources"); + return; + } + exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8"); + byte[] bytes = html.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } else { + sendError(exchange, 404, "Not found"); + } + } catch (Exception e) { + e.printStackTrace(); + sendError(exchange, 500, e.getMessage()); + } + }); + + server.start(); + System.out.println("🚀 HTTP server běží na http://localhost:8080"); + System.out.println(" ➜ /transmissions (všechny přenosy JSON)"); + System.out.println(" ➜ /search?q=Brno (vyhledávání JSON)"); + System.out.println(" ➜ /refresh (spustí opětovné načtení dat)"); + System.out.println(" ➜ / (web UI)"); + } + + private static void setCors(HttpExchange exchange) { + Headers h = exchange.getResponseHeaders(); + h.set("Access-Control-Allow-Origin", "*"); + h.set("Access-Control-Allow-Methods", "GET, OPTIONS"); + h.set("Access-Control-Allow-Headers", "Content-Type"); + if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) { + try { + exchange.sendResponseHeaders(204, -1); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void respondJson(HttpExchange exchange, String json) throws IOException { + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } + + private static void sendError(HttpExchange exchange, int code, String message) throws IOException { + exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); + byte[] bytes = message.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(code, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } + + private static String readResource(String resourcePath) { + try (InputStream is = HttpServerApp.class.getResourceAsStream(resourcePath)) { + if (is == null) return null; + try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + sb.append(line).append("\n"); + } + return sb.toString(); + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/cz/kamma/tvcom/Searcher.java b/src/main/java/cz/kamma/tvcom/Searcher.java new file mode 100644 index 0000000..02d483d --- /dev/null +++ b/src/main/java/cz/kamma/tvcom/Searcher.java @@ -0,0 +1,29 @@ +package cz.kamma.tvcom; + +import java.sql.*; + +public class Searcher { + private static final String URL = "jdbc:mysql://server01:3306/tvcom?useSSL=false&characterEncoding=UTF-8"; + private static final String USER = "tvcom"; + private static final String PASS = "Passw0rd"; + + public static void main(String[] args) throws Exception { + String search = args.length > 0 ? args[0] : "Nymburk"; + + try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) { + String sql = "SELECT * FROM transmissions WHERE MATCH(title, sport, league) AGAINST (? IN NATURAL LANGUAGE MODE)"; + PreparedStatement ps = conn.prepareStatement(sql); + ps.setString(1, search); + ResultSet rs = ps.executeQuery(); + + while (rs.next()) { + System.out.printf("%s | %s | %s | %s | %s%n", + rs.getString("date"), + rs.getString("time"), + rs.getString("title"), + rs.getString("sport"), + rs.getString("league")); + } + } + } +} diff --git a/src/main/java/cz/kamma/tvcom/Transmission.java b/src/main/java/cz/kamma/tvcom/Transmission.java new file mode 100644 index 0000000..2a333c1 --- /dev/null +++ b/src/main/java/cz/kamma/tvcom/Transmission.java @@ -0,0 +1,24 @@ +package cz.kamma.tvcom; + +public class Transmission { + public String title; + public String sport; + public String league; + public String leaguePart; + public String link; + public String date; + public String time; + public String image; + + public Transmission(String title, String sport, String league, String leaguePart, + String link, String date, String time, String image) { + this.title = title; + this.sport = sport; + this.league = league; + this.leaguePart = leaguePart; + this.link = link; + this.date = date; + this.time = time; + this.image = image; + } +} diff --git a/src/main/java/cz/kamma/tvcom/TransmissionService.java b/src/main/java/cz/kamma/tvcom/TransmissionService.java new file mode 100644 index 0000000..e27cb37 --- /dev/null +++ b/src/main/java/cz/kamma/tvcom/TransmissionService.java @@ -0,0 +1,92 @@ +package cz.kamma.tvcom; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +public class TransmissionService { + private final List transmissions = Collections.synchronizedList(new ArrayList<>()); + + public void loadNextDays(int daysToLoad) throws InterruptedException { + LocalDate today = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + ExecutorService executor = Executors.newFixedThreadPool(5); + + for (int i = 0; i < daysToLoad; i++) { + LocalDate targetDate = today.plusDays(i); + String dateStr = targetDate.format(formatter); + executor.submit(() -> loadDay(dateStr)); + } + + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.MINUTES); + System.out.println("✅ Načteno celkem přenosů: " + transmissions.size()); + } + + private void loadDay(String dateStr) { + try { + String url = "https://www.tvcom.cz/Den/?d=" + dateStr; + System.out.println("⏳ Načítám " + dateStr); + Document doc = Jsoup.connect(url).timeout(15000).get(); + + Elements items = doc.select("section.video.tile a.item"); + for (Element item : items) { + String href = item.attr("href"); + String link = href.startsWith("http") ? href : "https://www.tvcom.cz" + href; + String title = item.select(".title").text(); + String sport = item.select(".sport").text(); + String league = item.select(".league").text(); + String leaguePart = item.select(".leaguePart").text(); + String time = item.select(".starting .time").text(); + String image = item.select(".image img").attr("src"); + + transmissions.add(new Transmission(title, sport, league, leaguePart, link, dateStr, time, image)); + } + + System.out.println("✅ Den " + dateStr + ": " + items.size() + " přenosů"); + } catch (Exception e) { + System.err.println("⚠️ Chyba při načítání dne " + dateStr + ": " + e.getMessage()); + } + } + + public List getAll() { + synchronized (transmissions) { + return new ArrayList<>(transmissions); + } + } + + public List search(String query) { + if (query == null || query.isBlank()) return getAll(); + String q = query.toLowerCase(); + synchronized (transmissions) { + return transmissions.stream() + .filter(t -> ( (t.title == null ? "" : t.title) + " " + + (t.sport == null ? "" : t.sport) + " " + + (t.league == null ? "" : t.league) ) + .toLowerCase().contains(q)) + .collect(Collectors.toList()); + } + } + + public void reloadDataAsync() { + new Thread(() -> { + synchronized (transmissions) { + transmissions.clear(); + } + System.out.println("🔄 Obnovuji data z webu TVCOM..."); + try { + loadNextDays(10); + System.out.println("✅ Data znovu načtena, přenosů celkem: " + transmissions.size()); + } catch (Exception e) { + System.err.println("❌ Chyba při opětovném načítání: " + e.getMessage()); + } + }).start(); + } +} diff --git a/src/main/resources/index.html b/src/main/resources/index.html new file mode 100644 index 0000000..0b5356f --- /dev/null +++ b/src/main/resources/index.html @@ -0,0 +1,257 @@ + + + + + + TVCOM — Přenosy (lokální UI) + + + + + + +
+

TVCOM — Přenosy (následujících 10 dní)

+ +
+ + + + + + +
+ +
+
+ +
+ + + + + + + + + + + + + +
ObrDatumČasNázev / TýmySportLigaOdkaz
+
+ + +
+ + + +