1 Commits
wip ... french

Author SHA1 Message Date
d7b891abad French version 2025-06-03 13:06:37 +00:00
6 changed files with 186 additions and 180 deletions

169
README.md
View File

@ -1,171 +1,172 @@
<h1 align="center">Nvidia Stock Bot</h1> <h1 align="center">Nvidia Stock Bot</h1>
<div align="center"> <div align="center">
<a href="https://discord.gg/gxffg3GA96"> <a href="https://discord.gg/gxffg3GA96">
<img src="https://img.shields.io/badge/JV%20hardware-join-green?style=flat-square&logo=discord&logoColor=%23fff" alt="JV Hardware"> <img src="https://img.shields.io/badge/JV%20hardware-rejoindre-green?style=flat-square&logo=discord&logoColor=%23fff" alt="JV Hardware">
<a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank"> <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">
<img src="https://img.shields.io/badge/License-CC%20BY--NC%204.0-8E44AD?style=flat-square" alt="License: CC BY-NC 4.0"> <img src="https://img.shields.io/badge/License-CC%20BY--NC%204.0-8E44AD?style=flat-square" alt="License: CC BY-NC 4.0">
</a> </a>
</div> </div>
<div align="center" > <div align="center" >
<img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvidia-stock-bot-logo.png" alt="Nvidia Stock Bot" width="300"> <img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvidia-stock-bot-logo.png" alt="Nvidia Stock Bot" width="300">
</div> </div>
**🤖 Nvidia Stock Bot** - A bot that alerts you in real-time about **Nvidia RTX FE** GPU stock availability through Discord notifications. **🤖 Nvidia Stock Bot** - Un robot qui permet d'être alerté en temps réel des stocks de cartes graphiques **Nvidia RTX FE** grâce à des notifications Discord.
*The code was partially written and structured using a generative AI.* *Le code a été en partie rédigé et structuré à l'aide d'une IA générative.*
## 📌 Table of Contents ## 📌 Sommaire
- [✨ Features](#features) - [✨ Fonctionnalités](#fonctionnalit%C3%A9s)
- [🐳 Docker Installation without cloning the repo (quick)](#docker-installation-without-the-repo-quick) - [🐳 Installation docker sans le dépot (rapide)](#installation-sans-le-d%C3%A9pot-avec-docker-compose)
- [🐙 Docker Installation with the repo (developer)](#docker-installation-with-the-repo) - [🐙 Installation docker avec le dépot (développeur)](#installation-avec-le-d%C3%A9pot)
- [🐍 Python Installation (developer)](#python-installation) - [🐍 Installation avec Python (développeur)](#installation-avec-python)
- [🖼️ Screenshots](#screenshots) - [🖼️ Captures d'écran](#captures-d%C3%A9cran)
- [🧑‍💻 Contributors](#contributors) - [🧑‍💻 Contributeurs](#contributeurs)
## ✨ Features ## ✨ Fonctionnalités
- Discord `@everyone` notification on SKU change (possible imminent drop) - Notification Discord `@everyone` en cas de changement du SKU (potentiel drop imminent)
- Discord `@everyone` notification when stock is detected, including model, price, and link - Notification Discord `@everyone` en cas de stock détecté avec modèle, prix, et lien
- Silent Discord notification when no stock is detected - Notification Discord silencieuse en cas d'absence de stock détécté
- Selectable check frequency - Choix de la fréquence de la vérification
- Selectable GPU model - Choix du modèle à surveiller
## 🐳 Docker Installation without the repo (quick) <img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvbot_schematics.png" align="center">
Below are the instructions to set up the container using our pre-built image. With this setup, your bot will run independently as long as the container is active. ## 🐳 Installation sans le dépot avec docker compose
**Requirements** Vous trouverez-ci dessous les instructions pour configurer le conteneur avec notre image pré-compilée. Avec cette solution, votre bot tournera tout seul tant que le conteneur est actif.
**Pré-requis**
- [Docker](https://docs.docker.com/engine/install/) - [Docker](https://docs.docker.com/engine/install/)
**Configuration** **Configuration**
- Create a folder named `nvidia-stock-bot` - Créez un dossier `nvidia-stock-bot`
- Create a `compose.yaml` file inside that folder with the following content: - Créez le fichier `compose.yaml` dans ce dossier avec la configuration ci-dessous :
```yaml ```yaml
services: services:
nvidia-stock-bot: nvidia-stock-bot:
image: git.djeex.fr/djeex/nvidia-stock-bot:latest image: git.djeex.fr/djeex/nvidia-stock-bot:latest-french
container_name: nvidia-stock-bot container_name: nvidia-stock-bot
restart: unless-stopped restart: unless-stopped
environment: environment:
- DISCORD_WEBHOOK_URL= # Your Discord webhook URL - DISCORD_WEBHOOK_URL= # URL de votre webhook Discord
- PRODUCT_NAME= # Exact GPU name like "RTX 5080" - PRODUCT_NAME= # Le nom exact du GPU que vous recherchez comme "RTX 5080"
- PYTHONUNBUFFERED=1 # Enables real-time log output - PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
command: python nvidia-stock-bot.py command: python nvidia-stock-bot.py
``` ```
**Environment Variables:** **Variables d'environnements :**
| Variable | Description | Possible Values | Default Value | | Variables | Explications | Valeurs possibles | Valeur par défaut |
|---------------------|-------------------------------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| |---------------------|-------------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| DISCORD_WEBHOOK_URL | Your Discord webhook URL | A valid URL | | | DISCORD_WEBHOOK_URL | URL de votre webhook Discord | Une URL | |
| REFRESH_TIME | Script refresh interval in seconds | `60`, `30`, etc. | `30` | | REFRESH_TIME | Durée de rafraichissement du script en secondes | `60`, `30`, etc... | `30` |
| API_URL_SKU | API listing the product | A URL | `https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia` | | API_URL_SKU | API listant le produit | Une URL | `https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia` |
| API_URL_STOCK | API providing stock data | A URL | `https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=` | | API_URL_STOCK | API donnant le stock | Une URL | `https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=` |
| PRODUCT_URL | GPU purchase URL | A URL | `https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&manufacturer=NVIDIA` | | PRODUCT_URL | URL d'achat du GPU | Une URL | `https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&manufacturer=NVIDIA` |
| PRODUCT_NAME | The exact GPU name you're searching for | `RTX 5090`, `RTX 5080`, `RTX 5070` | | | PRODUCT_NAME | Le nom exact du GPU que vous recherchez | `RTX 5090`, `RTX 5080` ou `RTX 5070`. | |
| TEST_MODE | For testing without sending notifications | `True`, `False` | `False` | | TEST_MODE | Pour tester sans envoyer de notifs | `True`, `False` | `False` |
| PYTHONUNBUFFERED | Enables real-time log output | `1`, `0` | `1` | | PYTHONUNBUFFERED | #Permet d'afficher les logs en temps réel | `1`, `0` | `1` |
**Run the image** **Lancer l'image**
Navigate to the `nvidia-stock-bot` folder and launch the container: Rendez-vous dans le dossier `nvidia-stock-bot` et lancez le conteneur :
```sh ```sh
docker compose up -d docker compose up -d
``` ```
**Check logs to verify operation** **Voir les logs pour vérifier le bon fonctionnement**
```sh ```sh
docker logs -f nvidia-stock-bot docker logs -f nvidia-stock-bot
``` ```
## 📦 Docker Installation with the repo ## 📦 Installation avec le dépot
Instructions below show how to install the repo, build the Docker image, and launch the container. Your bot will run independently as long as the container is active. Vous trouverez-ci dessous les instructions pour installer le dépot, compiler l'image docker, et lancer le conteneur. Avec cette solution, votre bot tournera tout seul tant que le conteneur est actif.
**Requirements** **Pré-requis**
- [Git](https://git-scm.com/docs) - [Git](https://git-scm.com/docs)
- [Docker](https://docs.docker.com/engine/install/) - [Docker](https://docs.docker.com/engine/install/)
**Clone and configure** **Cloner et paramétrer**
- Clone the repo: - Clonez le repo :
```sh ```sh
git clone https://git.djeex.fr/Djeex/nvidia-stock-bot.git git clone https://git.djeex.fr/Djeex/nvidia-stock-bot.git
``` ```
- Navigate to `nvidia-stock-bot` and build the Docker image: - Rendez vous dans le dossier `nvidia-stock-bot` et compilez l'image docker :
```sh ```sh
docker build -t nvidia-stock-bot . docker build -t nvidia-stock-bot .
``` ```
- Then go to `nvidia-stock-bot/docker` and edit the `.env` file with: - Puis rendez-vous dans le dossier `nvidia-stock-bot/docker` et éditez le fichier `.env` avec :
- Your Discord webhook URL - l'url de votre webhook discord
- The API and product URLs - les différents liens API et produits
- Stock checking frequency (default: 60s; lowering too much may get your IP blocked by Nvidia) - la fréquence de consultation des stock (par défaut 60s, attention à ne pas trop descendre sous peine de blocage de votre adresse IP par nVidia)
**Run the image** **Lancer l'image**
Navigate to `nvidia-stock-bot/docker` and launch the container: Rendez-vous dans le dossier `nvidia-stock-bot/docker` et lancez le conteneur :
```sh ```sh
docker compose up -d docker compose up -d
``` ```
**Check logs to verify operation** **Voir les logs pour vérifier le bon fonctionnement**
```sh ```sh
docker logs -f nvidia-stock-bot docker logs -f nvidia-stock-bot
``` ```
## 🐍 Python Installation ## 🐍 Installation avec Python
Instructions to directly run the Python script. Note: the bot stops when you close the terminal. Vous trouverez ci-dessous comment exécuter directement le script Python. Avec cette solution, le bot s'arretera si vous fermez votre terminal.
**Requirements** **Pré-requis**
- Python 3.11 or newer - Python 3.11 ou plus
- requests: `pip install requests` - requests : `pip install requests`
**Configuration** **Configuration**
- Create a virtual environment (e.g., `python3 -m venv env_name`) - Créez un environnement virtuel (exemple : `python3 -m venv nom_de_l_environnement` )
- Create and navigate into a folder - Créez un dossier et aller dedans
- Download the Python script: - Téléchargez le script python :
```sh ```sh
curl -o nvidia-stock-bot.py -# https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/nvidia-stock-bot.py curl -o nvidia-stock-bot.py -# https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/nvidia-stock-bot.py
``` ```
- Exportez les variables d'environnement avec votre webhook discord et le temps de rafraichissement en secondes, par exemple :
- Export the environment variables with your webhook and refresh time: ```sh
export DISCORD_WEBHOOK_URL="https://votre_url_discord"
export REFRESH_TIME="60"
export API_URL_SKU="https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia&gpu=RTX%205080"
export API_URL_STOCK="https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus="
export PRODUCT_URL= "https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&gpu=RTX%205080&manufacturer=NVIDIA"
export PRODUCT_NAME="RTX 5080"
export TEST_MODE=false
```
- Lancez le script
```sh ```sh
export DISCORD_WEBHOOK_URL="https://your_discord_url" python nvidia-stock-bot.py
export REFRESH_TIME="60" ```
export API_URL_SKU="https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia&gpu=RTX%205080"
export API_URL_STOCK="https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus="
export PRODUCT_URL="https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&gpu=RTX%205080&manufacturer=NVIDIA"
export PRODUCT_NAME="RTX 5080"
export TEST_MODE=false
```
- Run the script ## 🖼️ Captures d'écran
```sh <div align="center" >
python nvidia-stock-bot.py <img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvidia-stock-bot-discord.png" alt="Nvidia Stock Bot - captures">
```
## 🖼️ Screenshots
<div align="center" >
<img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvidia-stock-bot-discord.png" alt="Nvidia Stock Bot - screenshots">
</div> </div>
## 🧑‍💻 Contributors ## 🧑‍💻 Contributeurs
Thanks for their contributions: On remercie pour leurs contributions :
- Djeex - Djeex
- KevOut - KevOut

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,6 +1,6 @@
DS_HOOK= # Your Discord webhook URL DS_HOOK= # Votre url du webhook Discord
FREQ= # Refresh frequency in seconds, default is 30 FREQ= # Frequence de rafraichissement en secondes, par défaut 30
API_URL_SKU= # API listing the product, default is https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia API_URL_SKU= # API listant le produit, par défaut https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia
API_URL_STOCK= # API used to check stock without specifying the SKU, default is https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus= API_URL_STOCK= # API appelant le stock sans préciser la valeur du sku, par défaut https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=
PRODUCT_URL= # Purchase URL of the GPU PRODUCT_URL= # URL d'achat du GPU
PRODUCT_NAME= # The exact name of the GPU you're looking for, e.g., "RTX 5080" PRODUCT_NAME= # Le nom exact du GPU que vous recherchez comme : "RTX 5080"

View File

@ -12,5 +12,5 @@ services:
- API_URL_STOCK=${API_URL_STOCK} - API_URL_STOCK=${API_URL_STOCK}
- PRODUCT_URL=${PRODUCT_URL} - PRODUCT_URL=${PRODUCT_URL}
- PRODUCT_NAME=${PRODUCT_NAME} - PRODUCT_NAME=${PRODUCT_NAME}
- PYTHONUNBUFFERED=1 # Allow to display log in real-time - PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
command: python nvidia-stock-bot.py # Run the script command: python nvidia-stock-bot.py # Lance le script Python

View File

@ -5,14 +5,14 @@ import os
import re import re
from requests.adapters import HTTPAdapter, Retry from requests.adapters import HTTPAdapter, Retry
# Logger configuration # Configuration du logger
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s", format="%(asctime)s [%(levelname)s] %(message)s",
) )
logging.info("Script started") logging.info("Démarrage du script")
# Retrieve environment variables # Récupération des variables d'environnement
try: try:
DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL') DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL')
API_URL_SKU = os.environ.get('API_URL_SKU', 'https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia') API_URL_SKU = os.environ.get('API_URL_SKU', 'https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia')
@ -22,48 +22,49 @@ try:
PRODUCT_URL = os.environ.get('PRODUCT_URL', 'https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&manufacturer=NVIDIA') PRODUCT_URL = os.environ.get('PRODUCT_URL', 'https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&manufacturer=NVIDIA')
PRODUCT_NAME = os.environ.get('PRODUCT_NAME') PRODUCT_NAME = os.environ.get('PRODUCT_NAME')
# Error logging # Logging des erreurs
if not DISCORD_WEBHOOK_URL: if not DISCORD_WEBHOOK_URL:
logging.error("❌ DISCORD_WEBHOOK_URL is required but not defined.") logging.error("❌ DISCORD_WEBHOOK_URL est requis mais non défini.")
exit(1) exit(1)
if not PRODUCT_NAME: if not PRODUCT_NAME:
logging.error("❌ PRODUCT_NAME is required but not defined.") logging.error("❌ PRODUCT_NAME est requis mais non défini.")
exit(1) exit(1)
# Regex to extract ID and token # Regex pour extraire l'ID et le token
match = re.search(r'/(\d+)/(.*)', DISCORD_WEBHOOK_URL) match = re.search(r'/(\d+)/(.*)', DISCORD_WEBHOOK_URL)
if match: if match:
webhook_id = match.group(1) webhook_id = match.group(1)
webhook_token = match.group(2) webhook_token = match.group(2)
# Mask last characters of the ID # Masquer derniers caractères de l'ID
masked_webhook_id = webhook_id[:len(webhook_id) - 10] + '*' * 10 masked_webhook_id = webhook_id[:len(webhook_id) - 10] + '*' * 10
# Mask last characters of the token # Masquer derniers caractères du token
masked_webhook_token = webhook_token[:len(webhook_token) - 120] + '*' * 10 masked_webhook_token = webhook_token[:len(webhook_token) - 120] + '*' * 10
# Rebuild masked URL # Reconstruction de l'url masquée
wh_masked_url = f"https://discord.com/api/webhooks/{masked_webhook_id}/{masked_webhook_token}" wh_masked_url = f"https://discord.com/api/webhooks/{masked_webhook_id}/{masked_webhook_token}"
# Error logging # Logging des erreurs
except KeyError as e: except KeyError as e:
logging.error(f"Missing environment variable: {e}") logging.error(f"Variable d'environnement manquante : {e}")
exit(1) exit(1)
except ValueError: except ValueError:
logging.error("REFRESH_TIME must be a valid integer.") logging.error("REFRESH_TIME doit être un entier valide.")
exit(1) exit(1)
# Display URLs and configurations # Affichage des URLs et configurations
logging.info(f"GPU: {PRODUCT_NAME}") logging.info(f"GPU: {PRODUCT_NAME}")
logging.info(f"Discord Webhook URL: {wh_masked_url}") logging.info(f"URL Webhook Discord: {wh_masked_url}")
logging.info(f"API URL SKU: {API_URL_SKU}") logging.info(f"URL API SKU: {API_URL_SKU}")
logging.info(f"API URL Stock: {API_URL_STOCK}") logging.info(f"URL API Stock: {API_URL_STOCK}")
logging.info(f"Product URL: {PRODUCT_URL}") logging.info(f"URL produit: {PRODUCT_URL}")
logging.info(f"Refresh time: {REFRESH_TIME} seconds") logging.info(f"Temps d'actualisation: {REFRESH_TIME} secondes")
logging.info(f"Test Mode: {TEST_MODE}") logging.info(f"Mode Test: {TEST_MODE}")
# HTTP headers
# Entêtes HTTP
HEADERS = { HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) " "AppleWebKit/537.36 (KHTML, like Gecko) "
@ -82,30 +83,30 @@ HEADERS = {
"Sec-GPC": "1", "Sec-GPC": "1",
} }
# Session with retries # Session avec retries
session = requests.Session() session = requests.Session()
retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries)) session.mount('https://', HTTPAdapter(max_retries=retries))
# Store stock status # Stockage de l'état des stocks
global_stock_status = {} global_stock_status = {}
# Store the last known SKU # Stocke le dernier SKU connu
last_sku = None last_sku = None
first_run = True # Before calling check_rtx_50_founders first_run = True # Before calling check_rtx_50_founders
# Discord notifications # Notifications Discord
def send_discord_notification(gpu_name: str, product_link: str, products_price: str): def send_discord_notification(gpu_name: str, product_link: str, products_price: str):
# Get current UNIX timestamp # Récupérer le timestamp UNIX actuel
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] Discord Notification: {gpu_name} available!") logging.info(f"[TEST MODE] Notification Discord: {gpu_name} disponible !")
return return
embed = { embed = {
"title": f"🚀 {PRODUCT_NAME} IN STOCK!", "title": f"🚀 {PRODUCT_NAME} EN STOCK !",
"color": 3066993, "color": 3066993,
"thumbnail": { "thumbnail": {
"url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg" "url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg"
@ -116,18 +117,18 @@ def send_discord_notification(gpu_name: str, product_link: str, products_price:
"fields": [ "fields": [
{ {
"name": "Price", "name": "Prix",
"value": f"`{products_price}€`", "value": f"`{products_price}€`",
"inline": True "inline": True
}, },
{ {
"name": "Time", "name": "Heure",
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>", "value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
"inline": True "inline": True
}, },
], ],
"description": f"**:point_right: [Buy now]({product_link})**", "description": f"**:point_right: [Acheter maintenant]({product_link})**",
"footer": { "footer": {
"text": "NviBot • JV Hardware 2.0", "text": "NviBot • JV Hardware 2.0",
"icon_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg" "icon_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg"
@ -137,24 +138,24 @@ def send_discord_notification(gpu_name: str, product_link: str, products_price:
try: try:
response = requests.post(DISCORD_WEBHOOK_URL, json=payload) response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
if response.status_code == 204: if response.status_code == 204:
logging.info("✅ Notification sent to Discord.") logging.info("✅ Notification envoyée sur Discord.")
else: else:
logging.error(f"❌ Webhook error: {response.status_code} - {response.text}") logging.error(f" Erreur Webhook : {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Error sending webhook: {e}") logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}")
def send_out_of_stock_notification(gpu_name: str, product_link: str, products_price: str): def send_out_of_stock_notification(gpu_name: str, product_link: str, products_price: str):
# Get current UNIX timestamp # Récupérer le timestamp UNIX actuel
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] Discord Notification: {gpu_name} out of stock!") logging.info(f"[TEST MODE] Notification Discord: {gpu_name} hors stock !")
return return
embed = { embed = {
"title": f"{PRODUCT_NAME} is out of stock", "title": f"{PRODUCT_NAME} n'est plus en stock",
"color": 15158332, # Red for out of stock "color": 15158332, # Rouge pour hors stock
"thumbnail": { "thumbnail": {
"url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg" "url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg"
}, },
@ -170,7 +171,7 @@ def send_out_of_stock_notification(gpu_name: str, product_link: str, products_pr
"fields": [ "fields": [
{ {
"name": "Time", "name": "Heure",
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>", "value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
"inline": True "inline": True
} }
@ -180,26 +181,26 @@ def send_out_of_stock_notification(gpu_name: str, product_link: str, products_pr
try: try:
response = requests.post(DISCORD_WEBHOOK_URL, json=payload) response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
if response.status_code == 204: if response.status_code == 204:
logging.info("'Out of stock' notification sent to Discord.") logging.info("Notification 'hors stock' envoyée sur Discord.")
else: else:
logging.error(f"❌ Webhook error: {response.status_code} - {response.text}") logging.error(f" Erreur Webhook : {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Error sending webhook: {e}") logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}")
def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str): def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
# Get current UNIX timestamp # Récupérer le timestamp UNIX actuel
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] SKU change detected: {old_sku}{new_sku}") logging.info(f"[TEST MODE] Changement de SKU détecté : {old_sku}{new_sku}")
return return
embed = { embed = {
"title": f"🔄 {PRODUCT_NAME} SKU change detected", "title": f"🔄 {PRODUCT_NAME} Changement de SKU détecté",
"url": f"{product_link}", "url": f"{product_link}",
"description": f"**Old SKU** : `{old_sku}`\n**New SKU** : `{new_sku}`", "description": f"**Ancien SKU** : `{old_sku}`\n**Nouveau SKU** : `{new_sku}`",
"color": 16776960, # Yellow "color": 16776960, # Jaune
"footer": { "footer": {
"text": "NviBot • JV Hardware 2.0", "text": "NviBot • JV Hardware 2.0",
@ -208,7 +209,7 @@ def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
"fields": [ "fields": [
{ {
"name": "Time", "name": "Heure",
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>", "value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
"inline": True "inline": True
} }
@ -216,7 +217,7 @@ def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
} }
payload = { payload = {
"content": "@everyone ⚠️ Possible imminent drop!", "content": "@everyone ⚠️ Potentiel drop imminent !",
"username": "NviBot", "username": "NviBot",
"avatar_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg", "avatar_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg",
"embeds": [embed] "embeds": [embed]
@ -225,88 +226,90 @@ def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
try: try:
response = requests.post(DISCORD_WEBHOOK_URL, json=payload) response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
if response.status_code == 204: if response.status_code == 204:
logging.info("SKU change notification sent to Discord.") logging.info("Notification de changement de SKU envoyée sur Discord.")
else: else:
logging.error(f"❌ Webhook error: {response.status_code} - {response.text}") logging.error(f" Erreur Webhook : {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Error sending webhook: {e}") logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}")
# Stock search # Recherche du stock
def check_rtx_50_founders(): def check_rtx_50_founders():
global global_stock_status, last_sku, first_run global global_stock_status, last_sku, first_run
# Call product API to retrieve SKU and UPC # Appel vers l'API produit pour récupérer le sku et l'upc
try: try:
response = session.get(API_URL_SKU, headers=HEADERS, timeout=10) response = session.get(API_URL_SKU, headers=HEADERS, timeout=10)
logging.info(f"SKU API response: {response.status_code}") logging.info(f"Réponse de l'API SKU : {response.status_code}")
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logging.error(f"SKU API error: {e}") logging.error(f"Erreur API SKU : {e}")
return return
# Look for product whose GPU matches PRODUCT_NAME # Recherche du produit dont le GPU correspond à PRODUCT_NAME
product_details = None product_details = None
for p in data['searchedProducts']['productDetails']: for p in data['searchedProducts']['productDetails']:
gpu_name = p.get("gpu", "").strip() gpu_name = p.get("gpu", "").strip()
# If the GPU matches exactly PRODUCT_NAME # Si le GPU correspond exactement à PRODUCT_NAME
if gpu_name == PRODUCT_NAME.strip(): if gpu_name == PRODUCT_NAME.strip():
product_details = p product_details = p
break # Exit as soon as the correct product is found break # Sortir dès qu'on trouve le bon produit
if not product_details: if not product_details:
logging.warning(f"⚠️ No product with GPU '{PRODUCT_NAME}' found.") logging.warning(f"⚠️ Aucun produit avec le GPU '{PRODUCT_NAME}' trouvé.")
return return
# Retrieve the SKU for the found GPU # Récupérer le SKU pour le GPU trouvé
product_sku = product_details['productSKU'] product_sku = product_details['productSKU']
product_upc = product_details.get('productUPC', "") product_upc = product_details.get('productUPC', "")
# Check if this is the first execution
# Vérifier si c'est la première exécution
if last_sku is not None and product_sku != last_sku: if last_sku is not None and product_sku != last_sku:
if not first_run: # Prevent sending notification on first run if not first_run: # Évite d'envoyer une notification au premier appel
product_link = PRODUCT_URL product_link = PRODUCT_URL
logging.warning(f"⚠️ SKU changed: {last_sku}{product_sku}") logging.warning(f"⚠️ SKU modifié : {last_sku}{product_sku}")
send_sku_change_notification(last_sku, product_sku, product_link) send_sku_change_notification(last_sku, product_sku, product_link)
# Update stored SKU # Mettre à jour le SKU stocké
last_sku = product_sku last_sku = product_sku
first_run = False # Disable first-run protection first_run = False # Désactive la protection après la première exécution
if not isinstance(product_upc, list): if not isinstance(product_upc, list):
product_upc = [product_upc] product_upc = [product_upc]
# Build stock API URL and call to check status # Construction de l'url de l'API de stock et appel pour vérifier le statut
API_URL = API_URL_STOCK + product_sku API_URL = API_URL_STOCK + product_sku
logging.info(f"Stock API URL called: {API_URL}") logging.info(f"URL de l'API de stock appelée : {API_URL}")
try: try:
response = session.get(API_URL, headers=HEADERS, timeout=10) response = session.get(API_URL, headers=HEADERS, timeout=10)
logging.info(f"Stock API response: {response.status_code}") logging.info(f"Réponse de l'API : {response.status_code}")
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logging.error(f"Stock API error: {e}") logging.error(f"Erreur API Stock : {e}")
return return
products = data.get("listMap", []) products = data.get("listMap", [])
products_price = 'Price not available' # Default value products_price = 'Prix non disponible' # Valeur par défaut
# Check product list and retrieve price # Vérification de la liste des produits et récupération du prix
if isinstance(products, list) and len(products) > 0: if isinstance(products, list) and len(products) > 0:
for product in products: for product in products:
price = product.get("price", 'Price not available') price = product.get("price", 'Prix non disponible')
if price != 'Price not available': if price != 'Prix non disponible':
products_price = price # Use found price products_price = price # Utiliser le prix trouvé
break # Stop at first found price break # Sortir dès qu'on trouve un prix
else: else:
logging.error("Product list is empty or malformed.") logging.error("La liste des produits est vide ou mal formée.")
found_in_stock = set() found_in_stock = set()
# Check status and send notifications accordingly # Recherche du statut et notifications selon le statut
for p in products: for p in products:
gpu_name = p.get("fe_sku", "").upper() gpu_name = p.get("fe_sku", "").upper()
is_active = p.get("is_active") == "true" is_active = p.get("is_active") == "true"
@ -322,18 +325,20 @@ def check_rtx_50_founders():
product_link = PRODUCT_URL product_link = PRODUCT_URL
send_discord_notification(gpu_upper, product_link, products_price) send_discord_notification(gpu_upper, product_link, products_price)
global_stock_status[gpu_upper] = True global_stock_status[gpu_upper] = True
logging.info(f"{gpu} is now in stock!") logging.info(f"{gpu} est maintenant en stock!")
elif not currently_in_stock and previously_in_stock: elif not currently_in_stock and previously_in_stock:
product_link = PRODUCT_URL product_link = PRODUCT_URL
send_out_of_stock_notification(gpu_upper, product_link, products_price) send_out_of_stock_notification(gpu_upper, product_link, products_price)
global_stock_status[gpu_upper] = False global_stock_status[gpu_upper] = False
logging.info(f"{gpu} is no longer in stock.") logging.info(f"{gpu} n'est plus en stock.")
elif currently_in_stock and previously_in_stock:
logging.info(f"{gpu} is currently in stock.")
else:
logging.info(f"{gpu} is currently out of stock.")
# Loop elif currently_in_stock and previously_in_stock:
logging.info(f"{gpu} est actuellement en stock.")
else:
logging.info(f"{gpu} est actuellement hors stock.")
# Boucle
if __name__ == "__main__": if __name__ == "__main__":
while True: while True:
check_rtx_50_founders() check_rtx_50_founders()