commit d4496a2eda35cdafcd03b451289ef1ff9929d031 Author: Strix Date: Wed Apr 9 23:59:52 2025 +0200 init: initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a1b9cbb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.env +__pycache__/ \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..01dc3d9 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +EXPORTER_PORT=8000 +SCRAPE_INTERVAL=30 + +# twitch +TWITCH_CLIENT_ID= +TWITCH_CLIENT_SECRET= +TWITCH_CHANNELS=ikiznear \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1541da5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..05d15f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11-slim + +WORKDIR /app +COPY . /app + +RUN pip install -r requirements.txt + +EXPOSE 8000 +CMD ["python", "main.py"] diff --git a/main.py b/main.py new file mode 100644 index 0000000..6b5e21e --- /dev/null +++ b/main.py @@ -0,0 +1,37 @@ +import os +import importlib.util +import time +from prometheus_client import start_http_server +from dotenv import load_dotenv + +load_dotenv() + +def load_stat_modules(path='stats'): + modules = [] + for filename in os.listdir(path): + if filename.endswith(".py") and filename != "__init__.py": + module_name = filename[:-3] + filepath = os.path.join(path, filename) + + spec = importlib.util.spec_from_file_location(module_name, filepath) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if hasattr(module, 'update'): + modules.append(module) + return modules + +if __name__ == '__main__': + port = int(os.getenv("EXPORTER_PORT", "8000")) + print(f"Starting exporter on port {port}...") + start_http_server(port) + + modules = load_stat_modules() + + while True: + for mod in modules: + try: + mod.update() + except Exception as e: + print(f"Error in {mod.__name__}: {e}") + time.sleep(int(os.getenv("SCRAPE_INTERVAL", "30"))) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d00d989 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +prometheus_client +requests +python-dotenv \ No newline at end of file diff --git a/stats/__init__.py b/stats/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stats/twitch_stats.py b/stats/twitch_stats.py new file mode 100644 index 0000000..cff12c8 --- /dev/null +++ b/stats/twitch_stats.py @@ -0,0 +1,60 @@ +from prometheus_client import Gauge +import requests +import os +import time + +# Load env vars +TWITCH_CLIENT_ID = os.getenv("TWITCH_CLIENT_ID") +TWITCH_CLIENT_SECRET = os.getenv("TWITCH_CLIENT_SECRET") +CHANNELS = os.getenv("TWITCH_CHANNELS", "").split(",") + +# Metric definition +live_status = Gauge('twitch_stream_live', 'Is the stream live?', ['channel']) + +_token_cache = { + "access_token": None, + "expires_at": 0 +} + +print("[twitch] Client ID:", TWITCH_CLIENT_ID) +print("[twitch] Client Secret:", TWITCH_CLIENT_SECRET) +print("[twitch] Channels:", CHANNELS) + +def get_token(): + if time.time() < _token_cache["expires_at"] - 60: + return _token_cache["access_token"] + + response = requests.post("https://id.twitch.tv/oauth2/token", data={ + "client_id": TWITCH_CLIENT_ID, + "client_secret": TWITCH_CLIENT_SECRET, + "grant_type": "client_credentials" + }) + + # Print the response for debugging + print(f"[twitch] Response: {response.json()}") + + data = response.json() + + # Check for errors in the response + if "access_token" not in data: + raise ValueError(f"Error getting token: {data}") + + _token_cache["access_token"] = data["access_token"] + _token_cache["expires_at"] = time.time() + data["expires_in"] + return _token_cache["access_token"] + +headers = { + 'Client-ID': TWITCH_CLIENT_ID, + 'Authorization': f'Bearer {get_token()}' +} + +def update(): + for channel in CHANNELS: + url = f'https://api.twitch.tv/helix/streams?user_login={channel}' + try: + response = requests.get(url, headers=headers) + data = response.json() + is_live = int(bool(data.get('data'))) + live_status.labels(channel=channel).set(is_live) + except Exception as e: + print(f"[twitch] Error checking {channel}: {e}")