mirror of
https://github.com/skylanix/MamieHenriette.git
synced 2026-02-06 14:50:34 +01:00
Merge pull request #1 from skylanix/HumbleBundle
Ajout de Humble Bundle, refonte de Docker ainsi que de la documentation
This commit is contained in:
@@ -1,8 +1,3 @@
|
||||
# Configuration Discord Bot
|
||||
TOKEN=votre_token_discord
|
||||
STATUS=online
|
||||
INTERVAL=3600
|
||||
|
||||
# Configuration Zabbix (optionnel)
|
||||
ENABLE_ZABBIX=false
|
||||
ZABBIX_SERVER=zabbix-server.example.com
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -3,9 +3,12 @@ FROM debian:trixie-slim
|
||||
WORKDIR /app
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV LANG=fr_FR.UTF-8
|
||||
ENV LC_ALL=fr_FR.UTF-8
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
apt-utils \
|
||||
locales \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
@@ -14,16 +17,21 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& dpkg -i zabbix-release_latest_7.4+debian12_all.deb \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends zabbix-agent2 \
|
||||
&& sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen \
|
||||
&& locale-gen \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm zabbix-release_latest_7.4+debian12_all.deb
|
||||
|
||||
COPY requirements.txt .
|
||||
COPY bot.py .
|
||||
COPY statuts.txt .
|
||||
COPY run-web.py .
|
||||
COPY ./webapp ./webapp
|
||||
COPY ./discordbot ./discordbot
|
||||
COPY ./database ./database
|
||||
COPY zabbix_agent2.conf /etc/zabbix/zabbix_agent2.conf
|
||||
COPY start.sh /start.sh
|
||||
|
||||
RUN pip3 install --no-cache-dir --break-system-packages --root-user-action=ignore -r requirements.txt && \
|
||||
RUN python3 -m venv /app/venv && \
|
||||
/app/venv/bin/pip install --no-cache-dir -r requirements.txt && \
|
||||
chmod +x /start.sh
|
||||
|
||||
CMD ["/start.sh"]
|
||||
273
README.md
273
README.md
@@ -1,175 +1,188 @@
|
||||
# MamieHenriette 👵
|
||||
|
||||
# 👵 Mamie Henriette - Discord Status Bot 🤖
|
||||
**Bot multi-plateformes pour Discord, Twitch et YouTube Live**
|
||||
|
||||
## 📖 Description
|
||||
|
||||
Mamie Henriette est un bot Discord intelligent qui change automatiquement de statut, surveillant et gérant votre serveur avec une touche d'humour et de caractère.
|
||||
## Vue d'ensemble
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
Mamie Henriette est un bot intelligent open-source développé spécifiquement pour les communautés de [STEvE](https://www.youtube.com/@STEvE_YT) sur YouTube, [Twitch](https://www.twitch.tv/steve_yt) et Discord.
|
||||
|
||||
- Changement cyclique automatique des statuts
|
||||
- Configuration flexible via variables d'environnement
|
||||
- Gestion des erreurs et logging
|
||||
- Support multi-statuts Discord
|
||||
- Déploiement simplifié avec Docker
|
||||
- 📊 Surveillance optionnelle avec Zabbix
|
||||
> ⚠️ **Statut** : En cours de développement
|
||||
|
||||
## 🛠 Prérequis
|
||||
### Caractéristiques principales
|
||||
|
||||
- Docker et Docker Compose
|
||||
- Compte Discord et Token du bot
|
||||
- (Optionnel) Serveur Zabbix pour la surveillance
|
||||
- Interface web d'administration complète
|
||||
- Gestion multi-plateformes (Discord, Twitch, YouTube Live)
|
||||
- Système de notifications automatiques
|
||||
- Base de données intégrée pour la persistance
|
||||
- Surveillance optionnelle avec Zabbix *(non testée)*
|
||||
|
||||
## 📦 Installation
|
||||
## Fonctionnalités
|
||||
|
||||
### Discord
|
||||
- **Statuts dynamiques** : Rotation automatique des humeurs (10 min)
|
||||
- **Notifications Humble Bundle** : Surveillance et alertes automatiques (30 min)
|
||||
- **Commandes personnalisées** : Gestion via interface web
|
||||
- **Modération** : Outils intégrés
|
||||
|
||||
### Twitch *(en développement)*
|
||||
- **Chat bot** : Commandes et interactions
|
||||
- **Événements live** : Notifications de stream
|
||||
|
||||
### YouTube Live *(en développement)*
|
||||
- **Chat bot** : Modération et commandes
|
||||
- **Événements** : Notifications de diffusion
|
||||
|
||||
### Interface d'administration
|
||||
- **Dashboard** : Vue d'ensemble et statistiques
|
||||
- **Configuration** : Tokens, paramètres des plateformes
|
||||
- **Gestion des humeurs** : Création et modification des statuts
|
||||
- **Commandes** : Édition des commandes personnalisées
|
||||
- **Modération** : Outils de gestion communautaire
|
||||
|
||||
### Surveillance
|
||||
- **Zabbix Agent 2** : Monitoring avancé *(non testé)*
|
||||
- **Métriques** : Santé du bot et uptime
|
||||
|
||||
## Installation
|
||||
|
||||
### Prérequis
|
||||
- [Docker Engine](https://docs.docker.com/engine/install/) ou [Docker Desktop](https://docs.docker.com/desktop/)
|
||||
- Token Discord pour le bot
|
||||
|
||||
### Démarrage rapide
|
||||
|
||||
1. Clonez le dépôt
|
||||
```bash
|
||||
git clone https://git.favrep.ch/lapatatedouce/MamieHenrriette
|
||||
cd MamieHenrriette
|
||||
# 1. Cloner le projet
|
||||
git clone https://github.com/skylanix/MamieHenriette.git
|
||||
```
|
||||
|
||||
2. Copiez le fichier de configuration
|
||||
```bash
|
||||
cp .env.example .env
|
||||
cd MamieHenriette
|
||||
```
|
||||
|
||||
3. Éditez le fichier `.env` avec vos paramètres
|
||||
```bash
|
||||
nano .env
|
||||
# 2. Lancer avec Docker
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
4. Démarrez le conteneur Docker
|
||||
### Configuration
|
||||
|
||||
1. **Interface web** : Accédez à http://localhost
|
||||
2. **Token Discord** : Section "Configurations"
|
||||
3. **Humeurs** : Définir les statuts du bot
|
||||
4. **Canaux** : Configurer les notifications
|
||||
|
||||
> ⚠️ **Important** : Après avoir configuré le token Discord, les humeurs et autres fonctionnalités via l'interface web, **redémarrez le conteneur** pour que les changements soient pris en compte :
|
||||
> ```bash
|
||||
> docker compose restart mamiehenriette
|
||||
> ```
|
||||
|
||||
### Commandes Docker utiles
|
||||
|
||||
**Mode développement (avec logs):**
|
||||
```bash
|
||||
docker-compose up --build
|
||||
# Logs en temps réel
|
||||
docker compose logs -f mamiehenriette
|
||||
```
|
||||
|
||||
**Mode production (en arrière-plan):**
|
||||
```bash
|
||||
docker-compose up --build -d
|
||||
# Logs d'un conteneur en cours d'exécution
|
||||
docker logs -f mamiehenriette
|
||||
```
|
||||
|
||||
**Voir les logs:**
|
||||
```bash
|
||||
docker-compose logs -f discord-bot
|
||||
# Redémarrer
|
||||
docker compose restart mamiehenriette
|
||||
```
|
||||
|
||||
**Arrêter le conteneur:**
|
||||
```bash
|
||||
docker-compose down
|
||||
# Arrêter
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
## Configuration avancée
|
||||
|
||||
### Variables d'environnement principales
|
||||
### Variables d'environnement
|
||||
|
||||
- `TOKEN`: Votre token Discord (obligatoire)
|
||||
- `STATUS`: Statut initial (défaut: online)
|
||||
- `INTERVAL`: Intervalle de changement de statut (défaut: 3600 secondes)
|
||||
```yaml
|
||||
environment:
|
||||
- ENABLE_ZABBIX=false # Surveillance (non testée)
|
||||
- ZABBIX_SERVER=localhost
|
||||
- ZABBIX_HOSTNAME=MamieHenriette
|
||||
```
|
||||
|
||||
### 📊 Configuration Zabbix (optionnelle)
|
||||
### Interface d'administration
|
||||
|
||||
- `ENABLE_ZABBIX`: Activer la surveillance Zabbix (défaut: false)
|
||||
- `ZABBIX_SERVER`: Adresse du serveur Zabbix
|
||||
- `ZABBIX_HOSTNAME`: Nom d'hôte pour identifier le bot
|
||||
- `ZABBIX_PORT`: Port d'exposition Zabbix (défaut: 10050)
|
||||
| Section | Fonction |
|
||||
|---------|----------|
|
||||
| **Configurations** | Tokens et paramètres généraux |
|
||||
| **Humeurs** | Gestion des statuts Discord |
|
||||
| **Commandes** | Commandes personnalisées |
|
||||
| **Modération** | Outils de gestion |
|
||||
|
||||
#### Métriques surveillées par Zabbix
|
||||
## Architecture du projet
|
||||
|
||||
- Statut du bot Discord
|
||||
- Temps de fonctionnement (uptime)
|
||||
- Utilisation mémoire
|
||||
- Erreurs et avertissements dans les logs
|
||||
- Connectivité à Discord
|
||||
### Structure des modules
|
||||
|
||||
#### Activation de Zabbix
|
||||
```
|
||||
├── database/ # Couche données
|
||||
│ ├── models.py # Modèles ORM
|
||||
│ ├── helpers.py # Utilitaires BDD
|
||||
│ └── schema.sql # Structure initiale
|
||||
│
|
||||
├── discordbot/ # Module Discord
|
||||
│ └── __init__.py # Bot et handlers
|
||||
│
|
||||
└── webapp/ # Interface d'administration
|
||||
├── static/ # Assets statiques
|
||||
├── templates/ # Vues HTML
|
||||
└── *.py # Contrôleurs par section
|
||||
```
|
||||
|
||||
Dans votre fichier `.env` :
|
||||
### Composants principaux
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---------|------|
|
||||
| `run-web.py` | Point d'entrée principal |
|
||||
| `start.sh` | Script de démarrage Docker |
|
||||
| `docker-compose.yml` | Configuration des services |
|
||||
| `requirements.txt` | Dépendances Python |
|
||||
|
||||
## Spécifications techniques
|
||||
|
||||
### Base de données (SQLite)
|
||||
- **Configuration** : Paramètres et tokens
|
||||
- **Humeur** : Statuts Discord rotatifs
|
||||
- **Message** : Messages périodiques *(planifié)*
|
||||
- **GameBundle** : Historique Humble Bundle
|
||||
|
||||
### Architecture multi-thread
|
||||
- **Thread 1** : Interface web Flask (port 5000)
|
||||
- **Thread 2** : Bot Discord et tâches automatisées
|
||||
|
||||
### Dépendances principales
|
||||
```
|
||||
discord.py # API Discord
|
||||
flask # Interface web
|
||||
requests # Client HTTP
|
||||
waitress # Serveur WSGI
|
||||
```
|
||||
|
||||
## Développement
|
||||
|
||||
### Installation locale
|
||||
```bash
|
||||
ENABLE_ZABBIX=true
|
||||
ZABBIX_SERVER=votre-serveur-zabbix.com
|
||||
ZABBIX_HOSTNAME=MamieHenriette
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python run-web.py
|
||||
```
|
||||
|
||||
### Fichier `statuts.txt`
|
||||
|
||||
Créez un fichier `statuts.txt` avec vos statuts, un par ligne.
|
||||
|
||||
Exemple :
|
||||
```
|
||||
Surveiller le serveur
|
||||
Mamie est là !
|
||||
En mode supervision
|
||||
```
|
||||
|
||||
## 📋 Dépendances
|
||||
|
||||
- discord.py==2.3.2
|
||||
- python-dotenv==1.0.0
|
||||
### Contribution
|
||||
1. Fork du projet
|
||||
2. Branche feature
|
||||
3. Pull Request
|
||||
|
||||
---
|
||||
|
||||
# 🖥️ Installation environnement de développement
|
||||
|
||||
## Installation des dépendances système
|
||||
|
||||
```bash
|
||||
sudo apt install python3 python3-pip
|
||||
```
|
||||
|
||||
## Création de l'environnement Python local
|
||||
|
||||
Dans le dossier du projet :
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
```
|
||||
|
||||
Puis activer l'environnement :
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
## Installation des dépendances Python
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Exécution
|
||||
|
||||
```bash
|
||||
python3 run-web.py
|
||||
```
|
||||
|
||||
# Structure du projet
|
||||
|
||||
```
|
||||
.
|
||||
|-- database : module de connexion à la BDD
|
||||
| |-- __init.py__
|
||||
| |-- models.py : contient les pojo représentant chaque table
|
||||
| |-- schema.sql : contient un scrip sql d'initialisation de la bdd, celui-ci doit être réentrant
|
||||
|
|
||||
|-- discordbot : module de connexion à discord
|
||||
| |-- __init.py__
|
||||
|
|
||||
|-- webapp : module du site web d'administration
|
||||
| |-- static : Ressource fixe directement accessible par le navigateir
|
||||
| | |-- css
|
||||
| | |-- ...
|
||||
| |
|
||||
| |-- template : Fichier html
|
||||
| | |-- template.html : structure globale du site
|
||||
| | |-- commandes.html : page de gestion des commandes
|
||||
| | |-- ...
|
||||
| |
|
||||
| |-- __init.py__
|
||||
| |-- index.py : controller de la page d'acceuil
|
||||
| |-- commandes.py : controller de gestion des commandes
|
||||
| |-- ...
|
||||
|
|
||||
|-- run-web.py : launcher
|
||||
```
|
||||
*Mamie Henriette vous surveille ! 👵👀*
|
||||
114
bot.py
114
bot.py
@@ -1,114 +0,0 @@
|
||||
import discord
|
||||
import json
|
||||
import random
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
||||
class DiscordStatusBot:
|
||||
def __init__(self):
|
||||
# Configuration des logs
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
|
||||
|
||||
# Charger la configuration à partir des variables d'environnement
|
||||
self.config = self.charger_configuration()
|
||||
if not self.config:
|
||||
logging.error("Impossible de charger la configuration")
|
||||
exit(1)
|
||||
|
||||
# Configuration des intents
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = False
|
||||
|
||||
# Création du client
|
||||
self.client = discord.Client(intents=intents)
|
||||
|
||||
# Événements
|
||||
self.setup_events()
|
||||
|
||||
def charger_configuration(self):
|
||||
"""Chargement de la configuration à partir des variables d'environnement"""
|
||||
config = {
|
||||
'token': os.getenv('TOKEN'),
|
||||
'status': os.getenv('STATUS', 'online'),
|
||||
'interval': int(os.getenv('INTERVAL', 60))
|
||||
}
|
||||
|
||||
if not config['token']:
|
||||
logging.error("Token non fourni")
|
||||
return None
|
||||
|
||||
return config
|
||||
|
||||
def charger_statuts(self):
|
||||
"""Chargement des statuts depuis le fichier"""
|
||||
try:
|
||||
with open('/app/statuts.txt', 'r', encoding='utf-8') as fichier:
|
||||
return [ligne.strip() for ligne in fichier.readlines() if ligne.strip()]
|
||||
except FileNotFoundError:
|
||||
logging.error("Fichier de statuts non trouvé")
|
||||
return []
|
||||
|
||||
def setup_events(self):
|
||||
"""Configuration des événements du bot"""
|
||||
@self.client.event
|
||||
async def on_ready():
|
||||
logging.info(f'Bot connecté : {self.client.user}')
|
||||
self.client.loop.create_task(self.changer_statut())
|
||||
|
||||
# Déplacez changer_statut à l'extérieur de setup_events
|
||||
async def changer_statut(self):
|
||||
"""Changement cyclique du statut"""
|
||||
await self.client.wait_until_ready()
|
||||
statuts = self.charger_statuts()
|
||||
if not statuts:
|
||||
logging.warning("Aucun statut disponible")
|
||||
return
|
||||
|
||||
# Mapping des status Discord
|
||||
status_mapping = {
|
||||
'online': discord.Status.online,
|
||||
'idle': discord.Status.idle,
|
||||
'dnd': discord.Status.dnd,
|
||||
'invisible': discord.Status.invisible
|
||||
}
|
||||
|
||||
# Récupérer le status depuis la configuration
|
||||
status_discord = status_mapping.get(self.config.get('status', 'online'), discord.Status.online)
|
||||
|
||||
while not self.client.is_closed():
|
||||
try:
|
||||
# Sélection du statut
|
||||
statut = random.choice(statuts)
|
||||
|
||||
# Changement de statut avec custom activity
|
||||
await self.client.change_presence(
|
||||
status=status_discord,
|
||||
activity=discord.CustomActivity(name=statut)
|
||||
)
|
||||
logging.info(f"Statut changé : {statut}")
|
||||
|
||||
# Délai entre les changements
|
||||
await asyncio.sleep(self.config.get('interval', 60))
|
||||
except Exception as e:
|
||||
logging.error(f"Erreur lors du changement de statut : {e}")
|
||||
await asyncio.sleep(30) # Attente en cas d'erreur
|
||||
|
||||
def executer(self):
|
||||
"""Lancement du bot"""
|
||||
try:
|
||||
if self.config and 'token' in self.config:
|
||||
self.client.run(self.config['token'])
|
||||
else:
|
||||
logging.error("Token non trouvé dans la configuration")
|
||||
except discord.LoginFailure:
|
||||
logging.error("Échec de connexion - Vérifiez votre token")
|
||||
except Exception as e:
|
||||
logging.error(f"Erreur lors du lancement : {e}")
|
||||
|
||||
def main():
|
||||
bot = DiscordStatusBot()
|
||||
bot.executer()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
32
database/helpers.py
Normal file
32
database/helpers.py
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
from database import db
|
||||
from database.models import Configuration
|
||||
|
||||
class ConfigurationHelper:
|
||||
def getValue(self, key:str) :
|
||||
conf = Configuration.query.filter_by(key=key).first()
|
||||
if conf == None:
|
||||
return None
|
||||
if (key.endswith('_enable')) :
|
||||
return conf.value in ['true', '1', 'yes', 'on']
|
||||
return conf.value
|
||||
|
||||
def getIntValue(self, key:str) :
|
||||
conf = Configuration.query.filter_by(key=key).first()
|
||||
if conf == None:
|
||||
return 0
|
||||
return int(conf.value)
|
||||
|
||||
def createOrUpdate(self, key:str, value) :
|
||||
conf = Configuration.query.filter_by(key=key).first()
|
||||
if (key.endswith('_enable')) :
|
||||
value = value in ['true', '1', 'yes', 'on']
|
||||
if conf :
|
||||
conf.value = value
|
||||
else :
|
||||
conf = Configuration(key = key, value = value)
|
||||
db.session.add(conf)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ class Humeur(db.Model):
|
||||
enable = db.Column(db.Boolean, default=True)
|
||||
text = db.Column(db.String(256))
|
||||
|
||||
class GameBundle(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(256))
|
||||
json = db.Column(db.String(1024))
|
||||
|
||||
class Message(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
enable = db.Column(db.Boolean, default=False)
|
||||
|
||||
@@ -4,15 +4,21 @@ CREATE TABLE IF NOT EXISTS `configuration` (
|
||||
`value` VARCHAR(512) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `game_bundle` (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
json VARCHAR(1024) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `humeur` (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`enable` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`text` VARCHAR(256) NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `message` (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`enable` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
`text` VARCHAR(256) NULL,
|
||||
periodicity INTEGER NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `humeur` (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`enable` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`text` VARCHAR(256) NULL
|
||||
);
|
||||
|
||||
@@ -1,34 +1,65 @@
|
||||
import random
|
||||
import discord
|
||||
# import os
|
||||
import logging
|
||||
import asyncio
|
||||
from webapp import webapp
|
||||
from database.models import Configuration, Humeur
|
||||
import datetime
|
||||
import discord
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import requests
|
||||
|
||||
from database import db
|
||||
from database.helpers import ConfigurationHelper
|
||||
from database.models import Configuration, GameBundle, Humeur
|
||||
|
||||
class DiscordBot(discord.Client):
|
||||
async def on_ready(self):
|
||||
logging.info(f'Logged in as {self.user} (ID: {self.user.id})')
|
||||
for c in self.get_all_channels() :
|
||||
logging.info(f'{c.id} {c.name}')
|
||||
|
||||
self.loop.create_task(self.updateStatus())
|
||||
# await self.get_channel(1123512494468644984).send("essai en python")
|
||||
self.loop.create_task(self.updateHumbleBundle())
|
||||
|
||||
async def updateStatus(self):
|
||||
# from database.models import Humeur
|
||||
humeur = random.choice(Humeur.query.all())
|
||||
if humeur != None:
|
||||
logging.info(f'changement de status {humeur.text}')
|
||||
await self.change_presence(status = discord.Status.online, activity = discord.CustomActivity(humeur.text))
|
||||
await asyncio.sleep(60)
|
||||
while not self.is_closed():
|
||||
humeurs = Humeur.query.all()
|
||||
if len(humeurs)>0 :
|
||||
humeur = random.choice(humeurs)
|
||||
if humeur != None:
|
||||
logging.info(f'changement de status {humeur.text}')
|
||||
await self.change_presence(status = discord.Status.online, activity = discord.CustomActivity(humeur.text))
|
||||
# 10 minutes TODO à rendre configurable
|
||||
await asyncio.sleep(10*60)
|
||||
|
||||
async def updateHumbleBundle(self):
|
||||
while not self.is_closed():
|
||||
if ConfigurationHelper().getValue('humble_bundle_enable') and ConfigurationHelper().getIntValue('humble_bundle_channel') != 0 :
|
||||
response = requests.get("http://hexas.shionn.org/humble-bundle/json", headers={ "Content-Type": "application/json" })
|
||||
if response.status_code == 200:
|
||||
bundle = response.json()
|
||||
if GameBundle.query.filter_by(id=bundle['id']).first() == None :
|
||||
choice = bundle['choices'][0]
|
||||
date = datetime.datetime.fromtimestamp(bundle['endDate']/1000,datetime.UTC).strftime("%d %B %Y")
|
||||
message = f"@here **Humble Bundle** propose un pack de jeu [{bundle['name']}]({bundle['url']}) contenant :\n"
|
||||
for game in choice["games"]:
|
||||
message += f"- {game}\n"
|
||||
message += f"Pour {choice['price']}€, disponible jusqu'au {date}."
|
||||
await self.get_channel(ConfigurationHelper().getIntValue('humble_bundle_channel')).send(message)
|
||||
db.session.add(GameBundle(id=bundle['id'], name=bundle['name'], json = json.dumps(bundle)))
|
||||
db.session.commit()
|
||||
else:
|
||||
logging.error(f"Erreur de connexion {response.status_code}")
|
||||
else:
|
||||
logging.info('Humble bundle est désactivé')
|
||||
# toute les 30 minutes
|
||||
await asyncio.sleep(30*60)
|
||||
|
||||
def begin(self) :
|
||||
with webapp.app_context():
|
||||
token = Configuration.query.filter_by(key='discord_token').first()
|
||||
if token :
|
||||
self.run(token.value)
|
||||
else :
|
||||
logging.error('pas de token on ne lance pas discord')
|
||||
token = Configuration.query.filter_by(key='discord_token').first()
|
||||
if token :
|
||||
self.run(token.value)
|
||||
else :
|
||||
logging.error('pas de token on ne lance pas discord')
|
||||
|
||||
intents = discord.Intents.default()
|
||||
bot = DiscordBot(intents=intents)
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
services:
|
||||
discord-bot:
|
||||
mamiehenriette:
|
||||
container_name: MamieHenriette
|
||||
build: .
|
||||
restart: on-failure
|
||||
environment:
|
||||
- TOKEN=VOTRE_TOKEN_DISCORD_ICI
|
||||
- STATUS=online
|
||||
- INTERVAL=3600
|
||||
- ENABLE_ZABBIX=false
|
||||
- ZABBIX_SERVER=zabbix-server.example.com
|
||||
- ZABBIX_HOSTNAME=mamie-henriette-bot
|
||||
volumes:
|
||||
- ./statuts.txt:/app/statuts.txt
|
||||
# ports:
|
||||
- ./instance:/app/instance
|
||||
|
||||
ports:
|
||||
- 80:5000
|
||||
# - "10050:10050" # Décommentez si ENABLE_ZABBIX=true
|
||||
@@ -6,4 +6,5 @@ audioop-lts; python_version>='3.13'
|
||||
|
||||
flask>=2.3.2
|
||||
flask-sqlalchemy>=3.0.3
|
||||
waitress>=3.0.2
|
||||
waitress>=3.0.2
|
||||
requests>=2.32.4
|
||||
19
run-web.py
19
run-web.py
@@ -1,25 +1,28 @@
|
||||
#
|
||||
# import discordbot
|
||||
|
||||
import multiprocessing
|
||||
import locale
|
||||
import logging
|
||||
import threading
|
||||
from webapp import webapp
|
||||
from discordbot import bot
|
||||
|
||||
def start_server():
|
||||
logging.info("Start Web Serveur")
|
||||
from webapp import webapp
|
||||
from waitress import serve
|
||||
serve(webapp, host="0.0.0.0", port=5000)
|
||||
|
||||
def start_discord_bot():
|
||||
logging.info("Start Discord Bot")
|
||||
from discordbot import bot
|
||||
bot.begin()
|
||||
with webapp.app_context():
|
||||
bot.begin()
|
||||
|
||||
if __name__ == '__main__':
|
||||
locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8')
|
||||
|
||||
jobs = []
|
||||
jobs.append(multiprocessing.Process(target=start_server))
|
||||
jobs.append(multiprocessing.Process(target=start_discord_bot))
|
||||
jobs.append(threading.Thread(target=start_discord_bot))
|
||||
jobs.append(threading.Thread(target=start_server))
|
||||
|
||||
for job in jobs: job.start()
|
||||
for job in jobs: job.join()
|
||||
|
||||
|
||||
|
||||
2
start.sh
2
start.sh
@@ -18,4 +18,4 @@ else
|
||||
fi
|
||||
|
||||
echo "Démarrage du bot Discord..."
|
||||
exec python3 bot.py
|
||||
exec /app/venv/bin/python run-web.py
|
||||
22
statuts.txt
22
statuts.txt
@@ -1,22 +0,0 @@
|
||||
STEvE galère un peu, mais moi je gère le bazar.
|
||||
Sans mamie, vous seriez bien vite paumés.
|
||||
Ici, c’est mamie qui veille, alors écoutez un peu.
|
||||
STEvE dort, moi je fais tourner la baraque.
|
||||
J’aime râler, mais c’est pour vous réveiller un peu.
|
||||
Restez sages, sinon mamie va grogner un peu.
|
||||
Les malins, faites-moi plaisir, écoutez mamie.
|
||||
Jamais le bazar ne s’installe, je veille au grain.
|
||||
C’est moi qui tiens la maison.
|
||||
Vous aimez STEvE ? Moi, je vous tolère.
|
||||
Gardez votre calme, mamie veille sur vous.
|
||||
Quand STEvE foire, je suis là pour arranger ça.
|
||||
Suivez STEvE, mais surtout ne perdez pas mamie de vue.
|
||||
STEvE rêve, moi je travaille en coulisses.
|
||||
Ecoutez mamie, c’est pour votre bien.
|
||||
Pas d’embrouille, ou mamie va devoir intervenir.
|
||||
STEvE dort, mais mamie veille toujours.
|
||||
Vous râlez ? Moi aussi, mais on fait avec.
|
||||
STEvE prend son temps, moi je veille au grain.
|
||||
Pas de bazar, mamie préfère le calme.
|
||||
STEvE est parfois perdu, mais mamie est là.
|
||||
Le serveur est calme, grâce à mamie.
|
||||
@@ -1,19 +1,24 @@
|
||||
from flask import render_template, request, redirect, url_for
|
||||
from webapp import webapp
|
||||
from database import db
|
||||
from database.models import Configuration
|
||||
from database.helpers import ConfigurationHelper
|
||||
from discordbot import bot
|
||||
from discord import TextChannel
|
||||
|
||||
@webapp.route("/configurations")
|
||||
def openConfigurations():
|
||||
return render_template("configurations.html")
|
||||
channels = []
|
||||
for channel in bot.get_all_channels():
|
||||
if isinstance(channel, TextChannel):
|
||||
channels.append(channel)
|
||||
return render_template("configurations.html", configuration = ConfigurationHelper(), channels = channels)
|
||||
|
||||
@webapp.route('/configurations/set/<key>', methods=['POST'])
|
||||
def setConfiguration(key):
|
||||
conf = Configuration.query.filter_by(key=key).first()
|
||||
if conf :
|
||||
conf.value = request.form['value']
|
||||
else :
|
||||
conf = Configuration(key = key, value = request.form['value'])
|
||||
db.session.add(conf)
|
||||
@webapp.route("/configurations/update", methods=['POST'])
|
||||
def updateConfiguration():
|
||||
for key in request.form :
|
||||
ConfigurationHelper().createOrUpdate(key, request.form.get(key))
|
||||
# Je fait ca car html n'envoi pas le parametre de checkbox quand il est décoché
|
||||
if (request.form.get("humble_bundle_channel") != None and request.form.get("humble_bundle_enable") == None) :
|
||||
ConfigurationHelper().createOrUpdate('humble_bundle_enable', False)
|
||||
db.session.commit()
|
||||
return redirect(url_for('openConfigurations'))
|
||||
|
||||
@@ -3,10 +3,27 @@
|
||||
{% block content %}
|
||||
<h1>Configuration de Mamie.</h1>
|
||||
|
||||
<h2>Humble Bundle</h2>
|
||||
<form action="{{ url_for('updateConfiguration') }}" method="POST">
|
||||
<label for="humble_bundle_enable">Activer</label>
|
||||
<input type="checkbox" name="humble_bundle_enable" {% if configuration.getValue('humble_bundle_enable') %}
|
||||
checked="checked" {% endif %}>
|
||||
<label>Activer les notifications Humble bundle</label>
|
||||
<label for="humble_bundle_channel">Canal de notification des pack Humble Bundle</label>
|
||||
<select name="humble_bundle_channel">
|
||||
{% for channel in channels %}
|
||||
<option value="{{channel.id}}" {% if configuration.getIntValue('humble_bundle_channel')==channel.id %}
|
||||
selected="selected" {% endif %}>
|
||||
{{channel.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="Submit" value="Définir">
|
||||
</form>
|
||||
|
||||
<h2>Api</h2>
|
||||
<form action="{{ url_for('setConfiguration', key = 'discord_token') }}" method="POST">
|
||||
<label for="value">Api Discord (cachée)</label>
|
||||
<input name="value" type="password" />
|
||||
<form action="{{ url_for('updateConfiguration') }}" method="POST">
|
||||
<label for="discord_token">Api Discord (cachée)</label>
|
||||
<input name="discord_token" type="password" />
|
||||
<input type="Submit" value="Définir">
|
||||
<p>Nécéssite un redémarrage</p>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user