From 0e3555e4bfe191b870c97710692d3de95f898070 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:17:38 +0000 Subject: [PATCH 1/2] Initial plan From 7a8dd7a48f6e88eaa2ce8c5975f18203fc183b8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:23:01 +0000 Subject: [PATCH 2/2] feat: split SKILL.md into individual skill files in .github/skills/ Co-authored-by: lipnelz <174376617+lipnelz@users.noreply.github.com> --- .github/skills/authentication.md | 134 +++++++++++++++++++++++ .github/skills/balance-history.md | 93 ++++++++++++++++ .github/skills/crypto-prices.md | 110 +++++++++++++++++++ .github/skills/docker-management.md | 162 ++++++++++++++++++++++++++++ .github/skills/node-monitoring.md | 80 ++++++++++++++ .github/skills/scheduled-reports.md | 109 +++++++++++++++++++ .github/skills/system-monitoring.md | 120 +++++++++++++++++++++ SKILL.md | 15 +++ 8 files changed, 823 insertions(+) create mode 100644 .github/skills/authentication.md create mode 100644 .github/skills/balance-history.md create mode 100644 .github/skills/crypto-prices.md create mode 100644 .github/skills/docker-management.md create mode 100644 .github/skills/node-monitoring.md create mode 100644 .github/skills/scheduled-reports.md create mode 100644 .github/skills/system-monitoring.md create mode 100644 SKILL.md diff --git a/.github/skills/authentication.md b/.github/skills/authentication.md new file mode 100644 index 0000000..8653b1d --- /dev/null +++ b/.github/skills/authentication.md @@ -0,0 +1,134 @@ +# Skill : Authentification des utilisateurs + +## Description + +Ce skill contrôle l'accès à toutes les commandes du bot. Seuls les utilisateurs présents dans la liste blanche définie dans `topology.json` peuvent utiliser les fonctionnalités du bot. L'authentification est implémentée via des décorateurs Python appliqués aux handlers. + +--- + +## Sous-skills + +### 1. Décorateur `@auth_required` (handlers de commandes) + +Fichier : `src/handlers/common.py` + +```python +@auth_required +async def node(update: Update, context: CallbackContext) -> None: + ... +``` + +**Fonctionnement :** +1. Extrait `user_id = str(update.effective_user.id)` +2. Lit la liste blanche depuis `context.bot_data['allowed_user_ids']` (un `set` de strings) +3. Si l'utilisateur **n'est pas** dans la liste : + - Envoie "You are not authorized to use this bot." + - Retourne `None` sans exécuter la fonction décorée +4. Si l'utilisateur **est** dans la liste : appelle la fonction handler normalement + +**Commandes concernées :** `/node`, `/btc`, `/mas`, `/hi`, `/temperature`, `/perf` + +--- + +### 2. Décorateur `@cb_auth_required` (handlers de callback query) + +Fichier : `src/handlers/common.py` + +```python +@cb_auth_required +async def flush_confirm_yes(update: Update, context: CallbackContext) -> int: + ... +``` + +**Spécificité :** Utilisé pour les callbacks des boutons inline (réponses aux claviers interactifs). Contrairement à `@auth_required`, il : +1. Extrait le `user_id` depuis `query.from_user.id` (et non `update.effective_user.id`) +2. En cas de refus : appelle `query.answer("Access denied.", show_alert=True)` (alerte popup) +3. Retourne `ConversationHandler.END` pour terminer proprement la conversation + +**Callbacks concernées :** `flush_confirm_yes/no`, `hist_confirm_yes/no`, `docker_start/stop`, `docker_*_confirm`, `massa_*` + +--- + +### 3. Vérification manuelle dans les ConversationHandlers + +Les points d'entrée des `ConversationHandler` ne peuvent pas utiliser `@auth_required` car ils doivent retourner un entier (état de la conversation) et non `None`. + +```python +async def flush(update: Update, context: CallbackContext) -> int: + user_id = str(update.effective_user.id) + allowed_user_ids = context.bot_data.get('allowed_user_ids', set()) + if user_id not in allowed_user_ids: + await update.message.reply_text("Access denied. You are not authorized.") + return ConversationHandler.END + ... +``` + +**Commandes concernées :** `/flush`, `/hist`, `/docker` + +--- + +### 4. Chargement de la liste blanche + +Fichier : `src/main.py` + +Au démarrage, la liste blanche est construite depuis `topology.json` : + +```python +admin_id = config.get('user_white_list', {}).get('admin') +allowed_user_ids = {str(admin_id)} +``` + +- La valeur est un `set` de strings (IDs Telegram sous forme de chaîne) +- Le set est stocké dans `application.bot_data['allowed_user_ids']` +- Tous les handlers y accèdent via `context.bot_data.get('allowed_user_ids', set())` +- Actuellement, seul le rôle `admin` est supporté (un seul utilisateur autorisé) + +--- + +### 5. Gestion des erreurs API (`handle_api_error`) + +Fichier : `src/handlers/common.py` + +Bien que non directement lié à l'authentification, cette fonction sécurise les réponses des APIs externes : + +```python +async def handle_api_error(update: Update, error_data: dict) -> bool +``` + +- Reçoit le dict retourné par une fonction API +- Si `"error"` est présent dans le dict : + - Timeout → envoie l'image `TIMEOUT_NAME` + - Autre erreur → envoie l'image `TIMEOUT_FIRE_NAME` + - Retourne `True` (erreur gérée, le handler doit s'arrêter) +- Si pas d'erreur → retourne `False` (le handler peut continuer) + +--- + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/common.py` | Décorateurs `auth_required`, `cb_auth_required`, `handle_api_error` | +| `src/main.py` | Chargement de `allowed_user_ids` depuis `topology.json` | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `user_white_list.admin` | ID Telegram de l'administrateur autorisé | + +## Exemple `topology.json` + +```json +{ + "user_white_list": { + "admin": "123456789" + } +} +``` + +## Sécurité + +- L'ID utilisateur est toujours converti en `str` avant comparaison (évite les erreurs de type int/str) +- La liste blanche est initialisée à `set()` par défaut (aucune autorisation si non configuré) +- La vérification s'effectue à chaque appel de handler (pas de cache de session) diff --git a/.github/skills/balance-history.md b/.github/skills/balance-history.md new file mode 100644 index 0000000..64750cc --- /dev/null +++ b/.github/skills/balance-history.md @@ -0,0 +1,93 @@ +# Skill : Historique du solde + +## Description + +Ce skill gère la persistance, la visualisation et l'effacement de l'historique des soldes Massa. Il comprend deux commandes interactives : `/hist` pour consulter l'historique sous forme de graphiques et de texte, et `/flush` pour effacer les logs et/ou l'historique. + +## Commandes + +``` +/hist +/flush +``` + +--- + +## Sous-skills + +### 1. Persistance JSON de l'historique (`services/history.py`) + +- L'historique est stocké dans `config/balance_history.json` (volume Docker monté) +- **`save_balance_history(balance_history)`** — sérialise et écrit le dict en JSON +- **`make_time_key(dt=None)`** — génère une clé temporelle au format `YYYY/MM/DD-HH:MM` +- **`build_balance_entry(balance, system_stats)`** — construit une entrée dict contenant : + - `balance` — solde MAS (float) + - `temperature` — température CPU moyenne (float ou `null`) + - `ram_percent` — pourcentage d'utilisation RAM (float ou `null`) +- Toutes les écritures dans `balance_history` sont protégées par un `threading.Lock` (`balance_lock` dans `bot_data`) + +### 2. Filtrage de l'historique + +- **`filter_last_24h(balance_history)`** — retourne les entrées des dernières 24 heures (fenêtre glissante) +- **`filter_since_midnight(balance_history)`** — retourne les entrées depuis minuit du jour courant +- **`get_entry_balance(entry)`** — extrait le solde d'une entrée (compatible formats ancien et nouveau) +- **`get_entry_temperature(entry)`** — extrait la température d'une entrée (retourne `None` si absente) +- **`format_history_entry(timestamp, entry)`** — formate une ligne d'historique : `HH:MM | balance MAS | temp°C | ram%` + +### 3. Commande `/hist` — Graphique et résumé + +#### Étape 1 : Menu de confirmation +- Affiche un message avec un clavier inline : + - **Graph only** → génère uniquement les graphiques + - **Graph + Text** → génère les graphiques et envoie également un résumé textuel + - **Cancel** → annule et termine la conversation + +#### Étape 2 : Génération des graphiques +- **`create_balance_history_plot(balance_history)`** — graphique matplotlib du solde dans le temps (`balance_history.png`) +- **`create_resources_plot(balance_history)`** — graphique matplotlib de la température CPU et RAM (`resources_plot.png`) +- Les deux images sont envoyées en réponse puis supprimées avec `safe_delete_file()` + +#### Étape 3 (optionnel) : Résumé textuel +- Liste toutes les entrées de l'historique formatées par `format_history_entry()` +- Gère la limite de 4096 caractères des messages Telegram (découpe en plusieurs messages si nécessaire) + +### 4. Commande `/flush` — Effacement des logs + +#### Étape 1 : Menu de confirmation +- Affiche un clavier inline : + - **Logs only** → supprime uniquement `bot_activity.log` + - **Logs + History** → supprime le log ET vide `balance_history.json` + - **Cancel** → annule sans rien supprimer + +#### Étape 2 : Exécution de l'effacement +- Efface le fichier `bot_activity.log` (chemin : `config/LOG_FILE_NAME`) +- Si « Logs + History » sélectionné : vide `balance_history` en mémoire et sur disque, puis recrée un fichier JSON vide + +### 5. Formats de clés temporelles (rétrocompatibilité) + +- Format actuel : `YYYY/MM/DD-HH:MM` (ex. `2024/03/15-14:30`) +- Format legacy : `DD/MM-HH:MM` (ex. `15/03-14:30`) — encore lisible par les filtres pour les anciennes données + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/node.py` | Handlers `/hist` et `/flush` (ConversationHandler) | +| `src/services/history.py` | Persistance, filtrage et formatage de l'historique | +| `src/services/plotting.py` | Génération des graphiques solde et ressources | +| `src/handlers/common.py` | `safe_delete_file()`, `cb_auth_required` | + +## Fichiers générés + +| Fichier | Description | Cycle de vie | +|---------|-------------|--------------| +| `config/balance_history.json` | Snapshots horodatés du solde | Persistant (volume Docker) | +| `balance_history.png` | Graphique du solde | Temporaire, supprimé après envoi | +| `resources_plot.png` | Graphique CPU / RAM | Temporaire, supprimé après envoi | +| `bot_activity.log` | Journal d'activité du bot | Persistant, effaçable via `/flush` | + +## Gestion des erreurs + +- Historique vide → message informatif sans graphique +- Échec de génération de graphique → message d'erreur +- Fichier log absent → ignore l'erreur silencieusement diff --git a/.github/skills/crypto-prices.md b/.github/skills/crypto-prices.md new file mode 100644 index 0000000..5b435ab --- /dev/null +++ b/.github/skills/crypto-prices.md @@ -0,0 +1,110 @@ +# Skill : Prix des cryptomonnaies + +## Description + +Ce skill récupère et affiche les prix en temps réel de Bitcoin (BTC) et de Massa (MAS) via des API externes. Il expose deux commandes : `/btc` pour le prix du Bitcoin et `/mas` pour le prix Massa/USDT. + +## Commandes + +``` +/btc +/mas +``` + +--- + +## Sous-skills + +### 1. Prix Bitcoin — `/btc` + +#### Source de données +- **API** : [API-Ninjas](https://www.api-ninjas.com/) — endpoint `/v1/cryptoprice?symbol=BTCUSDT` +- **Authentification** : Header `X-Api-Key: ` +- **Clé de configuration** : `ninja_api_key` dans `topology.json` + +#### Fonction d'appel (`services/price_api.py`) +```python +get_bitcoin_price(logger, ninja_key) -> dict +``` +- Effectue une requête GET avec retry (via `http_client.py`) +- Retourne les champs : `price`, `24h_price_change`, `24h_price_change_percent`, `24h_high`, `24h_low`, `24h_volume` +- En cas d'erreur réseau ou HTTP → retourne `{"error": "..."}` + +#### Format de la réponse bot +``` +Price: 65432.10 $ +24h Price Change: +1234.56 +24h Price Change Percent: +1.92% +24h High: 66000.00 +24h Low: 64000.00 +24h Volume: 12345678.90 +``` + +#### Gestion d'erreur +- En cas d'erreur API → message "Nooooo" + image `BTC_CRY_NAME` (définie dans `config.py`) + +--- + +### 2. Prix Massa — `/mas` + +#### Sources de données +- **API instantanée** : MEXC — `GET /api/v3/avgPrice?symbol=MASUSDT` +- **API 24h** : MEXC — `GET /api/v3/ticker/24hr?symbol=MASUSDT` +- Pas d'authentification requise pour les endpoints publics MEXC + +#### Fonctions d'appel (`services/price_api.py`) +```python +get_mas_instant(logger) -> dict # Prix moyen actuel +get_mas_daily(logger) -> dict # Statistiques 24h +``` +- Les deux appels sont lancés **en parallèle** via `asyncio.gather()` pour minimiser la latence +- Retournent `{"error": "..."}` en cas d'échec réseau ou HTTP + +#### Format de la réponse bot +``` +MASUSDT +----------- +Price: 0.00734 USDT +24h Volume: 1234567.890000 +----------- +Price Change %: +0.123456% +Price Change: +0.000009 +24h High: 0.007500 +24h Low: 0.007100 +``` + +#### Gestion d'erreur +- Vérifie les deux réponses API séquentiellement (bail on first error) +- En cas d'erreur → message "Nooooo" + image `MAS_CRY_NAME` (définie dans `config.py`) + +--- + +### 3. Client HTTP sécurisé (`services/http_client.py`) + +- Enveloppe commune utilisée par toutes les fonctions d'appel API +- Implémente une logique de retry avec backoff exponentiel +- Gère les timeouts de connexion et de lecture +- Retourne un dict avec le corps de la réponse ou `{"error": "..."}` en cas d'échec + +--- + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/price.py` | Handlers `/btc` et `/mas` | +| `src/services/price_api.py` | Appels API-Ninjas et MEXC | +| `src/services/http_client.py` | Client HTTP avec retry | +| `src/config.py` | Constantes `BTC_CRY_NAME`, `MAS_CRY_NAME` | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `ninja_api_key` | Clé API pour API-Ninjas (Bitcoin) | + +## Liens externes + +- [API-Ninjas — Crypto Price](https://www.api-ninjas.com/api/cryptoprice) +- [MEXC API — avgPrice](https://mexcdevelop.github.io/apidocs/spot_v3_en/#current-average-price) +- [MEXC API — 24hr Ticker](https://mexcdevelop.github.io/apidocs/spot_v3_en/#24hr-ticker-price-change-statistics) diff --git a/.github/skills/docker-management.md b/.github/skills/docker-management.md new file mode 100644 index 0000000..8e403fb --- /dev/null +++ b/.github/skills/docker-management.md @@ -0,0 +1,162 @@ +# Skill : Gestion Docker du nœud Massa + +## Description + +Ce skill permet de contrôler le conteneur Docker du nœud Massa directement depuis le bot, via la commande interactive `/docker`. Il utilise le SDK Python Docker (communication par socket Unix), sans nécessiter le CLI Docker dans le conteneur du bot. + +## Commande + +``` +/docker +``` + +--- + +## Architecture du menu interactif + +``` +/docker +└── 🐳 Docker Node Management + ├── ▶️ Start → ⚠️ Confirmation → Démarrage du conteneur + ├── ⏹️ Stop → ⚠️ Confirmation → Arrêt du conteneur + └── 💻 Massa Client + ├── 💰 Wallet Info → Exécution de wallet_info + ├── 🎲 Buy Rolls → Saisie du nombre → ⚠️ Confirmation → Exécution de buy_rolls + ├── 💸 Sell Rolls → Saisie du nombre → ⚠️ Confirmation → Exécution de sell_rolls + └── ⬅️ Back → Retour au menu principal +``` + +--- + +## Sous-skills + +### 1. Point d'entrée — `/docker` + +- Vérification manuelle de l'authentification (ConversationHandler ne permet pas `@auth_required`) +- Affiche le menu principal avec un clavier inline (Start / Stop / Massa Client) +- Retourne l'état `DOCKER_MENU_STATE` + +### 2. Démarrage du conteneur (`docker_start` → `docker_start_confirm`) + +- **Étape 1** : Demande de confirmation : "⚠️ Are you sure you want to start the node container?" + - **Yes** → passe à `DOCKER_START_CONFIRM_STATE` + - **No** → annule et termine la conversation + +- **Étape 2** : Exécution de `start_docker_node(logger, container_name)` : + ```python + client = docker.from_env() + container = client.containers.get(container_name) + container.start() + ``` + - Succès → message "✅ Container '' started." + - Échec → message "❌ Error: " + +### 3. Arrêt du conteneur (`docker_stop` → `docker_stop_confirm`) + +- **Étape 1** : Demande de confirmation : "⚠️ Are you sure you want to stop the node container?" + - **Yes** → passe à `DOCKER_STOP_CONFIRM_STATE` + - **No** → annule et termine la conversation + +- **Étape 2** : Exécution de `stop_docker_node(logger, container_name)` : + ```python + client = docker.from_env() + container = client.containers.get(container_name) + container.stop(timeout=30) + ``` + - Succès → message "✅ Container '' stopped." + - Délai d'arrêt : 30 secondes (grace period) + +### 4. Menu Massa Client (`docker_massa`) + +Sous-menu accessible depuis le menu principal. Présente trois actions : + +| Bouton | Commande massa-client | Description | +|--------|----------------------|-------------| +| 💰 Wallet Info | `wallet_info` | Affiche les informations du portefeuille | +| 🎲 Buy Rolls | `buy_rolls ` | Achète des rolls (avec saisie + confirmation) | +| 💸 Sell Rolls | `sell_rolls ` | Vend des rolls (avec saisie + confirmation) | + +### 5. Wallet Info (`docker_massa_wallet_info`) + +- Appelle directement `exec_massa_client(logger, container_name, password, "wallet_info")` +- Affiche la sortie brute de la commande dans le message bot +- Aucune confirmation requise + +### 6. Buy Rolls / Sell Rolls (flux en 3 étapes) + +**Étape 1 — Saisie du nombre de rolls** (état `DOCKER_BUYROLLS_INPUT_STATE` / `DOCKER_SELLROLLS_INPUT_STATE`) +- L'utilisateur envoie un message texte avec le nombre de rolls +- Validation : doit être un entier > 0 +- Le nombre est stocké dans `context.user_data['rolls_count']` + +**Étape 2 — Confirmation** (état `DOCKER_BUYROLLS_CONFIRM_STATE` / `DOCKER_SELLROLLS_CONFIRM_STATE`) +- Affiche "⚠️ Confirm: rolls?" +- **Yes** → exécution ; **No** → annulation + +**Étape 3 — Exécution** via `exec_massa_client` : +```python +# Buy Rolls +cmd = f"buy_rolls {wallet_address} {rolls_count} {buy_rolls_fee}" + +# Sell Rolls +cmd = f"sell_rolls {wallet_address} {rolls_count} {buy_rolls_fee}" +``` + +### 7. Exécution de commandes massa-client (`services/docker_manager.py`) + +Fonction `exec_massa_client(logger, container_name, password, command)` : + +```python +client = docker.from_env() +container = client.containers.get(container_name) +cmd = ['./massa-client', '-p', password, '-a'] + command.split() +exit_code, output = container.exec_run(cmd, workdir='/massa/massa-client') +``` + +- Décode la sortie en UTF-8 (erreurs remplacées) +- Envoie une commande `exit` après l'exécution pour fermer proprement la session +- Code de retour 0 → succès (`{"status": "ok", "output": ...}`) +- Code de retour non-nul → échec (`{"status": "error", "message": ...}`) + +### 8. Annulation et navigation + +- `docker_cancel` : annule l'action en cours, retourne au menu principal ou termine la conversation +- `docker_back` : retourne au menu Massa Client depuis un sous-menu +- Bouton "⬅️ Back" dans le menu Massa Client : retour au menu Docker principal + +--- + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/node.py` | Handlers du menu `/docker` et toutes les callbacks | +| `src/services/docker_manager.py` | Fonctions SDK Docker (start, stop, exec) | +| `src/config.py` | États de la conversation Docker (`DOCKER_*_STATE`) | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `docker_container_name` | Nom du conteneur Docker (ex. `massa-container`) | +| `massa_client_password` | Mot de passe pour `./massa-client -p` | +| `massa_wallet_address` | Adresse du portefeuille pour buy/sell rolls | +| `massa_buy_rolls_fee` | Frais de transaction (ex. `0.01`) | + +## Prérequis Docker + +Le conteneur du bot doit avoir accès au socket Docker : + +```yaml +volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` + +> Le package Python `docker` est requis (pas le CLI Docker). + +## Gestion des erreurs + +- Nom du conteneur non configuré → message d'erreur explicite +- Conteneur inexistant → propagation de l'exception Docker SDK +- Commande massa-client échouée → affichage de la sortie d'erreur +- Saisie invalide (rolls) → message de correction et nouvelle demande diff --git a/.github/skills/node-monitoring.md b/.github/skills/node-monitoring.md new file mode 100644 index 0000000..de7eaa9 --- /dev/null +++ b/.github/skills/node-monitoring.md @@ -0,0 +1,80 @@ +# Skill : Surveillance du nœud Massa + +## Description + +Ce skill permet de consulter l'état en temps réel du nœud Massa blockchain via la commande `/node`. Il interroge l'API JSON-RPC du nœud, affiche un résumé textuel et génère un graphique de validation. + +## Commande + +``` +/node +``` + +## Sous-skills + +### 1. Interrogation JSON-RPC + +- Appelle `get_addresses(logger, massa_node_address)` depuis `services/massa_rpc.py` +- Envoie une requête POST à l'endpoint JSON-RPC du nœud Massa +- Gère les erreurs réseau (timeout, connexion refusée) et retourne un dict `{"error": "..."}` en cas d'échec + +### 2. Extraction des données du nœud + +- Fonction `extract_address_data(json_data)` dans `handlers/node.py` +- Extrait depuis la réponse JSON : + - `final_balance` — solde courant du portefeuille (MAS) + - `final_roll_count` — nombre de rolls détenus + - `cycles` — liste des cycles de validation récents + - `ok_counts` — nombre de validations réussies par cycle + - `nok_counts` — nombre de validations échouées par cycle + - `active_rolls` — rolls actifs par cycle +- Retourne `None` si le nœud est injoignable ou si les données sont invalides + +### 3. Affichage du statut textuel + +- Compose et envoie un message texte récapitulatif : + ``` + Node status: + Final Balance: + Final Roll Count: + OK Counts: [...] + NOK Counts: [...] + Active Rolls: [...] + ``` + +### 4. Enregistrement du snapshot de solde + +- Appelle `make_time_key()` pour horodater l'entrée au format `YYYY/MM/DD-HH:MM` +- Appelle `build_balance_entry(balance, system_stats)` pour construire une entrée contenant : + - le solde + - la température CPU moyenne + - l'utilisation RAM +- Écrit dans `balance_history` (protégé par un `threading.Lock`) puis sauvegarde via `save_balance_history()` + +### 5. Génération du graphique de validation + +- Appelle `create_png_plot(cycles, ok_counts, nok_counts, active_rolls)` depuis `services/plotting.py` +- Génère un graphique matplotlib (`plot.png`) affichant OK/NOK/ActiveRolls par cycle +- Envoie l'image en réponse, puis la supprime avec `safe_delete_file()` + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/node.py` | Handler `/node`, extraction des données | +| `src/services/massa_rpc.py` | Appel JSON-RPC Massa | +| `src/services/plotting.py` | Génération du graphique de validation | +| `src/services/history.py` | Snapshot horodaté du solde | +| `src/services/system_monitor.py` | Statistiques système pour le snapshot | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `massa_node_address` | Adresse du portefeuille Massa pour le monitoring | + +## Gestion des erreurs + +- Timeout ou nœud injoignable → image d'erreur envoyée à l'utilisateur +- Données invalides → message texte d'erreur +- Échec de la génération du graphique → message d'erreur sans image diff --git a/.github/skills/scheduled-reports.md b/.github/skills/scheduled-reports.md new file mode 100644 index 0000000..7008714 --- /dev/null +++ b/.github/skills/scheduled-reports.md @@ -0,0 +1,109 @@ +# Skill : Rapports planifiés automatiques + +## Description + +Ce skill gère les vérifications périodiques du nœud Massa et l'envoi automatique de rapports de statut. Un job tourne toutes les 60 minutes pour contrôler l'état du nœud, enregistrer un snapshot du solde, et envoyer un rapport détaillé aux heures planifiées. + +## Déclencheurs + +- **Ping périodique** : toutes les 60 minutes (APScheduler, `interval`) +- **Rapports automatiques** : aux heures 7h, 12h et 21h si le nœud est actif + +--- + +## Sous-skills + +### 1. Initialisation du scheduler (`run_async_func`) + +- Crée ou réutilise la boucle `asyncio` (compatible environnements avec boucle déjà active) +- Initialise un `BackgroundScheduler` (APScheduler) +- Supprime un éventuel job obsolète portant le même ID (`JOB_SCHED_NAME`) +- Enregistre `periodic_node_ping` avec un intervalle de 60 minutes +- Démarre le scheduler si ce n'est pas déjà le cas + +### 2. Pont async/sync (`run_coroutine_in_loop`) + +- Permet d'exécuter une coroutine `asyncio` depuis un thread synchrone (celui du scheduler) +- Si la boucle est active : utilise `asyncio.run_coroutine_threadsafe()` (thread-safe) +- Si la boucle est inactive : utilise `loop.run_until_complete()` +- Les exceptions non gérées dans la coroutine sont loguées via un callback `add_done_callback` + +### 3. Ping périodique du nœud (`periodic_node_ping`) + +#### 3a. Vérification du statut +- Appelle `get_addresses(logger, massa_node_address)` pour interroger le nœud +- En cas d'erreur : + - Timeout → envoie une image dédiée `TIMEOUT_NAME` à tous les utilisateurs autorisés + - Autre erreur → envoie une image d'erreur `TIMEOUT_FIRE_NAME` + - Retour anticipé sans enregistrement de snapshot + +#### 3b. Détermination de l'état du nœud +- Le nœud est considéré **inactif** si : + - au moins un `nok_count` est non nul, **OU** + - `final_roll_count == 0` +- Alerte immédiate (`NODE_IS_DOWN`) envoyée à tous les utilisateurs si le nœud est inactif + +#### 3c. Enregistrement du snapshot +- Appelle `get_system_stats(logger)` pour collecter température CPU et RAM +- Crée la clé temporelle avec `make_time_key(now)` +- Construit l'entrée avec `build_balance_entry(balance, system_stats)` +- Écrit dans `balance_history` en utilisant `balance_lock` (thread-safe) et sauvegarde sur disque + +### 4. Rapport de statut aux heures planifiées (7h, 12h, 21h) + +Envoyé uniquement si le nœud est actif (`node_is_up == True`) et si `balance_history` n'est pas vide. + +#### Composition du rapport + +| Section | Contenu | +|---------|---------| +| **Indicateur** | `NODE_IS_UP` | +| **Comparaison de solde** | Premier solde depuis minuit (ou fenêtre 24h) vs solde courant | +| **Variation** | Montant et pourcentage avec indicateur 📈/📉 | +| **Température moyenne** | Moyenne CPU sur 24h glissantes (si données disponibles) | +| **Historique 24h** | Liste formatée de toutes les entrées des dernières 24 heures | + +#### Logique de référence du solde + +1. Priorité : premier enregistrement **depuis minuit** (`filter_since_midnight`) +2. Repli : première entrée de la **fenêtre glissante 24h** (`filter_last_24h`) +3. Dernier cas : solde à 0 si aucune donnée disponible + +#### Calcul de la variation +``` +balance_change = solde_courant - solde_reference_24h +change_percent = (balance_change / solde_reference_24h) * 100 +``` + +### 5. Diffusion aux utilisateurs + +- Parcourt `allowed_user_ids` (set stocké dans `application.bot_data`) +- Utilise `application.bot.send_message(chat_id=user_id, text=...)` pour chaque utilisateur autorisé +- Pour les erreurs avec image : utilise `application.bot.send_photo()` avec ouverture du fichier + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/scheduler.py` | Scheduler, ping périodique, rapports automatiques | +| `src/services/massa_rpc.py` | Interrogation JSON-RPC du nœud | +| `src/services/history.py` | Snapshots, filtrage 24h, formatage des entrées | +| `src/services/system_monitor.py` | Statistiques CPU/RAM pour les snapshots | +| `src/main.py` | Appel de `run_async_func()` au démarrage du bot | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `massa_node_address` | Adresse Massa pour les requêtes JSON-RPC | +| `user_white_list` | Liste des utilisateurs à notifier | + +## Constantes (`config.py`) + +| Constante | Valeur | Description | +|-----------|--------|-------------| +| `JOB_SCHED_NAME` | `"node_ping_job"` | Identifiant du job APScheduler | +| `NODE_IS_UP` | `"✅ Node is up"` | Message de statut nœud actif | +| `NODE_IS_DOWN` | `"❌ Node is down"` | Message d'alerte nœud inactif | +| `TIMEOUT_NAME` | `"timeout.png"` | Image pour les timeouts | +| `TIMEOUT_FIRE_NAME` | `"timeout_fire.png"` | Image pour les erreurs critiques | diff --git a/.github/skills/system-monitoring.md b/.github/skills/system-monitoring.md new file mode 100644 index 0000000..28568e5 --- /dev/null +++ b/.github/skills/system-monitoring.md @@ -0,0 +1,120 @@ +# Skill : Surveillance du système + +## Description + +Ce skill expose trois commandes pour surveiller l'état de la machine hébergeant le bot et le nœud Massa : `/hi` pour une salutation avec version, `/temperature` pour les métriques système détaillées, et `/perf` pour la performance du nœud (latence RPC et uptime). + +## Commandes + +``` +/hi +/temperature +/perf +``` + +--- + +## Sous-skills + +### 1. Salutation — `/hi` + +- Envoie un message de bienvenue avec la version courante du bot : + ``` + Hey dude! (version: a1b2c3d) + ``` +- Récupère le hash court du commit git courant via `subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])` +- En cas d'échec de la commande git → affiche `unknown` comme version +- Envoie une image de mascotte (`BUDDY_FILE_NAME` défini dans `config.py`) + +--- + +### 2. Statistiques système — `/temperature` + +#### Collecte des données (`services/system_monitor.py`) +- Appelle `get_system_stats(logger)` qui utilise la bibliothèque `psutil` +- Retourne un dict contenant : + +| Clé | Type | Description | +|-----|------|-------------| +| `temperature_details` | list | Détails par capteur : `sensor`, `label`, `current` (°C) | +| `temperature_avg` | float | Température CPU moyenne (°C) | +| `cpu_percent` | float | Utilisation CPU globale (%) | +| `cpu_cores` | list | Utilisation par cœur : `core`, `percent` | +| `ram_percent` | float | Utilisation RAM (%) | +| `ram_available_gb` | float | RAM disponible (Go) | +| `ram_total_gb` | float | RAM totale (Go) | + +> **Note** : `temperature_details` et `temperature_avg` ne sont disponibles que sur Linux (via `psutil.sensors_temperatures()`). Sur les systèmes sans capteurs, ces clés sont absentes. + +#### Format de la réponse bot +``` +🌡️ System Status +----------- +🌡️ Temperatures: + coretemp Physical id 0: 45.0°C + coretemp Core 0: 43.0°C + ... + Average: 44.5°C +----------- +CPU Usage Global: 12.3% +----------- +CPU Cores: + Core 0: 10.5% + Core 1: 14.1% + ... +----------- +RAM Usage: 67.2% +RAM Available: 5.2 GB / 15.6 GB +``` + +--- + +### 3. Performance du nœud — `/perf` + +#### 3a. Latence RPC (`services/massa_rpc.py`) +- Appelle `measure_rpc_latency(logger, massa_node_address)` +- Effectue une requête JSON-RPC minimaliste et mesure le temps de réponse +- Retourne `{"latency_ms": 42}` ou `{"error": "..."}` en cas d'échec + +#### 3b. Calcul de l'uptime (24h) +- Fonction `_calculate_uptime(balance_history)` dans `handlers/system.py` +- Compte les entrées de `balance_history` présentes dans la fenêtre des dernières 24 heures +- Hypothèse : 1 entrée par heure = 24 entrées → 100% d'uptime +- `uptime = min((entrées_24h / 24) * 100, 100.0)`, arrondi à 1 décimale + +#### 3c. Parsing des clés temporelles +- Fonction `_is_recent(key, cutoff, now)` — supporte deux formats : + - Format actuel : `YYYY/MM/DD-HH:MM` + - Format legacy : `DD/MM-HH:MM` (rétrocompatibilité avec les anciennes données) +- Si la date reconstruite semble dans le futur (à cause de l'absence d'année), recule d'un an + +#### Format de la réponse bot +``` +⚡ Node Performance +----------- +RPC Latency: 42 ms +Uptime (24h): 95.8% +``` + +--- + +## Fichiers concernés + +| Fichier | Rôle | +|---------|------| +| `src/handlers/system.py` | Handlers `/hi`, `/temperature`, `/perf`, calcul uptime | +| `src/services/system_monitor.py` | Collecte des métriques système via psutil | +| `src/services/massa_rpc.py` | Mesure de la latence RPC | +| `src/config.py` | Constante `BUDDY_FILE_NAME` | + +## Configuration requise + +| Clé `topology.json` | Description | +|---------------------|-------------| +| `massa_node_address` | Adresse Massa pour la mesure de latence RPC | + +## Gestion des erreurs + +- Échec `get_system_stats` → message d'erreur avec le détail +- Échec `measure_rpc_latency` → message d'erreur avec le détail +- Absence de capteurs de température → section température omise du message diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..1337f7f --- /dev/null +++ b/SKILL.md @@ -0,0 +1,15 @@ +# SKILL.md — Compétences du bot Robbi + +Ce fichier répertorie les compétences (skills) du bot Robbi. Chaque skill est documenté en détail dans un fichier dédié dans le répertoire [`.github/skills/`](.github/skills/). + +## Liste des skills + +| Skill | Commandes | Description | +|-------|-----------|-------------| +| [Surveillance du nœud Massa](.github/skills/node-monitoring.md) | `/node` | Statut temps réel du nœud, solde, rolls, graphique de validation | +| [Historique du solde](.github/skills/balance-history.md) | `/hist`, `/flush` | Persistance JSON, graphiques, effacement des logs | +| [Rapports planifiés](.github/skills/scheduled-reports.md) | *(automatique)* | Ping toutes les 60 min, rapports à 7h/12h/21h | +| [Prix des cryptomonnaies](.github/skills/crypto-prices.md) | `/btc`, `/mas` | Prix Bitcoin (API-Ninjas) et Massa/USDT (MEXC) | +| [Surveillance du système](.github/skills/system-monitoring.md) | `/hi`, `/temperature`, `/perf` | Métriques CPU/RAM/température, latence RPC, uptime | +| [Gestion Docker](.github/skills/docker-management.md) | `/docker` | Menu interactif : start/stop nœud, wallet_info, buy/sell rolls | +| [Authentification](.github/skills/authentication.md) | *(transversal)* | Décorateurs `@auth_required`, liste blanche `topology.json` |