From 82faed0991f50ba0a20eb41c61b3a8ea7e13b451 Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 01:03:00 +0200 Subject: [PATCH 1/9] =?UTF-8?q?Premlier=20essai=20de=20humble=20bundle,=20?= =?UTF-8?q?j'arrive=20pas=20=C3=A0=20lister=20les=20canauxx=20dans=20la=20?= =?UTF-8?q?page=20de=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/models.py | 5 +++ database/schema.sql | 17 ++++++--- discordbot/__init__.py | 52 +++++++++++++++++++--------- requirements.txt | 3 +- run-web.py | 21 +++++++---- webapp/__init__.py | 1 + webapp/configurations.py | 12 ++++++- webapp/templates/configurations.html | 13 +++++++ 8 files changed, 94 insertions(+), 30 deletions(-) diff --git a/database/models.py b/database/models.py index 05f1ad5..020a25e 100644 --- a/database/models.py +++ b/database/models.py @@ -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) diff --git a/database/schema.sql b/database/schema.sql index 0ba37a3..a0c2d59 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -4,6 +4,18 @@ 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, @@ -11,8 +23,3 @@ CREATE TABLE IF NOT EXISTS `message` ( periodicity INTEGER NULL ); -CREATE TABLE IF NOT EXISTS `humeur` ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - `enable` BOOLEAN NOT NULL DEFAULT TRUE, - `text` VARCHAR(256) NULL -); diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 6bbee83..335cd9c 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -1,34 +1,54 @@ -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 logging +import random +import requests + +from database import db +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}') + # for c in self.get_all_channels() : + # print(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) + async def updateHumbleBundle(self): + 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(1123512494468644984).send(message) + # db.session.add(GameBundle(id=bundle['id'], json = bundle)) + # db.session.commit() + else: + logging.error(f"Erreur de connexion {response.status_code}") + await asyncio.sleep(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) + diff --git a/requirements.txt b/requirements.txt index ab7ca91..9fecc34 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ audioop-lts; python_version>='3.13' flask>=2.3.2 flask-sqlalchemy>=3.0.3 -waitress>=3.0.2 \ No newline at end of file +waitress>=3.0.2 +requests>=2.32.4 \ No newline at end of file diff --git a/run-web.py b/run-web.py index a619128..19be727 100644 --- a/run-web.py +++ b/run-web.py @@ -1,25 +1,32 @@ -# -# import discordbot -import multiprocessing +import locale import logging +import multiprocessing +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) + # webapp.run() 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(multiprocessing.Process(target=start_server)) for job in jobs: job.start() + + print(bot.get_all_channels()) + for job in jobs: job.join() + diff --git a/webapp/__init__.py b/webapp/__init__.py index d011c17..6f5f7c4 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -1,4 +1,5 @@ from flask import Flask +from discordbot import bot webapp = Flask(__name__) diff --git a/webapp/configurations.py b/webapp/configurations.py index d8d6c0f..faa461d 100644 --- a/webapp/configurations.py +++ b/webapp/configurations.py @@ -2,10 +2,20 @@ from flask import render_template, request, redirect, url_for from webapp import webapp from database import db from database.models import Configuration +from discordbot import bot @webapp.route("/configurations") def openConfigurations(): - return render_template("configurations.html") + all = Configuration.query.all() + configurations = {conf.key: conf for conf in all} + + return render_template("configurations.html", configurations = configurations, channels = bot.get_all_channels()) + +@webapp.route("/updateConfiguration", methods=['POST']) +def updateConfiguration(): + + return redirect(url_for('openConfigurations')) + @webapp.route('/configurations/set/', methods=['POST']) def setConfiguration(key): diff --git a/webapp/templates/configurations.html b/webapp/templates/configurations.html index 075c718..7d67adc 100644 --- a/webapp/templates/configurations.html +++ b/webapp/templates/configurations.html @@ -3,6 +3,19 @@ {% block content %}

Configuration de Mamie.

+

Humble Bundle

+
+ + {{channels}} + + +
+ +

Api

