diff --git a/homelab-ai-bot/context.py b/homelab-ai-bot/context.py index b5eddffd..21fb7920 100644 --- a/homelab-ai-bot/context.py +++ b/homelab-ai-bot/context.py @@ -7,7 +7,7 @@ import re sys.path.insert(0, os.path.dirname(__file__)) from core import config, loki_client, proxmox_client, wordpress_client, prometheus_client -from core import forgejo_client, seafile_client, pbs_client, mail_client +from core import forgejo_client, seafile_client, pbs_client, mail_client, matomo_client def _load_config(): @@ -180,6 +180,20 @@ def _tool_get_wordpress_stats() -> str: return wordpress_client.format_overview(cfg) +def _tool_get_matomo_analytics() -> str: + cfg = _load_config() + if not matomo_client.init(cfg): + return "Matomo nicht konfiguriert (MATOMO_URL/MATOMO_TOKEN fehlt in homelab.conf)" + return matomo_client.format_analytics() + + +def _tool_get_matomo_trend(days: int = 30) -> str: + cfg = _load_config() + if not matomo_client.init(cfg): + return "Matomo nicht konfiguriert (MATOMO_URL/MATOMO_TOKEN fehlt in homelab.conf)" + return matomo_client.format_trend(days=days) + + def _tool_create_issue(title: str, body: str = "") -> str: cfg = _load_config() forgejo_client.init(cfg) @@ -408,6 +422,8 @@ def get_tool_handlers(session_id: str = None) -> dict: "search_mail": lambda query, days=30: _tool_search_mail(query, days=days), "get_todays_mails": lambda: _tool_get_todays_mails(), "get_smart_mail_digest": lambda hours=24: _tool_get_smart_mail_digest(hours=hours), + "get_matomo_analytics": lambda: _tool_get_matomo_analytics(), + "get_matomo_trend": lambda days=30: _tool_get_matomo_trend(days=days), "memory_read": lambda scope="": _tool_memory_read(scope), "memory_suggest": lambda scope, kind, content, memory_type="fact", confidence="high", expires_at=None: _tool_memory_suggest(scope, kind, content, memory_type=memory_type, confidence=confidence, expires_at=expires_at), "session_search": lambda query: _tool_session_search(query), diff --git a/homelab-ai-bot/core/matomo_client.py b/homelab-ai-bot/core/matomo_client.py new file mode 100644 index 00000000..762f0119 --- /dev/null +++ b/homelab-ai-bot/core/matomo_client.py @@ -0,0 +1,163 @@ +"""Matomo Analytics API Client — Besucherstatistiken fuer arakavanews.com.""" + +import requests +from datetime import datetime, timedelta + +MATOMO_URL = "" +MATOMO_TOKEN = "" +MATOMO_SITE_ID = "1" + + +def init(cfg): + global MATOMO_URL, MATOMO_TOKEN, MATOMO_SITE_ID + MATOMO_URL = cfg.raw.get("MATOMO_URL", "") + MATOMO_TOKEN = cfg.raw.get("MATOMO_TOKEN", "") + MATOMO_SITE_ID = cfg.raw.get("MATOMO_SITE_ID", "1") + return bool(MATOMO_URL and MATOMO_TOKEN) + + +def _api(method: str, **params) -> dict | list | None: + try: + p = { + "module": "API", + "method": method, + "idSite": MATOMO_SITE_ID, + "format": "json", + "token_auth": MATOMO_TOKEN, + } + p.update(params) + resp = requests.get(f"{MATOMO_URL}/index.php", params=p, timeout=15) + resp.raise_for_status() + return resp.json() + except Exception as e: + return {"error": str(e)} + + +def get_summary(period: str = "day", date: str = "today") -> dict: + """Besucher-Zusammenfassung (Visits, Unique, Actions, Bounce, Avg Time).""" + return _api("VisitsSummary.get", period=period, date=date) + + +def get_visitor_trend(days: int = 30) -> dict: + """Tageweise Besucherzahlen der letzten N Tage.""" + return _api("VisitsSummary.get", period="day", date=f"last{days}") + + +def get_top_pages(period: str = "day", date: str = "today", limit: int = 10) -> list: + """Meistbesuchte Seiten.""" + result = _api("Actions.getPageUrls", period=period, date=date, + filter_limit=limit, flat=1) + if isinstance(result, dict) and "error" in result: + return [] + return result if isinstance(result, list) else [] + + +def get_referrers(period: str = "day", date: str = "today", limit: int = 10) -> list: + """Woher kommen Besucher (Suchmaschinen, Social, Direkt).""" + result = _api("Referrers.getReferrerType", period=period, date=date, + filter_limit=limit) + if isinstance(result, dict) and "error" in result: + return [] + return result if isinstance(result, list) else [] + + +def get_countries(period: str = "day", date: str = "today", limit: int = 10) -> list: + """Besucher nach Laendern.""" + result = _api("UserCountry.getCountry", period=period, date=date, + filter_limit=limit) + if isinstance(result, dict) and "error" in result: + return [] + return result if isinstance(result, list) else [] + + +def get_devices(period: str = "day", date: str = "today") -> list: + """Besucher nach Geraetetyp (Desktop, Mobile, Tablet).""" + result = _api("DevicesDetection.getType", period=period, date=date) + if isinstance(result, dict) and "error" in result: + return [] + return result if isinstance(result, list) else [] + + +def format_analytics(period: str = "day", date: str = "today") -> str: + """Kompakter Analytics-Report fuer den Hausmeister-Bot.""" + lines = [] + + summary = get_summary(period, date) + if isinstance(summary, dict) and "error" not in summary: + visitors = summary.get("nb_uniq_visitors", 0) + visits = summary.get("nb_visits", 0) + actions = summary.get("nb_actions", 0) + bounce = summary.get("bounce_rate", "?") + avg_time = summary.get("avg_time_on_site", 0) + avg_min = int(avg_time) // 60 + avg_sec = int(avg_time) % 60 + lines.append(f"Besucher: {visitors} unique, {visits} visits, {actions} Seitenaufrufe") + lines.append(f"Bounce Rate: {bounce}, Verweildauer: {avg_min}m {avg_sec}s") + else: + return f"Matomo nicht erreichbar: {summary}" + + trend = get_visitor_trend(14) + if isinstance(trend, dict) and "error" not in trend: + trend_lines = [] + for date_str, data in sorted(trend.items()): + if isinstance(data, dict): + v = data.get("nb_uniq_visitors", 0) + trend_lines.append(f" {date_str}: {v} Besucher") + else: + trend_lines.append(f" {date_str}: 0") + if trend_lines: + lines.append("\nTrend (14 Tage):") + lines.extend(trend_lines) + + pages = get_top_pages(period, "today", 5) + if pages: + lines.append("\nTop Seiten (heute):") + for p in pages[:5]: + label = p.get("label", "?") + hits = p.get("nb_hits", 0) + lines.append(f" {label}: {hits}x") + + referrers = get_referrers(period, "today", 5) + if referrers: + lines.append("\nTraffic-Quellen (heute):") + for r in referrers[:5]: + label = r.get("label", "?") + visits = r.get("nb_visits", 0) + lines.append(f" {label}: {visits} visits") + + countries = get_countries(period, "today", 5) + if countries: + lines.append("\nLaender (heute):") + for c in countries[:5]: + label = c.get("label", "?") + visits = c.get("nb_visits", 0) + lines.append(f" {label}: {visits}") + + return "\n".join(lines) if lines else "Keine Daten verfuegbar." + + +def format_trend(days: int = 30) -> str: + """Besucherentwicklung ueber N Tage — fuer Trend-Fragen.""" + trend = get_visitor_trend(days) + if isinstance(trend, dict) and "error" in trend: + return f"Matomo-Fehler: {trend['error']}" + + lines = [f"Besucherentwicklung (letzte {days} Tage):"] + total = 0 + day_count = 0 + for date_str, data in sorted(trend.items()): + if isinstance(data, dict): + v = data.get("nb_uniq_visitors", 0) + actions = data.get("nb_actions", 0) + lines.append(f" {date_str}: {v} Besucher, {actions} Aufrufe") + total += v + day_count += 1 + else: + lines.append(f" {date_str}: 0") + day_count += 1 + + if day_count > 0: + avg = total / day_count + lines.append(f"\nDurchschnitt: {avg:.0f} Besucher/Tag, Gesamt: {total}") + + return "\n".join(lines) diff --git a/homelab-ai-bot/llm.py b/homelab-ai-bot/llm.py index fe0d0612..e623c3ca 100644 --- a/homelab-ai-bot/llm.py +++ b/homelab-ai-bot/llm.py @@ -264,6 +264,28 @@ TOOLS = [ "parameters": {"type": "object", "properties": {}, "required": []}, }, }, + { + "type": "function", + "function": { + "name": "get_matomo_analytics", + "description": "Matomo Website-Analytik: Besucher heute, Trend, Top-Seiten, Traffic-Quellen, Laender. Fuer alle Fragen zu Besucherzahlen, Zuschauern, Traffic, Seitenaufrufen, Bounce Rate, Herkunftslaendern.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, + { + "type": "function", + "function": { + "name": "get_matomo_trend", + "description": "Besucherentwicklung ueber Zeit: Tageweise Besucherzahlen ueber N Tage. Nutze bei Fragen wie 'wie entwickeln sich die Zuschauer', 'Besuchertrend', 'wachsen wir'.", + "parameters": { + "type": "object", + "properties": { + "days": {"type": "number", "description": "Anzahl Tage zurueckblicken (default: 30)", "default": 30} + }, + "required": [], + }, + }, + }, { "type": "function", "function": { diff --git a/homelab.conf b/homelab.conf index 94070c44..f7808e65 100644 --- a/homelab.conf +++ b/homelab.conf @@ -179,6 +179,9 @@ OPENROUTER_KEY="sk-or-v1-f5b2699f4a4708aff73ea0b8bb2653d0d913d57c56472942e510f82 OPENAI_API_KEY="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" MEMORY_API_TOKEN="Ai8eeQibV6Z1RWc7oNPim4PXB4vILU1nRW2-XgRcX2M" MEMORY_API_URL="http://100.121.192.94:8400" +MATOMO_TOKEN="7d3987d48dcd7fdf9776bd81a4da1778" +MATOMO_URL="http://100.113.244.101" +MATOMO_SITE_ID="1" # --- HOMELAB MCP-SERVER (auf pve-hetzner Host) --- MCP_PATH="/root/homelab-mcp"