8 Commits
v2.5 ... wip

Author SHA1 Message Date
bfd8428073 Fixed logo 2025-06-03 13:19:02 +00:00
b0eef8b8df Logo translated 2025-06-03 13:17:10 +00:00
e2d86cb787 Fully translated 2025-06-03 12:58:34 +00:00
8124ad6a2d In english please 2025-06-03 12:41:49 +00:00
27966c04e5 Merge pull request 'Correction structure, orthographe, emoji' (#8) from wip into main
Reviewed-on: #8
2025-04-24 12:53:48 +02:00
17ade38bcd Correction structure, orthographe, emoji 2025-04-24 10:51:52 +00:00
2f8ae16533 Merge pull request 'Correction variables par défaut' (#7) from wip into main
Reviewed-on: #7
2025-04-23 21:12:32 +02:00
80fe61f91d Minor changes 2025-04-23 18:58:15 +00:00
7 changed files with 180 additions and 184 deletions

View File

@ -16,7 +16,7 @@ RUN pip install --no-cache-dir -r requirements.txt
# Définir les variables d'environnement par défaut (modifiable lors du lancement du conteneur) # Définir les variables d'environnement par défaut (modifiable lors du lancement du conteneur)
ENV DISCORD_WEBHOOK_URL="https://example.com/webhook" \ ENV DISCORD_WEBHOOK_URL="https://example.com/webhook" \
REFRESH_TIME="60" REFRESH_TIME="30"
# Exposer un point de commande pour exécuter le script # Exposer un point de commande pour exécuter le script
CMD ["python", "nvidia-stock-bot.py"] CMD ["python", "nvidia-stock-bot.py"]

169
README.md
View File

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

Binary file not shown.

Before

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= # votre url du webhook Discord DS_HOOK= # Your Discord webhook URL
FREQ= # frequence de rafraichissement en secondes, par défaut 30 FREQ= # Refresh frequency in seconds, default is 30
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_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_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= 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=
PRODUCT_URL= # URL d'achat du GPU PRODUCT_URL= # Purchase URL of the GPU
PRODUCT_NAME= # Le nom exact du GPU que vous recherchez comme : "RTX 5080" PRODUCT_NAME= # The exact name of the GPU you're looking for, e.g., "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 # Permet d'afficher les logs en temps réel - PYTHONUNBUFFERED=1 # Allow to display log in real-time
command: python nvidia-stock-bot.py # Lance le script Python command: python nvidia-stock-bot.py # Run the script

View File

@ -5,64 +5,65 @@ import os
import re import re
from requests.adapters import HTTPAdapter, Retry from requests.adapters import HTTPAdapter, Retry
# Configuration du logger # Logger configuration
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("Démarrage du script") logging.info("Script started")
# Récupération des variables d'environnement # Retrieve environment variables
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')
API_URL_STOCK = os.environ.get('API_URL_STOCK', 'https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=') API_URL_STOCK = os.environ.get('API_URL_STOCK', 'https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=')
REFRESH_TIME = int(os.environ.get('REFRESH_TIME', 30)) REFRESH_TIME = int(os.environ.get('REFRESH_TIME'))
TEST_MODE = os.environ.get('TEST_MODE', 'False').lower() == 'true' TEST_MODE = os.environ.get('TEST_MODE', 'False').lower() == 'true'
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
if not DISCORD_WEBHOOK_URL: if not DISCORD_WEBHOOK_URL:
logging.error("❌ DISCORD_WEBHOOK_URL est requis mais non défini.") logging.error("❌ DISCORD_WEBHOOK_URL is required but not defined.")
exit(1) exit(1)
if not PRODUCT_NAME: if not PRODUCT_NAME:
logging.error("❌ PRODUCT_NAME est requis mais non défini.") logging.error("❌ PRODUCT_NAME is required but not defined.")
exit(1) exit(1)
# Regex pour extraire l'ID et le token # Regex to extract ID and 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)
# Masquer derniers caractères de l'ID # Mask last characters of the ID
masked_webhook_id = webhook_id[:len(webhook_id) - 10] + '*' * 10 masked_webhook_id = webhook_id[:len(webhook_id) - 10] + '*' * 10
# Masquer derniers caractères du token # Mask last characters of the token
masked_webhook_token = webhook_token[:len(webhook_token) - 120] + '*' * 10 masked_webhook_token = webhook_token[:len(webhook_token) - 120] + '*' * 10
# Reconstruction de l'url masquée # Rebuild masked URL
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
except KeyError as e: except KeyError as e:
logging.error(f"Variable d'environnement manquante : {e}") logging.error(f"Missing environment variable: {e}")
exit(1) exit(1)
except ValueError: except ValueError:
logging.error("REFRESH_TIME doit être un entier valide.") logging.error("REFRESH_TIME must be a valid integer.")
exit(1) exit(1)
# Affichage des URLs et configurations # Display URLs and configurations
logging.info(f"GPU: {PRODUCT_NAME}") logging.info(f"GPU: {PRODUCT_NAME}")
logging.info(f"URL Webhook Discord: {wh_masked_url}") logging.info(f"Discord Webhook URL: {wh_masked_url}")
logging.info(f"URL API SKU: {API_URL_SKU}") logging.info(f"API URL SKU: {API_URL_SKU}")
logging.info(f"URL API Stock: {API_URL_STOCK}") logging.info(f"API URL Stock: {API_URL_STOCK}")
logging.info(f"URL produit: {PRODUCT_URL}") logging.info(f"Product URL: {PRODUCT_URL}")
logging.info(f"Temps d'actualisation: {REFRESH_TIME} secondes") logging.info(f"Refresh time: {REFRESH_TIME} seconds")
logging.info(f"Mode Test: {TEST_MODE}") logging.info(f"Test Mode: {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) "
@ -81,30 +82,30 @@ HEADERS = {
"Sec-GPC": "1", "Sec-GPC": "1",
} }
# Session avec retries # Session with 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))
# Stockage de l'état des stocks # Store stock status
global_stock_status = {} global_stock_status = {}
# Stocke le dernier SKU connu # Store the last known SKU
last_sku = None last_sku = None
first_run = True # Before calling check_rtx_50_founders first_run = True # Before calling check_rtx_50_founders
# Notifications Discord # Discord notifications
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):
# Récupérer le timestamp UNIX actuel # Get current UNIX timestamp
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] Notification Discord: {gpu_name} disponible !") logging.info(f"[TEST MODE] Discord Notification: {gpu_name} available!")
return return
embed = { embed = {
"title": f"🚀 {PRODUCT_NAME} EN STOCK !", "title": f"🚀 {PRODUCT_NAME} IN 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"
@ -115,18 +116,18 @@ def send_discord_notification(gpu_name: str, product_link: str, products_price:
"fields": [ "fields": [
{ {
"name": "Prix", "name": "Price",
"value": f"`{products_price}€`", "value": f"`{products_price}€`",
"inline": True "inline": True
}, },
{ {
"name": "Heure", "name": "Time",
"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: [Acheter maintenant]({product_link})**", "description": f"**:point_right: [Buy now]({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"
@ -136,24 +137,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 envoyée sur Discord.") logging.info("✅ Notification sent to Discord.")
else: else:
logging.error(f" Erreur Webhook : {response.status_code} - {response.text}") logging.error(f"❌ Webhook error: {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}") logging.error(f"🚨 Error sending 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):
# Récupérer le timestamp UNIX actuel # Get current UNIX timestamp
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] Notification Discord: {gpu_name} hors stock !") logging.info(f"[TEST MODE] Discord Notification: {gpu_name} out of stock!")
return return
embed = { embed = {
"title": f"{PRODUCT_NAME} n'est plus en stock", "title": f"{PRODUCT_NAME} is out of stock",
"color": 15158332, # Rouge pour hors stock "color": 15158332, # Red for out of 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"
}, },
@ -169,7 +170,7 @@ def send_out_of_stock_notification(gpu_name: str, product_link: str, products_pr
"fields": [ "fields": [
{ {
"name": "Heure", "name": "Time",
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>", "value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
"inline": True "inline": True
} }
@ -179,26 +180,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("Notification 'hors stock' envoyée sur Discord.") logging.info("'Out of stock' notification sent to Discord.")
else: else:
logging.error(f" Erreur Webhook : {response.status_code} - {response.text}") logging.error(f"❌ Webhook error: {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}") logging.error(f"🚨 Error sending 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):
# Récupérer le timestamp UNIX actuel # Get current UNIX timestamp
timestamp_unix = int(time.time()) timestamp_unix = int(time.time())
if TEST_MODE: if TEST_MODE:
logging.info(f"[TEST MODE] Changement de SKU détecté : {old_sku}{new_sku}") logging.info(f"[TEST MODE] SKU change detected: {old_sku}{new_sku}")
return return
embed = { embed = {
"title": f"🔄 {PRODUCT_NAME} Changement de SKU détecté", "title": f"🔄 {PRODUCT_NAME} SKU change detected",
"url": f"{product_link}", "url": f"{product_link}",
"description": f"**Ancien SKU** : `{old_sku}`\n**Nouveau SKU** : `{new_sku}`", "description": f"**Old SKU** : `{old_sku}`\n**New SKU** : `{new_sku}`",
"color": 16776960, # Jaune "color": 16776960, # Yellow
"footer": { "footer": {
"text": "NviBot • JV Hardware 2.0", "text": "NviBot • JV Hardware 2.0",
@ -207,7 +208,7 @@ def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
"fields": [ "fields": [
{ {
"name": "Heure", "name": "Time",
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>", "value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
"inline": True "inline": True
} }
@ -215,7 +216,7 @@ def send_sku_change_notification(old_sku: str, new_sku: str, product_link: str):
} }
payload = { payload = {
"content": "@everyone ⚠️ Potentiel drop imminent !", "content": "@everyone ⚠️ Possible imminent drop!",
"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]
@ -224,90 +225,88 @@ 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("Notification de changement de SKU envoyée sur Discord.") logging.info("SKU change notification sent to Discord.")
else: else:
logging.error(f" Erreur Webhook : {response.status_code} - {response.text}") logging.error(f"❌ Webhook error: {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}") logging.error(f"🚨 Error sending webhook: {e}")
# Recherche du stock # Stock search
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
# Appel vers l'API produit pour récupérer le sku et l'upc # Call product API to retrieve SKU and 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"Réponse de l'API SKU : {response.status_code}") logging.info(f"SKU API response: {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"Erreur API SKU : {e}") logging.error(f"SKU API error: {e}")
return return
# Recherche du produit dont le GPU correspond à PRODUCT_NAME # Look for product whose GPU matches 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()
# Si le GPU correspond exactement à PRODUCT_NAME # If the GPU matches exactly PRODUCT_NAME
if gpu_name == PRODUCT_NAME.strip(): if gpu_name == PRODUCT_NAME.strip():
product_details = p product_details = p
break # Sortir dès qu'on trouve le bon produit break # Exit as soon as the correct product is found
if not product_details: if not product_details:
logging.warning(f"⚠️ Aucun produit avec le GPU '{PRODUCT_NAME}' trouvé.") logging.warning(f"⚠️ No product with GPU '{PRODUCT_NAME}' found.")
return return
# Récupérer le SKU pour le GPU trouvé # Retrieve the SKU for the found GPU
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: # Évite d'envoyer une notification au premier appel if not first_run: # Prevent sending notification on first run
product_link = PRODUCT_URL product_link = PRODUCT_URL
logging.warning(f"⚠️ SKU modifié : {last_sku}{product_sku}") logging.warning(f"⚠️ SKU changed: {last_sku}{product_sku}")
send_sku_change_notification(last_sku, product_sku, product_link) send_sku_change_notification(last_sku, product_sku, product_link)
# Mettre à jour le SKU stocké # Update stored SKU
last_sku = product_sku last_sku = product_sku
first_run = False # Désactive la protection après la première exécution first_run = False # Disable first-run protection
if not isinstance(product_upc, list): if not isinstance(product_upc, list):
product_upc = [product_upc] product_upc = [product_upc]
# Construction de l'url de l'API de stock et appel pour vérifier le statut # Build stock API URL and call to check status
API_URL = API_URL_STOCK + product_sku API_URL = API_URL_STOCK + product_sku
logging.info(f"URL de l'API de stock appelée : {API_URL}") logging.info(f"Stock API URL called: {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"Réponse de l'API : {response.status_code}") logging.info(f"Stock API response: {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"Erreur API Stock : {e}") logging.error(f"Stock API error: {e}")
return return
products = data.get("listMap", []) products = data.get("listMap", [])
products_price = 'Prix non disponible' # Valeur par défaut products_price = 'Price not available' # Default value
# Vérification de la liste des produits et récupération du prix # Check product list and retrieve price
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", 'Prix non disponible') price = product.get("price", 'Price not available')
if price != 'Prix non disponible': if price != 'Price not available':
products_price = price # Utiliser le prix trouvé products_price = price # Use found price
break # Sortir dès qu'on trouve un prix break # Stop at first found price
else: else:
logging.error("La liste des produits est vide ou mal formée.") logging.error("Product list is empty or malformed.")
found_in_stock = set() found_in_stock = set()
# Recherche du statut et notifications selon le statut # Check status and send notifications accordingly
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"
@ -323,20 +322,18 @@ 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} est maintenant en stock!") logging.info(f"{gpu} is now in 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} n'est plus en stock.") logging.info(f"{gpu} is no longer in stock.")
elif currently_in_stock and previously_in_stock: elif currently_in_stock and previously_in_stock:
logging.info(f"{gpu} est actuellement en stock.") logging.info(f"{gpu} is currently in stock.")
else: else:
logging.info(f"{gpu} est actuellement hors stock.") logging.info(f"{gpu} is currently out of stock.")
# Boucle # Loop
if __name__ == "__main__": if __name__ == "__main__":
while True: while True:
check_rtx_50_founders() check_rtx_50_founders()