From 38b2ecc216c24bc32bcc29afc670a43d9d434142 Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 01:19:53 +0200 Subject: [PATCH 2/9] je passe sur des thread et ca marche, mystere --- discordbot/__init__.py | 4 ++-- run-web.py | 9 +++------ webapp/__init__.py | 1 - webapp/configurations.py | 1 - 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index 335cd9c..ff5d9ad 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -11,8 +11,8 @@ 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() : - # print(f'{c.id} {c.name}') + for c in self.get_all_channels() : + logging.info(f'{c.id} {c.name}') self.loop.create_task(self.updateStatus()) self.loop.create_task(self.updateHumbleBundle()) diff --git a/run-web.py b/run-web.py index 19be727..009268e 100644 --- a/run-web.py +++ b/run-web.py @@ -1,7 +1,7 @@ import locale import logging -import multiprocessing +import threading from webapp import webapp from discordbot import bot @@ -20,13 +20,10 @@ if __name__ == '__main__': locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8') jobs = [] - jobs.append(multiprocessing.Process(target=start_discord_bot)) - jobs.append(multiprocessing.Process(target=start_server)) + jobs.append(threading.Thread(target=start_discord_bot)) + jobs.append(threading.Thread(target=start_server)) for job in jobs: job.start() - - print(bot.get_all_channels()) - for job in jobs: job.join() diff --git a/webapp/__init__.py b/webapp/__init__.py index 6f5f7c4..d011c17 100644 --- a/webapp/__init__.py +++ b/webapp/__init__.py @@ -1,5 +1,4 @@ from flask import Flask -from discordbot import bot webapp = Flask(__name__) diff --git a/webapp/configurations.py b/webapp/configurations.py index faa461d..110f5cf 100644 --- a/webapp/configurations.py +++ b/webapp/configurations.py @@ -8,7 +8,6 @@ from discordbot import bot def openConfigurations(): all = Configuration.query.all() configurations = {conf.key: conf for conf in all} - return render_template("configurations.html", configurations = configurations, channels = bot.get_all_channels()) @webapp.route("/updateConfiguration", methods=['POST']) From 84b92f636f1281b32cc36bf3207e1899d5a9183b Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 10:39:58 +0200 Subject: [PATCH 3/9] configuration humble bundle --- database/helpers.py | 32 ++++++++++++++++++++++++++++ database/schema.sql | 1 - run-web.py | 1 - webapp/configurations.py | 27 +++++++++++------------ webapp/templates/configurations.html | 18 ++++++++++------ 5 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 database/helpers.py diff --git a/database/helpers.py b/database/helpers.py new file mode 100644 index 0000000..319285e --- /dev/null +++ b/database/helpers.py @@ -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) + + + + diff --git a/database/schema.sql b/database/schema.sql index a0c2d59..7a88e61 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -22,4 +22,3 @@ CREATE TABLE IF NOT EXISTS `message` ( `text` VARCHAR(256) NULL, periodicity INTEGER NULL ); - diff --git a/run-web.py b/run-web.py index 009268e..218fe2b 100644 --- a/run-web.py +++ b/run-web.py @@ -9,7 +9,6 @@ def start_server(): logging.info("Start Web Serveur") from waitress import serve serve(webapp, host="0.0.0.0", port=5000) - # webapp.run() def start_discord_bot(): logging.info("Start Discord Bot") diff --git a/webapp/configurations.py b/webapp/configurations.py index 110f5cf..534e533 100644 --- a/webapp/configurations.py +++ b/webapp/configurations.py @@ -2,27 +2,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(): all = Configuration.query.all() - configurations = {conf.key: conf for conf in all} - return render_template("configurations.html", configurations = configurations, channels = bot.get_all_channels()) + 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("/updateConfiguration", methods=['POST']) +@webapp.route("/configurations/update", methods=['POST']) def updateConfiguration(): - - return redirect(url_for('openConfigurations')) - - -@webapp.route('/configurations/set/', 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) + for key in request.form : + ConfigurationHelper().createOrUpdate(key, request.form.get(key)) + 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')) diff --git a/webapp/templates/configurations.html b/webapp/templates/configurations.html index 7d67adc..f2e6038 100644 --- a/webapp/templates/configurations.html +++ b/webapp/templates/configurations.html @@ -5,21 +5,25 @@

Humble Bundle

- - {{channels}} + + + + -

Api

-
- - + + +

Nécéssite un redémarrage

