Compare commits
61 Commits
f9bc3dfe32
...
v2.3
Author | SHA1 | Date | |
---|---|---|---|
0822e309db | |||
85827eddc5 | |||
a6a62e84d3 | |||
06eef00c79 | |||
6d4a160416 | |||
7894b03883 | |||
c787c61332 | |||
d6b5273e5a | |||
10ab4f6fc0 | |||
f133344f7a | |||
d1609d0776 | |||
bf5fa6203b | |||
aae087c91b | |||
b69b45a03f | |||
5503fbf275 | |||
2b192247bb | |||
e1e3502be2 | |||
e585f18a93 | |||
fc6ae91c55 | |||
a496a1b1bf | |||
1722f276a9 | |||
38b60300c4 | |||
6a6336a07c | |||
ef27e320e2 | |||
16e80db82f | |||
3dd9dd5946 | |||
9dd3d76d7f | |||
45b6bed116 | |||
24cad015bc | |||
b0309f34fe | |||
841ffb5efe | |||
d4e34f20ec | |||
ddfbe853e9 | |||
4115825953 | |||
dfa3a5e19c | |||
cdbb1be864 | |||
3ed38e7aa3 | |||
c392efce35 | |||
2761a9dacb | |||
006531aacc | |||
a8d4c05c81 | |||
280e8d0347 | |||
53c46b1d2f | |||
5b1e180c6f | |||
7872f8aa00 | |||
23b2f375fc | |||
62bc725a1c | |||
b381efd257 | |||
037f5bebf8 | |||
0803e3074b | |||
21f6bd7a1f | |||
8e2e91a760 | |||
d95afc4627 | |||
d97a32623f | |||
705a96d0c2 | |||
66ca9e907d | |||
82557b77ab | |||
d48d9d537b | |||
8d1fd5d94a | |||
86665b7137 | |||
8ccc925966 |
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0)
|
||||||
|
|
||||||
|
Copyright (c) 2025 Djeex
|
||||||
|
|
||||||
|
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
|
||||||
|
|
||||||
|
You are free to:
|
||||||
|
|
||||||
|
Share — copy, distribute, and transmit the material in any medium or format
|
||||||
|
|
||||||
|
Adapt — remix, transform, and build upon the material
|
||||||
|
|
||||||
|
Under the following terms:
|
||||||
|
|
||||||
|
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
||||||
|
|
||||||
|
NonCommercial — You may not use the material for commercial purposes.
|
||||||
|
|
||||||
|
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
||||||
|
|
||||||
|
You can review the full license here:
|
||||||
|
https://creativecommons.org/licenses/by-nc/4.0/
|
||||||
|
|
||||||
|
DISCLAIMER: This text does not constitute legal advice. For any legal questions, please consult a professional.
|
156
README.md
156
README.md
@ -1,94 +1,131 @@
|
|||||||
# Nvidia Stock Bot - WIP -
|
<h1 align="center"> Nvidia Stock Bot</h1>
|
||||||
Par KevOut & Djeex
|
<div align="center">
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
</a>
|
||||||
|
|
||||||
[](https://discord.gg/gxffg3GA96)
|
</div>
|
||||||
|
<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">
|
||||||
|
</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.
|
||||||
|
|
||||||
Ce robot :
|
*Le code a été en partie rédigé et structuré à l'aide d'une IA générative.*
|
||||||
- Appelle régulièrement l'api des stocks français de nvidia FE (par défaut toutes les 60s)
|
|
||||||
- Vérifie si RTX 5090, RTX 5080, RTX 5070ti et RTX 5070 sont en stock
|
|
||||||
- Si du stock est trouvé, envoie une notification discord via le webhook paramétré
|
|
||||||
|
|
||||||
<img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvbot.png" align="center">
|
## Sommaire
|
||||||
|
|
||||||
|
- [Fonctionnalités](#fonctionnalit%C3%A9s)
|
||||||
|
- [Installation docker sans le dépot (rapide)](#installation-sans-le-d%C3%A9pot-avec-docker-compose)
|
||||||
|
- [Installation docker avec le dépot (développeur)](#installation-avec-le-d%C3%A9pot)
|
||||||
|
- [Installation avec Python (développeur)](#installation-avec-python)
|
||||||
|
- [Captures d'écran](#captures-d%C3%A9cran)
|
||||||
|
- [Contributeurs](#contributeurs)
|
||||||
|
|
||||||
Trois modes d'installation :
|
## Fonctionnalités
|
||||||
- [Avec le dépot Git et Docker](https://git.djeex.fr/Djeex/nvidia-stock-bot/#installation-avec-le-d%C3%A9pot)
|
|
||||||
- [Sans le dépot Git et avec notre image docker fournie](https://git.djeex.fr/Djeex/nvidia-stock-bot/#installation-sans-le-d%C3%A9pot-avec-docker-compose)
|
- Notification Discord `@everyone` en cas de changement du SKU (potentiel drop imminent)
|
||||||
- [Avec python (développeurs)](https://git.djeex.fr/Djeex/nvidia-stock-bot/#installation-sans-le-d%C3%A9pot-avec-docker-compose)
|
- Notification Discord `@everyone` en cas de stock détecté avec modèle, prix, et lien
|
||||||
|
- Notification Discord silencieuse en cas d'absence de stock détécté
|
||||||
|
- Choix de la fréquence de la vérification
|
||||||
|
- Choix du modèle à surveiller
|
||||||
|
|
||||||
|
<img src="https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/nvbot_schematics.png" align="center">
|
||||||
|
|
||||||
|
## Installation sans le dépot avec docker compose
|
||||||
|
|
||||||
|
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/)
|
||||||
|
|
||||||
|
**Configuration**
|
||||||
|
|
||||||
|
- Créez un dossier `nvidia-stock-bot`
|
||||||
|
- Créez le fichier `compose.yaml` dans ce dossier avec la configuration ci-dessous :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
nvidia-stock-bot:
|
||||||
|
image: git.djeex.fr/djeex/nvidia-stock-bot:latest
|
||||||
|
container_name: nvidia-stock-bot
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- DISCORD_WEBHOOK_URL= # URL de votre webhook Discord
|
||||||
|
- REFRESH_TIME= # Durée de rafraichissement du script en secondes
|
||||||
|
- API_URL_SKU= # API listant le produit par exemple https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia&gpu=RTX%205090
|
||||||
|
- API_URL_STOCK= # API appelant le stock sans préciser la valeur du sku, par exemple https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=
|
||||||
|
- PRODUCT_URL= # URL d'achat du GPU
|
||||||
|
- PRODUCT_NAME= # Le nom du GPU qui s'affiche dans les notifications
|
||||||
|
- TEST_MODE= # true pour tester les notifications discord. false par défaut.
|
||||||
|
- PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
|
||||||
|
command: python nvidia-stock-bot.py # Lance le script Python au démarrage du conteneur
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lancer l'image**
|
||||||
|
|
||||||
|
Rendez-vous dans le dossier `nvidia-stock-bot` et lancez le conteneur :
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
**Voir les logs pour vérifier le bon fonctionnement**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker logs -f nvidia-stock-bot
|
||||||
|
```
|
||||||
|
|
||||||
## Installation avec le dépot
|
## Installation avec le dépot
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### Pré-requis
|
**Pré-requis**
|
||||||
- Git
|
- [Git](https://git-scm.com/docs)
|
||||||
- Docker
|
- [Docker](https://docs.docker.com/engine/install/)
|
||||||
|
|
||||||
### Cloner et paramétrer
|
**Cloner et paramétrer**
|
||||||
|
|
||||||
Clonez le 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Rendez vous dans le dossier `nvidia-stock-bot` et compilez l'image docker :
|
- 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 .
|
||||||
```
|
```
|
||||||
|
|
||||||
Rendez-vous dans le dossier `nvidia-stock-bot/docker` et éditez le fichier `.env` avec :
|
- Puis rendez-vous dans le dossier `nvidia-stock-bot/docker` et éditez le fichier `.env` avec :
|
||||||
- l'url de votre webhook discord
|
- l'url de votre webhook discord
|
||||||
- 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)
|
- les différents liens API et produits
|
||||||
|
- 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)
|
||||||
|
|
||||||
### Lancer l'image
|
**Lancer l'image**
|
||||||
|
|
||||||
Rendez-vous dans le dossier `nvidia-stock-bot/docker` et lancez le conteneur :
|
Rendez-vous dans le dossier `nvidia-stock-bot/docker` et lancez le conteneur :
|
||||||
```sh
|
```sh
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Voir les logs pour vérifier le bon fonctionnement
|
**Voir les logs pour vérifier le bon fonctionnement**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker logs -f nvidia-stock-bot
|
docker logs -f nvidia-stock-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation sans le dépot avec docker compose
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: "3.8"
|
|
||||||
services:
|
|
||||||
nvidia-stock-bot:
|
|
||||||
image: git.djeex.fr/djeex/nvidia-stock-bot:wip
|
|
||||||
container_name: nvidia-stock-bot
|
|
||||||
restart: always # Le conteneur redémarrera automatiquement en cas d'échec
|
|
||||||
environment:
|
|
||||||
- DISCORD_WEBHOOK_URL= # URL de votre webhook Discord
|
|
||||||
- REFRESH_TIME= # Durée de rafraichissement du script en secondes
|
|
||||||
- GPU_TARGETS= #SKU
|
|
||||||
- API_URL= #URL de l'API
|
|
||||||
- PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
|
|
||||||
command: python nvidia-stock-bot.py # Lance le script Python au démarrage du conteneur
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation avec Python
|
## Installation avec Python
|
||||||
|
|
||||||
Vous trouverez ci-dessous comment exécuter directement le script Python. Avec cette solution, le bot s'arretera si vous fermez votre terminal.
|
Vous trouverez ci-dessous comment exécuter directement le script Python. Avec cette solution, le bot s'arretera si vous fermez votre terminal.
|
||||||
|
|
||||||
### Pré-requis
|
**Pré-requis**
|
||||||
|
|
||||||
- Python 3.11 ou plus
|
- Python 3.11 ou plus
|
||||||
- requests : `pip install requests`
|
- requests : `pip install requests`
|
||||||
|
|
||||||
### Configuration
|
**Configuration**
|
||||||
|
|
||||||
- Créez un environnement virtuel (exemple : `python3 -m venv nom_de_l_environnement` )
|
- Créez un environnement virtuel (exemple : `python3 -m venv nom_de_l_environnement` )
|
||||||
- Créez un dossier et aller dedans
|
- Créez un dossier et aller dedans
|
||||||
@ -102,9 +139,28 @@ Vous trouverez ci-dessous comment exécuter directement le script Python. Avec c
|
|||||||
```sh
|
```sh
|
||||||
export DISCORD_WEBHOOK_URL="https://votre_url_discord"
|
export DISCORD_WEBHOOK_URL="https://votre_url_discord"
|
||||||
export REFRESH_TIME="60"
|
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
|
- Lancez le script
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python nvidia-stock-bot.py
|
python nvidia-stock-bot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Captures d'écran
|
||||||
|
|
||||||
|
<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 - captures">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Contributeurs
|
||||||
|
|
||||||
|
On remercie pour leurs contributions :
|
||||||
|
|
||||||
|
- Djeex
|
||||||
|
- KevOut
|
||||||
|
- Extreme2pac
|
BIN
assets/img/RTX5000.jpg
Normal file
BIN
assets/img/RTX5000.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
assets/img/ds_wh_pp.jpg
Normal file
BIN
assets/img/ds_wh_pp.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 322 KiB |
BIN
assets/img/nvbot_schematics.png
Normal file
BIN
assets/img/nvbot_schematics.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 499 KiB |
BIN
assets/img/nvidia-stock-bot-discord.png
Normal file
BIN
assets/img/nvidia-stock-bot-discord.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
BIN
assets/img/nvidia-stock-bot-logo.png
Normal file
BIN
assets/img/nvidia-stock-bot-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
10
docker/.env
10
docker/.env
@ -1,4 +1,6 @@
|
|||||||
DS_HOOK="votre url du webhook discord"
|
DS_HOOK= #votre url du webhook Discord
|
||||||
FREQ=60 #frequence de rafraichissement en secondes
|
FREQ= #frequence de rafraichissement en secondes
|
||||||
GPU=
|
API_URL_SKU= # API listant le produit par exemple https://api.nvidia.partners/edge/product/search?page=1&limit=100&locale=fr-fr&Manufacturer=Nvidia&gpu=RTX%205090
|
||||||
URL=""
|
API_URL_STOCK= # API appelant le stock sans préciser la valeur du sku, par exemple https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=
|
||||||
|
PRODUCT_URL= # URL d'achat du GPU
|
||||||
|
PRODUCT_NAME= #Le nom du GPU qui s'affiche dans les notifications
|
||||||
|
@ -9,7 +9,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- DISCORD_WEBHOOK_URL=${DS_HOOK}
|
- DISCORD_WEBHOOK_URL=${DS_HOOK}
|
||||||
- REFRESH_TIME=${FREQ}
|
- REFRESH_TIME=${FREQ}
|
||||||
- GPU_TARGETS=${GPU} #SKU
|
- API_URL_SKU=${API_URL_SKU}
|
||||||
- API_URL=${URL} #URL de l'API
|
- API_URL_STOCK=${API_URL_STOCK}
|
||||||
- PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
|
- PYTHONUNBUFFERED=1 # Permet d'afficher les logs en temps réel
|
||||||
command: python nvidia-stock-bot.py # Lance le script Python
|
command: python nvidia-stock-bot.py # Lance le script Python
|
@ -2,42 +2,59 @@ import requests
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from requests.adapters import HTTPAdapter, Retry
|
||||||
|
|
||||||
# Configuration du logger
|
# 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("Démarrage du script")
|
logging.info("Démarrage du script")
|
||||||
|
|
||||||
# Récupération des variables d'environnement
|
# Récupération des variables d'environnement
|
||||||
try:
|
try:
|
||||||
DISCORD_WEBHOOK_URL = os.environ['DISCORD_WEBHOOK_URL']
|
DISCORD_WEBHOOK_URL = os.environ['DISCORD_WEBHOOK_URL']
|
||||||
API_URL = os.environ['API_URL']
|
API_URL_SKU = os.environ['API_URL_SKU']
|
||||||
GPU_TARGETS = os.environ['GPU_TARGETS'].split(",") # Séparer en liste
|
API_URL_STOCK = os.environ['API_URL_STOCK']
|
||||||
GPU_TARGETS = [gpu.strip() for gpu in GPU_TARGETS] # Nettoyer les espaces
|
|
||||||
REFRESH_TIME = int(os.environ['REFRESH_TIME']) # Convertir en entier
|
REFRESH_TIME = int(os.environ['REFRESH_TIME']) # Convertir en entier
|
||||||
|
TEST_MODE = os.environ.get('TEST_MODE', 'False').lower() == 'true'
|
||||||
|
PRODUCT_URL = os.environ['PRODUCT_URL']
|
||||||
|
PRODUCT_NAME = os.environ['PRODUCT_NAME']
|
||||||
|
|
||||||
|
# Regex pour extraire l'ID et le token
|
||||||
|
match = re.search(r'/(\d+)/(.*)', DISCORD_WEBHOOK_URL)
|
||||||
|
if match:
|
||||||
|
webhook_id = match.group(1)
|
||||||
|
webhook_token = match.group(2)
|
||||||
|
|
||||||
|
# Masquer derniers caractères de l'ID
|
||||||
|
masked_webhook_id = webhook_id[:len(webhook_id) - 10] + '*' * 10
|
||||||
|
|
||||||
|
# Masquer derniers caractères du token
|
||||||
|
masked_webhook_token = webhook_token[:len(webhook_token) - 120] + '*' * 10
|
||||||
|
|
||||||
|
# Reconstruction de l'url masquée
|
||||||
|
wh_masked_url = f"https://discord.com/api/webhooks/{masked_webhook_id}/{masked_webhook_token}"
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logging.error(f"Variable d'environnement manquante : {e}")
|
logging.error(f"Variable d'environnement manquante : {e}")
|
||||||
exit(1) # Quitter le script proprement en cas d'erreur
|
exit(1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.error("REFRESH_TIME doit être un entier valide.")
|
logging.error("REFRESH_TIME doit être un entier valide.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Afficher les valeurs des variables d'environnement
|
# Affichage des URLs et configurations
|
||||||
print(f"url du webhook Discord: {DISCORD_WEBHOOK_URL}")
|
logging.info(f"GPU: {PRODUCT_NAME}")
|
||||||
print(f"url de l'API: {API_URL}")
|
logging.info(f"URL Webhook Discord: {wh_masked_url}")
|
||||||
print(f"GPU recherché: {GPU_TARGETS}")
|
logging.info(f"URL API SKU: {API_URL_SKU}")
|
||||||
print(f"Temps d'actualisation (en secondes) : {REFRESH_TIME}")
|
logging.info(f"URL API Stock: {API_URL_STOCK}")
|
||||||
|
logging.info(f"URL produit: {PRODUCT_URL}")
|
||||||
|
logging.info(f"Temps d'actualisation: {REFRESH_TIME} secondes")
|
||||||
|
logging.info(f"Mode Test: {TEST_MODE}")
|
||||||
|
|
||||||
# L’URL de l’API (exemple)
|
|
||||||
#API_URL = "https://api.store.nvidia.com/partner/v1/feinventory?locale=fr-fr&skus=5090LDLCFE"
|
|
||||||
|
|
||||||
# GPUs à surveiller
|
# Entêtes HTTP
|
||||||
#GPU_TARGETS = ["5090LDLCFE_FR"]
|
|
||||||
|
|
||||||
# Entêtes HTTP pour la requête
|
|
||||||
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) "
|
||||||
@ -56,84 +73,248 @@ HEADERS = {
|
|||||||
"Sec-GPC": "1",
|
"Sec-GPC": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Dictionnaire stockant l'état de stock
|
# Session avec retries
|
||||||
stock_status = {gpu.upper(): False for gpu in GPU_TARGETS}
|
|
||||||
|
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
|
retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
|
||||||
|
session.mount('https://', HTTPAdapter(max_retries=retries))
|
||||||
|
|
||||||
|
# Stockage de l'état des stocks
|
||||||
|
global_stock_status = {}
|
||||||
|
|
||||||
|
# Stocke le dernier SKU connu
|
||||||
|
last_sku = None
|
||||||
|
first_run = True # Before calling check_rtx_50_founders
|
||||||
|
|
||||||
|
# Notifications Discord
|
||||||
|
def send_discord_notification(gpu_name: str, product_link: str, products_price: str):
|
||||||
|
|
||||||
|
# Récupérer le timestamp UNIX actuel
|
||||||
|
timestamp_unix = int(time.time())
|
||||||
|
|
||||||
|
if TEST_MODE:
|
||||||
|
logging.info(f"[TEST MODE] Notification Discord: {gpu_name} disponible !")
|
||||||
|
return
|
||||||
|
|
||||||
def send_discord_notification(gpu_name: str, product_link: str):
|
|
||||||
"""Envoie une notification Discord avec un embed via un webhook."""
|
|
||||||
embed = {
|
embed = {
|
||||||
"title": f"🚀 {gpu_name} en stock !",
|
"title": f"🚀 {PRODUCT_NAME} EN STOCK !",
|
||||||
"description": f":point_right: **[Achetez ici](https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&gpu=RTX%205090,RTX%205080&manufacturer=NVIDIA)**",
|
"color": 3066993,
|
||||||
"color": 3066993, # Couleur verte
|
"thumbnail": {
|
||||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()),
|
"url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg"
|
||||||
#"thumbnail": {
|
},
|
||||||
# "url": "https://www.nvidia.com/content/dam/en-zz/Solutions/geforce/graphic-cards/50-series/rtx-5090/geforce-rtx-5090-learn-more-og-1200x630.jpg"
|
"author": {
|
||||||
#}
|
"name": "Nvidia Founder Editions"
|
||||||
|
},
|
||||||
|
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Prix",
|
||||||
|
"value": f"`{products_price}€`",
|
||||||
|
"inline": True
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Heure",
|
||||||
|
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
|
||||||
|
"inline": True
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"description": f"**:point_right: [Acheter maintenant]({product_link})**",
|
||||||
|
"footer": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload = {"content": "@everyone", "username": "NviBot", "avatar_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg", "embeds": [embed]}
|
||||||
|
try:
|
||||||
|
response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
|
||||||
|
if response.status_code == 204:
|
||||||
|
logging.info("✅ Notification envoyée sur Discord.")
|
||||||
|
else:
|
||||||
|
logging.error(f"❌ Erreur Webhook : {response.status_code} - {response.text}")
|
||||||
|
except Exception as 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):
|
||||||
|
|
||||||
|
# Récupérer le timestamp UNIX actuel
|
||||||
|
timestamp_unix = int(time.time())
|
||||||
|
|
||||||
|
if TEST_MODE:
|
||||||
|
logging.info(f"[TEST MODE] Notification Discord: {gpu_name} hors stock !")
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = {
|
||||||
|
"title": f"❌ {PRODUCT_NAME} n'est plus en stock",
|
||||||
|
"color": 15158332, # Rouge pour hors stock
|
||||||
|
"thumbnail": {
|
||||||
|
"url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/RTX5000.jpg"
|
||||||
|
},
|
||||||
|
"url": f"{product_link}",
|
||||||
|
"author": {
|
||||||
|
"name": "Nvidia Founder Editions"
|
||||||
|
},
|
||||||
|
|
||||||
|
"footer": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Heure",
|
||||||
|
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
|
||||||
|
"inline": True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
payload = {"username": "NviBot", "avatar_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg", "embeds": [embed]}
|
||||||
|
try:
|
||||||
|
response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
|
||||||
|
if response.status_code == 204:
|
||||||
|
logging.info("✅ Notification 'hors stock' envoyée sur Discord.")
|
||||||
|
else:
|
||||||
|
logging.error(f"❌ Erreur Webhook : {response.status_code} - {response.text}")
|
||||||
|
except Exception as 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):
|
||||||
|
|
||||||
|
# Récupérer le timestamp UNIX actuel
|
||||||
|
timestamp_unix = int(time.time())
|
||||||
|
|
||||||
|
if TEST_MODE:
|
||||||
|
logging.info(f"[TEST MODE] Changement de SKU détecté : {old_sku} → {new_sku}")
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = {
|
||||||
|
"title": f"🔄 {PRODUCT_NAME} Changement de SKU détecté",
|
||||||
|
"url": f"{product_link}",
|
||||||
|
"description": f"**Ancien SKU** : `{old_sku}`\n**Nouveau SKU** : `{new_sku}`",
|
||||||
|
"color": 16776960, # Jaune
|
||||||
|
|
||||||
|
"footer": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Heure",
|
||||||
|
"value": f"<t:{timestamp_unix}:d> <t:{timestamp_unix}:T>",
|
||||||
|
"inline": True
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"content": "@everyone",
|
"content": "@everyone ⚠️ Potentiel drop imminent !",
|
||||||
"username": "Nvidia Bot",
|
"username": "NviBot",
|
||||||
|
"avatar_url": "https://git.djeex.fr/Djeex/nvidia-stock-bot/raw/branch/main/assets/img/ds_wh_pp.jpg",
|
||||||
"embeds": [embed]
|
"embeds": [embed]
|
||||||
}
|
}
|
||||||
|
|
||||||
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("✅ Embed envoyé sur Discord.")
|
logging.info("✅ Notification de changement de SKU envoyée sur Discord.")
|
||||||
else:
|
else:
|
||||||
logging.error(f"❌ Erreur d'envoi du webhook : {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"🚨 Erreur lors de l'envoi du webhook : {e}")
|
logging.error(f"🚨 Erreur lors de l'envoi du webhook : {e}")
|
||||||
|
|
||||||
|
# Recherche du stock
|
||||||
def check_rtx_50_founders():
|
def check_rtx_50_founders():
|
||||||
"""Vérifie l'état de stock des GPU Founders Edition et notifie Discord si un GPU repasse en stock."""
|
global global_stock_status, last_sku, first_run
|
||||||
|
|
||||||
|
# Appel vers l'API produit pour récupérer le sku et l'upc
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = session.get(API_URL_SKU, headers=HEADERS, timeout=10)
|
||||||
|
logging.info(f"Réponse de l'API SKU : {response.status_code}")
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.error(f"Erreur API SKU : {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
product_details = data['searchedProducts']['productDetails'][0]
|
||||||
|
product_sku = product_details['productSKU']
|
||||||
|
|
||||||
|
# Vérifier si c'est la première exécution
|
||||||
|
if last_sku is not None and product_sku != last_sku:
|
||||||
|
if not first_run: # Évite d'envoyer une notification au premier appel
|
||||||
|
product_link = PRODUCT_URL
|
||||||
|
logging.warning(f"⚠️ SKU modifié : {last_sku} → {product_sku}")
|
||||||
|
send_sku_change_notification(last_sku, product_sku, product_link)
|
||||||
|
|
||||||
|
# Mettre à jour le SKU stocké
|
||||||
|
last_sku = product_sku
|
||||||
|
first_run = False # Désactive la protection après la première exécution
|
||||||
|
|
||||||
|
product_details = data['searchedProducts']['productDetails'][0]
|
||||||
|
product_sku = product_details['productSKU']
|
||||||
|
product_upc = product_details.get('productUPC', "")
|
||||||
|
if not isinstance(product_upc, list):
|
||||||
|
product_upc = [product_upc]
|
||||||
|
|
||||||
|
# Construction de l'url de l'API de stock et appel pour vérifier le statut
|
||||||
|
API_URL = API_URL_STOCK + product_sku
|
||||||
|
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"Réponse de l'API : {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"Erreur lors de l'appel API : {e}")
|
logging.error(f"Erreur API Stock : {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
products = data.get("listMap", [])
|
products = data.get("listMap", [])
|
||||||
|
products_price = 'Prix non disponible' # Valeur par défaut
|
||||||
|
|
||||||
|
# Vérification de la liste des produits et récupération du prix
|
||||||
|
if isinstance(products, list) and len(products) > 0:
|
||||||
|
for product in products:
|
||||||
|
price = product.get("price", 'Prix non disponible')
|
||||||
|
if price != 'Prix non disponible':
|
||||||
|
products_price = price # Utiliser le prix trouvé
|
||||||
|
break # Sortir dès qu'on trouve un prix
|
||||||
|
else:
|
||||||
|
logging.error("La liste des produits est vide ou mal formée.")
|
||||||
|
|
||||||
found_in_stock = set()
|
found_in_stock = set()
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
if is_active and any(target.upper() in gpu_name for target in product_upc):
|
||||||
if is_active:
|
|
||||||
if any(target.upper() in gpu_name for target in GPU_TARGETS):
|
|
||||||
found_in_stock.add(gpu_name)
|
found_in_stock.add(gpu_name)
|
||||||
|
|
||||||
for gpu in GPU_TARGETS:
|
for gpu in product_upc:
|
||||||
gpu_upper = gpu.upper()
|
gpu_upper = gpu.upper()
|
||||||
currently_in_stock = (gpu_upper in found_in_stock)
|
currently_in_stock = gpu_upper in found_in_stock
|
||||||
previously_in_stock = stock_status[gpu_upper]
|
previously_in_stock = global_stock_status.get(gpu_upper, False)
|
||||||
|
|
||||||
if currently_in_stock and not previously_in_stock:
|
if currently_in_stock and not previously_in_stock:
|
||||||
for product in products:
|
product_link = PRODUCT_URL
|
||||||
product_name = product.get("fe_sku", "").upper()
|
send_discord_notification(gpu_upper, product_link, products_price)
|
||||||
if product_name == gpu_upper:
|
global_stock_status[gpu_upper] = True
|
||||||
real_gpu_name = product.get("fe_sku", "Inconnu")
|
logging.info(f"{gpu} est maintenant en stock!")
|
||||||
product_link = "https://marketplace.nvidia.com/fr-fr/consumer/graphics-cards/?locale=fr-fr&page=1&limit=12&gpu=RTX%205090,RTX%205080"
|
elif not currently_in_stock and previously_in_stock:
|
||||||
send_discord_notification(real_gpu_name, product_link)
|
product_link = PRODUCT_URL
|
||||||
|
send_out_of_stock_notification(gpu_upper, product_link, products_price)
|
||||||
stock_status[gpu_upper] = True
|
global_stock_status[gpu_upper] = False
|
||||||
print(f"{gpu} est maintenant en stock!")
|
|
||||||
|
|
||||||
elif (not currently_in_stock) and previously_in_stock:
|
|
||||||
logging.info(f"{gpu} n'est plus en stock.")
|
logging.info(f"{gpu} n'est plus en stock.")
|
||||||
stock_status[gpu_upper] = False
|
|
||||||
print(f"{gpu} est hors stock !")
|
|
||||||
|
|
||||||
elif not currently_in_stock:
|
elif currently_in_stock and previously_in_stock:
|
||||||
print(f"{gpu} est actuellement hors 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()
|
||||||
|
Reference in New Issue
Block a user