Ajout de la gestion des jeux gratuits et des invitations Discord. Création des modèles FreeGame et DiscordInvite, mise à jour de la base de données et des fichiers de configuration. Intégration de nouvelles fonctionnalités pour synchroniser et révoquer les invitations, ainsi que l'ajout d'un flux RSS pour les jeux gratuits. Amélioration de l'interface d'administration avec des statistiques et des options de gestion des serveurs.
This commit is contained in:
+229
-3
@@ -5,9 +5,11 @@ import random
|
||||
|
||||
from database import db
|
||||
from database.helpers import ConfigurationHelper
|
||||
from database.models import Configuration, Humeur, Commande
|
||||
from database.models import Configuration, Humeur, Commande, DiscordInvite
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from discord import Message, TextChannel, Member
|
||||
from discordbot.humblebundle import checkHumbleBundleAndNotify
|
||||
from discordbot.freegames import checkFreeGamesAndNotify
|
||||
from discordbot.moderation import (
|
||||
handle_warning_command,
|
||||
handle_remove_warning_command,
|
||||
@@ -22,7 +24,9 @@ from discordbot.moderation import (
|
||||
handle_say_command
|
||||
)
|
||||
from discordbot.welcome import sendWelcomeMessage, sendLeaveMessage, updateInviteCache
|
||||
from discordbot.autorole import assignAutoRole
|
||||
from protondb import searhProtonDb
|
||||
from shared_stats import stats_manager, discord_bridge
|
||||
|
||||
class DiscordBot(discord.Client):
|
||||
async def on_ready(self):
|
||||
@@ -33,8 +37,53 @@ class DiscordBot(discord.Client):
|
||||
for guild in self.guilds:
|
||||
await updateInviteCache(guild)
|
||||
|
||||
# Enregistrer le bot dans le bridge pour communication avec Flask
|
||||
discord_bridge.register_bot(self, self.loop)
|
||||
|
||||
# Mise à jour des stats partagées
|
||||
self._update_shared_stats()
|
||||
|
||||
# Synchronisation initiale des invitations
|
||||
await self.syncInvites()
|
||||
|
||||
self.loop.create_task(self.updateStatus())
|
||||
self.loop.create_task(self.updateHumbleBundle())
|
||||
self.loop.create_task(self.updateFreeGames())
|
||||
self.loop.create_task(self._periodic_stats_update())
|
||||
|
||||
def _update_shared_stats(self):
|
||||
"""Met à jour les statistiques partagées"""
|
||||
total_members = sum(g.member_count or 0 for g in self.guilds)
|
||||
total_channels = len(list(self.get_all_channels()))
|
||||
|
||||
stats_manager.update_discord_stats(
|
||||
connected=True,
|
||||
guilds=len(self.guilds),
|
||||
members=total_members,
|
||||
channels=total_channels,
|
||||
bot_name=str(self.user),
|
||||
bot_id=self.user.id
|
||||
)
|
||||
|
||||
# Mise à jour des cogs/fonctionnalités activées
|
||||
helper = ConfigurationHelper()
|
||||
cogs = {
|
||||
'Modération': helper.getValue('moderation_enable') or False,
|
||||
'Ban': helper.getValue('moderation_ban_enable') or False,
|
||||
'Kick': helper.getValue('moderation_kick_enable') or False,
|
||||
'ProtonDB': helper.getValue('proton_db_enable_enable') or False,
|
||||
'Humeurs': True, # Toujours actif si le bot tourne
|
||||
'Jeux Gratuits': helper.getValue('freegames_enable') or False,
|
||||
'Messages de bienvenue': helper.getValue('welcome_enable') or False,
|
||||
'Auto-Role': helper.getValue('autorole_enable') or False,
|
||||
}
|
||||
stats_manager.update_cogs(cogs)
|
||||
|
||||
async def _periodic_stats_update(self):
|
||||
"""Met à jour les stats périodiquement"""
|
||||
while not self.is_closed():
|
||||
await asyncio.sleep(60) # Toutes les minutes
|
||||
self._update_shared_stats()
|
||||
|
||||
async def updateStatus(self):
|
||||
while not self.is_closed():
|
||||
@@ -42,8 +91,18 @@ class DiscordBot(discord.Client):
|
||||
if len(humeurs)>0 :
|
||||
humeur = random.choice(humeurs)
|
||||
if humeur != None:
|
||||
logging.info(f'Changement de statut : {humeur.text}')
|
||||
await self.change_presence(status = discord.Status.online, activity = discord.CustomActivity(humeur.text))
|
||||
# Récupérer les stats pour les variables
|
||||
total_members = sum(g.member_count or 0 for g in self.guilds)
|
||||
total_channels = len(list(self.get_all_channels()))
|
||||
|
||||
# Remplacer les variables dans le texte
|
||||
status_text = humeur.text
|
||||
status_text = status_text.replace('{servers}', str(len(self.guilds)))
|
||||
status_text = status_text.replace('{members}', str(total_members))
|
||||
status_text = status_text.replace('{channels}', str(total_channels))
|
||||
|
||||
logging.info(f'Changement de statut : {status_text}')
|
||||
await self.change_presence(status = discord.Status.online, activity = discord.CustomActivity(status_text))
|
||||
await asyncio.sleep(10*60)
|
||||
|
||||
async def updateHumbleBundle(self):
|
||||
@@ -51,6 +110,11 @@ class DiscordBot(discord.Client):
|
||||
await checkHumbleBundleAndNotify(self)
|
||||
await asyncio.sleep(30*60)
|
||||
|
||||
async def updateFreeGames(self):
|
||||
while not self.is_closed():
|
||||
await checkFreeGamesAndNotify(self)
|
||||
await asyncio.sleep(60*60) # Vérification toutes les heures
|
||||
|
||||
def getAllTextChannel(self) -> list[TextChannel]:
|
||||
channels = []
|
||||
for channel in self.get_all_channels():
|
||||
@@ -72,7 +136,156 @@ class DiscordBot(discord.Client):
|
||||
'roles': roles
|
||||
})
|
||||
return guilds_roles
|
||||
|
||||
def getAllGuilds(self):
|
||||
"""Retourne la liste de tous les serveurs Discord"""
|
||||
guilds_list = []
|
||||
for guild in self.guilds:
|
||||
guilds_list.append({
|
||||
'id': guild.id,
|
||||
'name': guild.name,
|
||||
'member_count': guild.member_count,
|
||||
'icon_url': str(guild.icon.url) if guild.icon else None,
|
||||
'owner_id': guild.owner_id
|
||||
})
|
||||
return guilds_list
|
||||
|
||||
async def leaveGuild(self, guild_id: int) -> bool:
|
||||
"""Quitte un serveur Discord par son ID"""
|
||||
guild = self.get_guild(guild_id)
|
||||
if guild:
|
||||
await guild.leave()
|
||||
logging.info(f'Le bot a quitté le serveur : {guild.name} (ID: {guild_id})')
|
||||
return True
|
||||
return False
|
||||
|
||||
async def syncInvites(self, guild_id: int = None) -> dict:
|
||||
"""Synchronise les invitations Discord avec la base de données"""
|
||||
result = {'synced': 0, 'errors': [], 'guilds': []}
|
||||
|
||||
guilds_to_sync = [self.get_guild(guild_id)] if guild_id else self.guilds
|
||||
|
||||
for guild in guilds_to_sync:
|
||||
if not guild:
|
||||
continue
|
||||
|
||||
try:
|
||||
invites = await guild.invites()
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Marquer les invitations existantes comme révoquées (on les réactivera si elles existent encore)
|
||||
existing_invites = DiscordInvite.query.filter_by(guild_id=str(guild.id), revoked=False).all()
|
||||
existing_codes = {inv.code for inv in existing_invites}
|
||||
current_codes = {inv.code for inv in invites}
|
||||
|
||||
# Marquer comme révoquées celles qui n'existent plus
|
||||
for inv in existing_invites:
|
||||
if inv.code not in current_codes:
|
||||
inv.revoked = True
|
||||
inv.last_sync = now
|
||||
|
||||
for invite in invites:
|
||||
# Calculer la date d'expiration
|
||||
expires_at = None
|
||||
if invite.max_age and invite.max_age > 0 and invite.created_at:
|
||||
expires_at = invite.created_at + timedelta(seconds=invite.max_age)
|
||||
|
||||
# Chercher si l'invitation existe déjà
|
||||
db_invite = DiscordInvite.query.filter_by(code=invite.code).first()
|
||||
|
||||
if db_invite:
|
||||
# Mettre à jour l'invitation existante
|
||||
db_invite.uses = invite.uses or 0
|
||||
db_invite.revoked = False
|
||||
db_invite.last_sync = now
|
||||
db_invite.channel_name = invite.channel.name if invite.channel else None
|
||||
db_invite.inviter_name = invite.inviter.name if invite.inviter else None
|
||||
else:
|
||||
# Créer une nouvelle invitation
|
||||
db_invite = DiscordInvite(
|
||||
code=invite.code,
|
||||
guild_id=str(guild.id),
|
||||
channel_id=str(invite.channel.id) if invite.channel else '',
|
||||
channel_name=invite.channel.name if invite.channel else None,
|
||||
inviter_id=str(invite.inviter.id) if invite.inviter else None,
|
||||
inviter_name=invite.inviter.name if invite.inviter else None,
|
||||
uses=invite.uses or 0,
|
||||
max_uses=invite.max_uses or 0,
|
||||
max_age=invite.max_age or 0,
|
||||
temporary=invite.temporary or False,
|
||||
created_at=invite.created_at,
|
||||
expires_at=expires_at,
|
||||
revoked=False,
|
||||
last_sync=now
|
||||
)
|
||||
db.session.add(db_invite)
|
||||
|
||||
result['synced'] += 1
|
||||
|
||||
db.session.commit()
|
||||
result['guilds'].append({'id': guild.id, 'name': guild.name, 'invites': len(invites)})
|
||||
logging.info(f'Invitations synchronisées pour {guild.name}: {len(invites)} invitations')
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f'Erreur lors de la synchronisation des invitations pour {guild.name}: {e}')
|
||||
result['errors'].append(f'{guild.name}: {str(e)}')
|
||||
|
||||
return result
|
||||
|
||||
async def revokeInvite(self, invite_code: str) -> dict:
|
||||
"""Révoque une invitation Discord"""
|
||||
result = {'success': False, 'message': '', 'invite_code': invite_code}
|
||||
|
||||
try:
|
||||
# Chercher l'invitation dans la BDD
|
||||
db_invite = DiscordInvite.query.filter_by(code=invite_code).first()
|
||||
if not db_invite:
|
||||
result['message'] = 'Invitation non trouvée dans la base de données'
|
||||
return result
|
||||
|
||||
# Récupérer le guild
|
||||
guild = self.get_guild(int(db_invite.guild_id))
|
||||
if not guild:
|
||||
result['message'] = 'Serveur Discord non trouvé'
|
||||
return result
|
||||
|
||||
# Chercher l'invitation sur Discord
|
||||
try:
|
||||
invites = await guild.invites()
|
||||
discord_invite = next((inv for inv in invites if inv.code == invite_code), None)
|
||||
|
||||
if discord_invite:
|
||||
await discord_invite.delete(reason='Révoquée via interface web')
|
||||
logging.info(f'Invitation {invite_code} révoquée sur Discord')
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f'Impossible de révoquer l\'invitation sur Discord: {e}')
|
||||
|
||||
# Marquer comme révoquée dans la BDD
|
||||
db_invite.revoked = True
|
||||
db_invite.last_sync = datetime.now(timezone.utc)
|
||||
db.session.commit()
|
||||
|
||||
result['success'] = True
|
||||
result['message'] = f'Invitation {invite_code} révoquée avec succès'
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f'Erreur lors de la révocation de l\'invitation {invite_code}: {e}')
|
||||
result['message'] = f'Erreur: {str(e)}'
|
||||
|
||||
return result
|
||||
|
||||
def getInvites(self, guild_id: int = None, include_revoked: bool = False) -> list:
|
||||
"""Récupère les invitations depuis la base de données"""
|
||||
query = DiscordInvite.query
|
||||
|
||||
if guild_id:
|
||||
query = query.filter_by(guild_id=str(guild_id))
|
||||
|
||||
if not include_revoked:
|
||||
query = query.filter_by(revoked=False)
|
||||
|
||||
return query.order_by(DiscordInvite.created_at.desc()).all()
|
||||
|
||||
def begin(self) :
|
||||
token = Configuration.query.filter_by(key='discord_token').first()
|
||||
@@ -251,6 +464,7 @@ async def on_message(message: Message):
|
||||
@bot.event
|
||||
async def on_member_join(member: Member):
|
||||
await sendWelcomeMessage(bot, member)
|
||||
await assignAutoRole(bot, member)
|
||||
|
||||
@bot.event
|
||||
async def on_member_remove(member: Member):
|
||||
@@ -259,8 +473,20 @@ async def on_member_remove(member: Member):
|
||||
@bot.event
|
||||
async def on_invite_create(invite):
|
||||
await updateInviteCache(invite.guild)
|
||||
# Synchroniser la nouvelle invitation avec la BDD
|
||||
await bot.syncInvites(invite.guild.id)
|
||||
|
||||
@bot.event
|
||||
async def on_invite_delete(invite):
|
||||
await updateInviteCache(invite.guild)
|
||||
# Marquer l'invitation comme révoquée dans la BDD
|
||||
try:
|
||||
db_invite = DiscordInvite.query.filter_by(code=invite.code).first()
|
||||
if db_invite:
|
||||
db_invite.revoked = True
|
||||
db_invite.last_sync = datetime.now(timezone.utc)
|
||||
db.session.commit()
|
||||
logging.info(f'Invitation {invite.code} marquée comme révoquée')
|
||||
except Exception as e:
|
||||
logging.error(f'Erreur lors de la révocation de l\'invitation {invite.code}: {e}')
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import discord
|
||||
import logging
|
||||
from database.helpers import ConfigurationHelper
|
||||
from discord import Member
|
||||
|
||||
async def assignAutoRole(bot: discord.Client, member: Member):
|
||||
"""Attribue automatiquement un rôle aux nouveaux membres"""
|
||||
config = ConfigurationHelper()
|
||||
|
||||
if not config.getValue('autorole_enable'):
|
||||
return
|
||||
|
||||
role_id = config.getIntValue('autorole_role_id')
|
||||
if not role_id:
|
||||
logging.warning('Auto-role activé mais aucun rôle configuré')
|
||||
return
|
||||
|
||||
# Chercher le rôle dans le serveur du membre
|
||||
role = member.guild.get_role(role_id)
|
||||
if not role:
|
||||
logging.error(f'Rôle auto-role {role_id} introuvable dans le serveur {member.guild.name}')
|
||||
return
|
||||
|
||||
try:
|
||||
await member.add_roles(role, reason='Auto-role: Attribution automatique à l\'arrivée')
|
||||
logging.info(f'Auto-role: Rôle "{role.name}" attribué à {member.name} sur {member.guild.name}')
|
||||
except discord.Forbidden:
|
||||
logging.error(f'Auto-role: Permission refusée pour attribuer le rôle "{role.name}" à {member.name}')
|
||||
except discord.HTTPException as e:
|
||||
logging.error(f'Auto-role: Erreur HTTP lors de l\'attribution du rôle : {e}')
|
||||
except Exception as e:
|
||||
logging.error(f'Auto-role: Erreur inattendue : {e}')
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
import discord
|
||||
import feedparser
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from html import unescape
|
||||
|
||||
from database import db
|
||||
from database.helpers import ConfigurationHelper
|
||||
from database.models import FreeGame
|
||||
from discord import Client
|
||||
|
||||
RSS_URL = "https://feed.eikowagenknecht.com/lootscraper.xml"
|
||||
|
||||
KNOWN_SOURCES = {
|
||||
'epic': 'Epic Games',
|
||||
'steam': 'Steam',
|
||||
'gog': 'GOG',
|
||||
'amazon': 'Amazon Prime',
|
||||
'humble': 'Humble Bundle',
|
||||
'apple': 'Apple App Store',
|
||||
'google': 'Google Play',
|
||||
'itch': 'Itch.io',
|
||||
'ubisoft': 'Ubisoft',
|
||||
'indiegala': 'IndieGala'
|
||||
}
|
||||
|
||||
def _isEnabled():
|
||||
helper = ConfigurationHelper()
|
||||
return helper.getValue('freegames_enable') and helper.getIntValue('freegames_channel') != 0
|
||||
|
||||
def _parseSource(title: str) -> str:
|
||||
title_lower = title.lower()
|
||||
for key, name in KNOWN_SOURCES.items():
|
||||
if key in title_lower:
|
||||
return name
|
||||
match = re.search(r'\(([^)]+)\)', title)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return "Autre"
|
||||
|
||||
def _parseEntryContent(entry) -> dict:
|
||||
content = entry.get('content', [{}])[0].get('value', '') if entry.get('content') else ''
|
||||
summary = entry.get('summary', '')
|
||||
|
||||
image_url = None
|
||||
img_match = re.search(r'<img[^>]+src=["\']([^"\']+)["\']', content or summary)
|
||||
if img_match:
|
||||
image_url = img_match.group(1)
|
||||
|
||||
valid_from = None
|
||||
valid_to = None
|
||||
|
||||
from_match = re.search(r'Offer valid from:</b>\s*([^<]+)', content)
|
||||
if from_match:
|
||||
try:
|
||||
date_str = from_match.group(1).strip()
|
||||
valid_from = datetime.strptime(date_str, '%Y-%m-%d %H:%M')
|
||||
except:
|
||||
pass
|
||||
|
||||
to_match = re.search(r'Offer valid to:</b>\s*([^<]+)', content)
|
||||
if to_match:
|
||||
try:
|
||||
date_str = to_match.group(1).strip()
|
||||
valid_to = datetime.strptime(date_str, '%Y-%m-%d %H:%M')
|
||||
except:
|
||||
pass
|
||||
|
||||
description = None
|
||||
desc_match = re.search(r'Description:</b>\s*([^<]+)', content)
|
||||
if desc_match:
|
||||
description = unescape(desc_match.group(1).strip())
|
||||
|
||||
price = None
|
||||
price_match = re.search(r'Recommended price[^:]*:</b>\s*([^<]+)', content)
|
||||
if price_match:
|
||||
price = price_match.group(1).strip()
|
||||
|
||||
genres = []
|
||||
for category in entry.get('tags', []):
|
||||
term = category.get('term', '')
|
||||
if term.startswith('Genre:'):
|
||||
genres.append(term.replace('Genre:', '').strip())
|
||||
|
||||
return {
|
||||
'image_url': image_url,
|
||||
'valid_from': valid_from,
|
||||
'valid_to': valid_to,
|
||||
'description': description,
|
||||
'price': price,
|
||||
'genres': genres
|
||||
}
|
||||
|
||||
def _fetchRSS() -> list:
|
||||
try:
|
||||
feed = feedparser.parse(RSS_URL)
|
||||
if feed.bozo:
|
||||
logging.warning(f"Erreur de parsing RSS: {feed.bozo_exception}")
|
||||
return feed.entries
|
||||
except Exception as e:
|
||||
logging.error(f"Échec de la récupération du flux RSS: {e}")
|
||||
return []
|
||||
|
||||
def _isSourceEnabled(source: str) -> bool:
|
||||
helper = ConfigurationHelper()
|
||||
enabled_sources = helper.getValue('freegames_sources')
|
||||
if not enabled_sources:
|
||||
return True
|
||||
enabled_list = [s.strip().lower() for s in enabled_sources.split(',')]
|
||||
return source.lower() in enabled_list or any(k in source.lower() for k in enabled_list)
|
||||
|
||||
def _getMentionText() -> str:
|
||||
helper = ConfigurationHelper()
|
||||
mention_type = helper.getValue('freegames_mention_type')
|
||||
|
||||
if mention_type == 'everyone':
|
||||
return '@everyone '
|
||||
elif mention_type == 'here':
|
||||
return '@here '
|
||||
elif mention_type == 'role':
|
||||
role_id = helper.getIntValue('freegames_mention_role')
|
||||
if role_id:
|
||||
return f'<@&{role_id}> '
|
||||
return ''
|
||||
|
||||
def _formatEmbed(game: FreeGame, parsed_data: dict) -> discord.Embed:
|
||||
clean_title = game.title
|
||||
if ' - ' in clean_title:
|
||||
clean_title = clean_title.split(' - ', 1)[1]
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{clean_title} gratuit sur {game.source} !",
|
||||
url=game.url,
|
||||
color=discord.Color.green()
|
||||
)
|
||||
|
||||
if game.description:
|
||||
desc = game.description[:300] + '...' if len(game.description) > 300 else game.description
|
||||
embed.description = desc
|
||||
|
||||
price_line = ""
|
||||
if parsed_data.get('price'):
|
||||
price_line = f"~~{parsed_data['price']}~~ **Gratuit**"
|
||||
else:
|
||||
price_line = "**Gratuit**"
|
||||
|
||||
if game.valid_to:
|
||||
price_line += f" jusqu'au {game.valid_to.strftime('%d/%m/%Y')}"
|
||||
|
||||
embed.add_field(name="💰 Prix", value=price_line, inline=False)
|
||||
embed.add_field(name="🔗 Récupérer le jeu", value=f"[Ouvrir dans la boutique !]({game.url})", inline=False)
|
||||
|
||||
if game.image_url:
|
||||
embed.set_image(url=game.image_url)
|
||||
|
||||
embed.set_footer(text="🎁 Jeu à looter - Mamie Henriette")
|
||||
|
||||
return embed
|
||||
|
||||
def fetchAndStoreGames() -> list[FreeGame]:
|
||||
entries = _fetchRSS()
|
||||
new_games = []
|
||||
|
||||
for entry in entries:
|
||||
entry_id = entry.get('id', entry.get('link', ''))
|
||||
|
||||
existing = FreeGame.query.filter_by(entry_id=entry_id).first()
|
||||
if existing:
|
||||
continue
|
||||
|
||||
title = entry.get('title', 'Jeu inconnu')
|
||||
source = _parseSource(title)
|
||||
url = entry.get('link', '')
|
||||
|
||||
parsed = _parseEntryContent(entry)
|
||||
|
||||
game = FreeGame(
|
||||
entry_id=entry_id,
|
||||
title=title,
|
||||
source=source,
|
||||
url=url,
|
||||
image_url=parsed.get('image_url'),
|
||||
description=parsed.get('description'),
|
||||
valid_from=parsed.get('valid_from'),
|
||||
valid_to=parsed.get('valid_to'),
|
||||
notified=False,
|
||||
created_at=datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
db.session.add(game)
|
||||
new_games.append(game)
|
||||
|
||||
if new_games:
|
||||
db.session.commit()
|
||||
logging.info(f"{len(new_games)} nouveaux jeux gratuits ajoutés")
|
||||
|
||||
return new_games
|
||||
|
||||
def getPendingGames() -> list[FreeGame]:
|
||||
return FreeGame.query.filter_by(notified=False).order_by(FreeGame.created_at.desc()).all()
|
||||
|
||||
def getAllGames() -> list[FreeGame]:
|
||||
return FreeGame.query.order_by(FreeGame.created_at.desc()).all()
|
||||
|
||||
async def notifyGame(bot: Client, game_id: int) -> bool:
|
||||
if not _isEnabled():
|
||||
return False
|
||||
|
||||
game = FreeGame.query.get(game_id)
|
||||
if not game or game.notified:
|
||||
return False
|
||||
|
||||
helper = ConfigurationHelper()
|
||||
channel_id = helper.getIntValue('freegames_channel')
|
||||
channel = bot.get_channel(channel_id)
|
||||
|
||||
if not channel:
|
||||
logging.error(f"Canal Free Games {channel_id} introuvable")
|
||||
return False
|
||||
|
||||
try:
|
||||
parsed = _parseEntryContent({'content': [{'value': game.description or ''}]})
|
||||
embed = _formatEmbed(game, parsed)
|
||||
mention = _getMentionText()
|
||||
|
||||
await channel.send(content=mention if mention else None, embed=embed)
|
||||
|
||||
game.notified = True
|
||||
game.notified_at = datetime.now(timezone.utc)
|
||||
db.session.commit()
|
||||
|
||||
logging.info(f"Notification envoyée pour: {game.title}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Échec de l'envoi de la notification Free Games: {e}")
|
||||
return False
|
||||
|
||||
async def checkFreeGamesAndNotify(bot: Client):
|
||||
if not _isEnabled():
|
||||
logging.debug('Free Games est désactivé')
|
||||
return
|
||||
|
||||
helper = ConfigurationHelper()
|
||||
auto_notify = helper.getValue('freegames_auto_notify')
|
||||
|
||||
new_games = fetchAndStoreGames()
|
||||
|
||||
if auto_notify:
|
||||
for game in new_games:
|
||||
if _isSourceEnabled(game.source):
|
||||
await notifyGame(bot, game.id)
|
||||
|
||||
def markAsNotified(game_id: int) -> bool:
|
||||
game = FreeGame.query.get(game_id)
|
||||
if game:
|
||||
game.notified = True
|
||||
game.notified_at = datetime.now(timezone.utc)
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def resetNotification(game_id: int) -> bool:
|
||||
game = FreeGame.query.get(game_id)
|
||||
if game:
|
||||
game.notified = False
|
||||
game.notified_at = None
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
@@ -1220,7 +1220,7 @@ async def parse_target_user(message: Message, bot, parts: list):
|
||||
except (ValueError, discord.NotFound):
|
||||
return None
|
||||
|
||||
def create_inspect_embed(user, member, join_date, days_on_server, account_age, warnings, kicks, bans, invite_info):
|
||||
def create_inspect_embed(user, member, join_date, days_on_server, account_age, warnings, kicks, bans, invite_info, requester):
|
||||
embed = discord.Embed(
|
||||
title=f"🔍 Inspection de {user.name}",
|
||||
color=discord.Color.blue(),
|
||||
@@ -1228,7 +1228,7 @@ def create_inspect_embed(user, member, join_date, days_on_server, account_age, w
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=user.display_avatar.url)
|
||||
embed.add_field(name="👤 Utilisateur", value=f"**{user.name}**\n`{user.id}`", inline=True)
|
||||
embed.add_field(name="👤 Utilisateur", value=f"**{user.name}**\n```{user.id}```", inline=True)
|
||||
|
||||
if account_age is not None:
|
||||
embed.add_field(
|
||||
@@ -1281,7 +1281,7 @@ def create_inspect_embed(user, member, join_date, days_on_server, account_age, w
|
||||
else:
|
||||
embed.add_field(name="✅ Historique de modération", value="Aucun incident", inline=False)
|
||||
|
||||
embed.set_footer(text="Mamie Henriette")
|
||||
embed.set_footer(text=f"Demandé par {requester.name}")
|
||||
return embed
|
||||
|
||||
async def get_invite_info_for_user(bot, guild, user_id: int):
|
||||
@@ -1339,7 +1339,8 @@ async def handle_inspect_command(message: Message, bot):
|
||||
warnings,
|
||||
kicks,
|
||||
bans,
|
||||
invite_info
|
||||
invite_info,
|
||||
message.author
|
||||
)
|
||||
|
||||
await message.channel.send(embed=embed)
|
||||
|
||||
@@ -87,6 +87,8 @@ async def sendWelcomeMessage(bot: discord.Client, member: Member):
|
||||
except Exception as e:
|
||||
logging.error(f'Échec de la sauvegarde de l\'invitation : {e}')
|
||||
|
||||
account_age_days = (datetime.now(timezone.utc) - member.created_at).days
|
||||
|
||||
embed = discord.Embed(
|
||||
title='🎉 Nouveau membre !',
|
||||
description=welcome_message,
|
||||
@@ -100,8 +102,10 @@ async def sendWelcomeMessage(bot: discord.Client, member: Member):
|
||||
embed.set_footer(text=f'ID: {member.id}')
|
||||
|
||||
try:
|
||||
await channel.send(embed=embed)
|
||||
message = await channel.send(embed=embed)
|
||||
logging.info(f'Message de bienvenue envoyé pour {member.name}')
|
||||
if account_age_days < 7:
|
||||
await message.add_reaction('⚠️')
|
||||
except Exception as e:
|
||||
logging.error(f'Échec de l\'envoi du message de bienvenue : {e}')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user