From ac7f00bd8b4d3b66b20fa1a7d94a55caee404b10 Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 10:40:42 +0200 Subject: [PATCH 4/9] configuration humble bundle --- webapp/configurations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/configurations.py b/webapp/configurations.py index 534e533..17817f3 100644 --- a/webapp/configurations.py +++ b/webapp/configurations.py @@ -19,6 +19,7 @@ def openConfigurations(): 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() From c00e0b491d047e84d21ed7047f1d4966fecb4beb Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 13:28:20 +0200 Subject: [PATCH 5/9] notification humble bundle --- discordbot/__init__.py | 51 +++++++++++++++++++++++----------------- webapp/configurations.py | 2 -- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index ff5d9ad..a5799c8 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -1,11 +1,13 @@ import asyncio 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): @@ -18,29 +20,36 @@ class DiscordBot(discord.Client): self.loop.create_task(self.updateHumbleBundle()) async def updateStatus(self): - 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(): + 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)) + # 10 minutes TODO à rendre configurable + await asyncio.sleep(10*60) async def updateHumbleBundle(self): - 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(1123512494468644984).send(message) - # db.session.add(GameBundle(id=bundle['id'], json = bundle)) - # db.session.commit() - else: - logging.error(f"Erreur de connexion {response.status_code}") - await asyncio.sleep(60) + 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) : token = Configuration.query.filter_by(key='discord_token').first() diff --git a/webapp/configurations.py b/webapp/configurations.py index 17817f3..e629e69 100644 --- a/webapp/configurations.py +++ b/webapp/configurations.py @@ -1,14 +1,12 @@ 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(): - all = Configuration.query.all() channels = [] for channel in bot.get_all_channels(): if isinstance(channel, TextChannel): From a96e3ebc63164d455c824951ef5720ea9722ce32 Mon Sep 17 00:00:00 2001 From: Kepka Ludovic Date: Mon, 11 Aug 2025 23:00:03 +0200 Subject: [PATCH 6/9] corrections quand y a pas d'humeurs --- discordbot/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/discordbot/__init__.py b/discordbot/__init__.py index a5799c8..6d2dae8 100644 --- a/discordbot/__init__.py +++ b/discordbot/__init__.py @@ -21,10 +21,12 @@ class DiscordBot(discord.Client): async def updateStatus(self): while not self.is_closed(): - 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)) + 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) From 86e8e4695983e4d4c980048c9a199e6c4ede066a Mon Sep 17 00:00:00 2001 From: skylanix Date: Tue, 12 Aug 2025 01:33:04 +0200 Subject: [PATCH 7/9] =?UTF-8?q?Migration=20vers=20pr=C3=A9-prod=20:=20refo?= =?UTF-8?q?nte=20Docker=20et=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile modernisé avec venv Python et locales françaises - Configuration docker-compose simplifiée - README complètement réécrit pour l'architecture multi-plateformes - Suppression bot.py et statuts.txt (remplacés par modules webapp/discordbot) - Migration vers interface d'administration web --- .env.example | 5 - Dockerfile | 14 ++- README.md | 271 ++++++++++++++++++++++++--------------------- bot.py | 114 ------------------- docker-compose.yml | 11 +- start.sh | 2 +- statuts.txt | 22 ---- 7 files changed, 159 insertions(+), 280 deletions(-) delete mode 100755 bot.py delete mode 100755 statuts.txt diff --git a/.env.example b/.env.example index 36cd4eb..7f886eb 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/Dockerfile b/Dockerfile index b72b160..743b41c 100755 --- a/Dockerfile +++ b/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"] \ No newline at end of file diff --git a/README.md b/README.md index 68e01d3..61595cc 100755 --- a/README.md +++ b/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 +- 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)* + +## 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 et Docker Compose -- Compte Discord et Token du bot -- (Optionnel) Serveur Zabbix pour la surveillance +- Token Discord pour le bot -## 📦 Installation +### 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/MamieHenrietteCredential ``` -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 -``` \ No newline at end of file +*Mamie Henriette vous surveille ! 👵👀* \ No newline at end of file diff --git a/bot.py b/bot.py deleted file mode 100755 index d9fe373..0000000 --- a/bot.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f6f46a0..c5f75bf 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/start.sh b/start.sh index 4371cb5..b6cc32c 100644 --- a/start.sh +++ b/start.sh @@ -18,4 +18,4 @@ else fi echo "Démarrage du bot Discord..." -exec python3 bot.py \ No newline at end of file +exec /app/venv/bin/python run-web.py \ No newline at end of file diff --git a/statuts.txt b/statuts.txt deleted file mode 100755 index bf36966..0000000 --- a/statuts.txt +++ /dev/null @@ -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. \ No newline at end of file From 2eca411ced6520e62ac58bc88e70e40dcda844fe Mon Sep 17 00:00:00 2001 From: skylanix Date: Tue, 12 Aug 2025 01:48:05 +0200 Subject: [PATCH 8/9] Correction lien git clone --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61595cc..258ca04 100755 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Mamie Henriette est un bot intelligent open-source développé spécifiquement p ```bash # 1. Cloner le projet -git clone https://github.com/skylanix/MamieHenrietteCredential +git clone https://github.com/skylanix/MamieHenriette.git ``` ```bash From 5ef5900ed779a25ec498cc6b6f7fb5cfcde29d75 Mon Sep 17 00:00:00 2001 From: skylanix Date: Tue, 12 Aug 2025 02:42:34 +0200 Subject: [PATCH 9/9] Remplacement Docker compose par Docker Engine et Docker Desktop avec liens installations. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 258ca04..e2d09af 100755 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Mamie Henriette est un bot intelligent open-source développé spécifiquement p ## Installation ### Prérequis -- Docker et Docker Compose +- [Docker Engine](https://docs.docker.com/engine/install/) ou [Docker Desktop](https://docs.docker.com/desktop/) - Token Discord pour le bot ### Démarrage rapide