requests ve plyer kurulumu yapmanız gerekiyor.
not: yaptım derken yapay zekaya yaptırdım. hakkını vereyim.

import json
import queue
import threading
import time
import tkinter as tk
from datetime import date
from pathlib import Path
from tkinter import messagebox, ttk
import requests
try:
from plyer import notification
except Exception: # plyer yoksa uygulama yine açılır
notification = None
API_BASE = "https://v3.football.api-sports.io"
CONFIG_FILE = Path(__file__).with_name("config.json")
LIVE_STATUSES = {"1H", "HT", "2H", "ET", "BT", "P", "LIVE", "INT"}
# ----------------------------------------------------------------- veri katmanı
class ApiFootball:
def __init__(self, api_key: str):
self.headers = {"x-apisports-key": api_key}
def _get(self, path: str, params: dict):
r = requests.get(f"{API_BASE}{path}", headers=self.headers,
params=params, timeout=15)
r.raise_for_status()
data = r.json()
if data.get("errors"):
raise RuntimeError(str(data["errors"]))
return data.get("response", [])
def live(self):
return [self._parse(x) for x in self._get("/fixtures", {"live": "all"})]
def today(self):
return [self._parse(x) for x in
self._get("/fixtures", {"date": date.today().isoformat(),
"timezone": "Europe/Istanbul"})]
@staticmethod
def _parse(x: dict) -> dict:
fx, teams, goals = x["fixture"], x["teams"], x["goals"]
return {
"id": fx["id"],
"league": x["league"]["name"],
"home": teams["home"]["name"],
"away": teams["away"]["name"],
"gh": goals["home"] or 0,
"ga": goals["away"] or 0,
"status": fx["status"]["short"],
"minute": fx["status"].get("elapsed"),
"time": fx["date"][11:16],
}
# ------------------------------------------------------------------- uygulama
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Maç Takip")
self.geometry("820x560")
self.cfg = self._load_cfg()
self.tracked: dict[int, dict] = {} # id -> son bilinen maç verisi
self.matches: dict[str, dict] = {} # treeview iid -> maç
self.events = queue.Queue()
self.stop_flag = threading.Event()
self.worker = None
self._build_ui()
self.after(300, self._drain_events)
self.protocol("WM_DELETE_WINDOW", self._on_close)
# ---- ayarlar
def _load_cfg(self):
try:
return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
except Exception:
return {"api_key": "", "interval": 90}
def _save_cfg(self):
CONFIG_FILE.write_text(json.dumps(self.cfg), encoding="utf-8")
# ---- arayüz
def _build_ui(self):
top = ttk.Frame(self, padding=8)
top.pack(fill="x")
ttk.Label(top, text="API anahtarı:").pack(side="left")
self.key_var = tk.StringVar(value=self.cfg.get("api_key", ""))
ttk.Entry(top, textvariable=self.key_var, width=34, show="*").pack(side="left", padx=4)
ttk.Label(top, text="Aralık (sn):").pack(side="left", padx=(10, 0))
self.interval_var = tk.IntVar(value=self.cfg.get("interval", 90))
ttk.Spinbox(top, from_=15, to=600, width=5, textvariable=self.interval_var).pack(side="left", padx=4)
btns = ttk.Frame(self, padding=(8, 0))
btns.pack(fill="x")
ttk.Button(btns, text="Canlı maçlar", command=lambda: self._fetch("live")).pack(side="left")
ttk.Button(btns, text="Bugünün maçları", command=lambda: self._fetch("today")).pack(side="left", padx=4)
ttk.Button(btns, text="Seçilenleri takibe ekle", command=self._add_selected).pack(side="left", padx=12)
ttk.Button(btns, text="Seçilenleri çıkar", command=self._remove_selected).pack(side="left")
self.run_btn = ttk.Button(btns, text="▶ Takibi başlat", command=self._toggle)
self.run_btn.pack(side="right")
cols = ("tracked", "league", "match", "score", "status")
self.tree = ttk.Treeview(self, columns=cols, show="headings", selectmode="extended")
for c, t, w in (("tracked", "Takip", 50), ("league", "Lig", 170),
("match", "Maç", 280), ("score", "Skor", 70), ("status", "Durum", 100)):
self.tree.heading(c, text=t)
self.tree.column(c, width=w, anchor="center" if c != "match" else "w")
self.tree.pack(fill="both", expand=True, padx=8, pady=8)
self.log = tk.Text(self, height=7, state="disabled")
self.log.pack(fill="x", padx=8, pady=(0, 8))
def _log(self, msg):
self.log.configure(state="normal")
self.log.insert("end", f"[{time.strftime('%H:%M:%S')}] {msg}\n")
self.log.see("end")
self.log.configure(state="disabled")
# ---- maç listesi
def _client(self):
key = self.key_var.get().strip()
if not key:
messagebox.showwarning("Eksik", "Lütfen API anahtarını girin.")
return None
self.cfg.update(api_key=key, interval=int(self.interval_var.get()))
self._save_cfg()
return ApiFootball(key)
def _fetch(self, kind):
client = self._client()
if not client:
return
def job():
try:
data = client.live() if kind == "live" else client.today()
self.events.put(("list", data))
except Exception as e:
self.events.put(("log", f"Hata: {e}"))
threading.Thread(target=job, daemon=True).start()
def _fill_tree(self, matches):
self.tree.delete(*self.tree.get_children())
self.matches.clear()
for m in matches:
iid = str(m["id"])
self.matches[iid] = m
self.tree.insert("", "end", iid=iid, values=self._row(m))
self._log(f"{len(matches)} maç listelendi.")
def _row(self, m):
mark = "✔" if m["id"] in self.tracked else ""
status = f"{m['minute']}'" if m["status"] in LIVE_STATUSES and m["minute"] else (
m["time"] if m["status"] == "NS" else m["status"])
return (mark, m["league"], f"{m['home']} - {m['away']}", f"{m['gh']} - {m['ga']}", status)
def _refresh_row(self, m):
iid = str(m["id"])
if self.tree.exists(iid):
self.tree.item(iid, values=self._row(m))
def _add_selected(self):
for iid in self.tree.selection():
m = self.matches[iid]
self.tracked[m["id"]] = m
self._refresh_row(m)
self._log(f"Takipteki maç sayısı: {len(self.tracked)}")
def _remove_selected(self):
for iid in self.tree.selection():
m = self.matches[iid]
self.tracked.pop(m["id"], None)
self._refresh_row(m)
self._log(f"Takipteki maç sayısı: {len(self.tracked)}")
# ---- takip döngüsü
def _toggle(self):
if self.worker and self.worker.is_alive():
self.stop_flag.set()
self.run_btn.configure(text="▶ Takibi başlat")
self._log("Takip durduruldu.")
return
client = self._client()
if not client:
return
if not self.tracked:
messagebox.showinfo("Bilgi", "Önce takip edilecek maçları seçip ekleyin.")
return
self.stop_flag.clear()
interval = max(15, int(self.interval_var.get()))
self.worker = threading.Thread(target=self._poll, args=(client, interval), daemon=True)
self.worker.start()
self.run_btn.configure(text="■ Takibi durdur")
self._log(f"Takip başladı ({interval} sn aralıkla).")
def _poll(self, client, interval):
while not self.stop_flag.is_set():
try:
live = {m["id"]: m for m in client.live()}
for mid, old in list(self.tracked.items()):
new = live.get(mid)
if not new:
continue
self.events.put(("update", old, new))
except Exception as e:
self.events.put(("log", f"Veri alınamadı: {e}"))
self.stop_flag.wait(interval)
# ---- ana thread'de olay işleme
def _drain_events(self):
try:
while True:
ev = self.events.get_nowait()
if ev[0] == "list":
self._fill_tree(ev[1])
elif ev[0] == "log":
self._log(ev[1])
elif ev[0] == "update":
self._handle_update(ev[1], ev[2])
except queue.Empty:
pass
self.after(300, self._drain_events)
def _handle_update(self, old, new):
if new["id"] not in self.tracked:
return
self.tracked[new["id"]] = new
self._refresh_row(new)
title = f"{new['home']} {new['gh']} - {new['ga']} {new['away']}"
if new["gh"] > old["gh"]:
self._notify(f"⚽ GOL! {new['home']}", f"{title} ({new['minute']}')")
elif new["ga"] > old["ga"]:
self._notify(f"⚽ GOL! {new['away']}", f"{title} ({new['minute']}')")
elif new["gh"] < old["gh"] or new["ga"] < old["ga"]:
self._notify("Gol iptal edildi (VAR)", title)
def _notify(self, title, msg):
self._log(f"{title} | {msg}")
if notification:
try:
notification.notify(title=title, message=msg, app_name="Maç Takip", timeout=10)
except Exception as e:
self._log(f"Bildirim gönderilemedi: {e}")
else:
self._log("plyer kurulu değil: pip install plyer")
def _on_close(self):
self.stop_flag.set()
self.destroy()
if __name__ == "__main__":
App().mainloop()