diff --git a/VERSION.txt b/VERSION.txt index 0b51dff..4654951 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.5.0 (2ec4a5c) \ No newline at end of file +1.4.0 (536f4e3) \ No newline at end of file diff --git a/blog/models.py b/blog/models.py index 5d0d68c..387d847 100644 --- a/blog/models.py +++ b/blog/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.urls import reverse class Post(models.Model): name = models.CharField(max_length=200) @@ -16,7 +15,4 @@ class Post(models.Model): verbose_name_plural = "Articles" def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse('blog:post_detail', kwargs={'slug': self.slug}) \ No newline at end of file + return self.name \ No newline at end of file diff --git a/courses/apps.py b/courses/apps.py index 9a0a0cd..89f1ba2 100644 --- a/courses/apps.py +++ b/courses/apps.py @@ -1,5 +1,6 @@ from django.apps import AppConfig + class CoursesConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'courses' \ No newline at end of file diff --git a/devart/settings.py b/devart/settings.py index f2dab3b..db43f75 100644 --- a/devart/settings.py +++ b/devart/settings.py @@ -55,8 +55,6 @@ INSTALLED_APPS = [ 'users', 'progression', 'blog', - - 'discord_integration.apps.DiscordIntegrationConfig', ] MIDDLEWARE = [ diff --git a/discord_integration/__init__.py b/discord_integration/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/discord_integration/admin.py b/discord_integration/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/discord_integration/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/discord_integration/apps.py b/discord_integration/apps.py deleted file mode 100644 index 56fe46c..0000000 --- a/discord_integration/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.apps import AppConfig - - -class DiscordIntegrationConfig(AppConfig): - name = 'discord_integration' - - def ready(self): - import discord_integration.signals diff --git a/discord_integration/core/BotClass.py b/discord_integration/core/BotClass.py deleted file mode 100644 index ac9da47..0000000 --- a/discord_integration/core/BotClass.py +++ /dev/null @@ -1,14 +0,0 @@ -import discord -from discord import app_commands - -intents = discord.Intents.default() -intents.members = True -intents.message_content = True - -class Bot(discord.Client): - def __init__(self, *, intents: discord.Intents): - super().__init__(intents=intents) - self.tree = app_commands.CommandTree(self) - - async def setup_hook(self): - await self.tree.sync() \ No newline at end of file diff --git a/discord_integration/core/__init__.py b/discord_integration/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/discord_integration/core/announces_logic.py b/discord_integration/core/announces_logic.py deleted file mode 100644 index 6781202..0000000 --- a/discord_integration/core/announces_logic.py +++ /dev/null @@ -1,57 +0,0 @@ -from asgiref.sync import sync_to_async -import discord -from discord.ext import tasks -from discord_integration.models import DiscordNotification - -def get_pending_notifications(): - return list(DiscordNotification.objects.filter(is_announced=False)) - -def mark_as_done(notif): - notif.is_announced = True - notif.save() - -def process_notifications(): - # Cette fonction fait tout le travail SQL "interdit" en mode async - notifs = list(DiscordNotification.objects.filter(is_announced=False)) - results = [] - for n in notifs: - # Ici, on peut toucher à content_object car on est en mode "sync" ! - obj = n.content_object - if obj: - # 1. On cherche la description, sinon le contenu, sinon rien - teaser = getattr(obj, 'description', getattr(obj, 'content', "")) - - # 2. Sécurité : On coupe à 3000 caractères pour éviter les erreurs Discord - if len(teaser) > 3000: - teaser = teaser[:2997] + "..." - - results.append({ - 'notif_model': n, - 'title': getattr(obj, 'title', getattr(obj, 'name', str(obj))), - 'url': obj.get_absolute_url() if hasattr(obj, 'get_absolute_url') else "#", - 'summary': teaser - }) - - return results - -@tasks.loop(seconds=5.0) -async def check_announcements(client, channel_id): - announcements = await sync_to_async(process_notifications)() - - for data in announcements: - title = data['title'] - link = data['url'] - notif = data['notif_model'] - summary = data['summary'] - - embed = discord.Embed( - title = f"📣 Nouveau contenu : {title}", - url = f"https://partirdezero.com{link}", - description = summary, - color=discord.Color.blue() - ) - - channel_id = client.get_channel(channel_id) - if channel_id: - await channel_id.send(embed=embed) - await sync_to_async(mark_as_done)(notif) diff --git a/discord_integration/core/enums.py b/discord_integration/core/enums.py deleted file mode 100644 index 75c7ced..0000000 --- a/discord_integration/core/enums.py +++ /dev/null @@ -1,16 +0,0 @@ -XP = { - "MESSAGE": 5 -} - -RANK = { - 1: "Nouveau membre", - 3: "Membre", - 7: "Habitué du comptoir", - 12: "Expert", - 18: "Chevalier du code", - 25: "Baron C#", - 35: "Lord Script", - 50: "Héros des architectures", - 75: "Vétéran", - 100: "Légende" -} \ No newline at end of file diff --git a/discord_integration/core/level_logic.py b/discord_integration/core/level_logic.py deleted file mode 100644 index fe42f8f..0000000 --- a/discord_integration/core/level_logic.py +++ /dev/null @@ -1,70 +0,0 @@ -from datetime import datetime, timezone - -from asgiref.sync import sync_to_async -import discord -from datetime import datetime -from discord_integration.models import DiscordLevel -from enums import XP, RANK -from discord import app_commands - -def get_user(id_discord): - user, created = DiscordLevel.objects.get_or_create(discord_id=id_discord) - return user, created - -def update_user_xp(user, xp_to_add): - leveled_up = False - - # 1. Mise à jour de l'XP et du temps - user.total_xp += xp_to_add - user.last_message = datetime.now(timezone.utc) - - # 2. Calcul du niveau théorique - calculated_level = int(0.5 + (0.25 + user.total_xp / 50)**0.5) - - # 3. Vérification du Level Up - if calculated_level > user.level: - user.level = calculated_level - leveled_up = True - - new_rank = RANK[1] - - for level_threshold, rank_name in RANK.items(): - if user.level >= level_threshold: - new_rank = rank_name - else: - break - - user.rank = new_rank - - user.save() - return user, leveled_up - -async def check_add_xp(message, client): - id_discord = message.author.id - username = message.author.name - - user_db, created = await sync_to_async(get_user)(id_discord) - - if not created and user_db.last_message: - delta = datetime.now(timezone.utc) - user_db.last_message - if delta.seconds < 6: - return - - user_db, leveled_up = await sync_to_async(update_user_xp)(user_db, XP["MESSAGE"]) - - if leveled_up: - # On crée un petit message sympa - await message.channel.send( - f"🎊 **LEVEL UP** 🎊\nBravo {message.author.mention}, tu passes **niveau {user_db.level}** ! " - f"On applaudit tous bien fort ! Clap Clap !!" - ) - else: - # Juste un petit log console pour toi - print(f"✨ XP ajouté pour {message.author.name} (Total: {user_db.total_xp})") - -# AJOUT DES COMMANDES /xp et /level -@app_commands.command(name="level", description="Permet de connaitre ton xp actuel") -async def get_xp(interaction: discord.Interaction): - user_id = interaction.user.id - user_db, _ = await sync_to_async(get_user)(user_id) - await interaction.response.send_message(f"{interaction.user.mention} : Tu as {user_db.total_xp} XP et tu es niveau {user_db.level} !\nTon rang est **{user_db.rank}** !") \ No newline at end of file diff --git a/discord_integration/core/main_bot.py b/discord_integration/core/main_bot.py deleted file mode 100644 index 6fa7d4c..0000000 --- a/discord_integration/core/main_bot.py +++ /dev/null @@ -1,73 +0,0 @@ -import discord -import django -import os, sys - -import dotenv -from discord.ext import commands - -# On import django pour communiquer avec -BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -if BASE_DIR not in sys.path: - sys.path.insert(0, BASE_DIR) -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'devart.settings') -django.setup() - -# Import des fonctions -from role_logic import check_role_reaction -from announces_logic import check_announcements -from random_phrase import get_random_phrase -from level_logic import check_add_xp, get_xp -import BotClass - -# CONFIGURATION -TOKEN = dotenv.get_key(BASE_DIR + '/.env', 'D_TOKEN') -MESSAGE_ID = 1450928822156263505 # L'ID du message des règles (clic droit > Copier l'identifiant) -ROLE_ID = 1450920002868875435 # L'ID du rôle "Membres" -ANNOUNCEMENT_CHANNEL_ID = 1450912559774306346 -EMOJI_VALIDATION = "✅" - -# LES INTENTS (PERMISSIONS DU BOT) -intents = discord.Intents.default() -intents.members = True # Important pour pouvoir donner des rôles -intents.message_content = True - -client = BotClass.Bot(intents=intents) -client.tree.add_command(get_random_phrase) -client.tree.add_command(get_xp) - -@client.event -async def on_ready(): - print(f'✅ Bot connecté : {client.user}') - - try: - synced = await client.tree.sync() - print(f"🌍 {len(synced)} commandes slash synchronisées !") - except Exception as e: - print(f"❌ Erreur de synchronisation : {e}") - - if not check_announcements.is_running(): - check_announcements.start(client, ANNOUNCEMENT_CHANNEL_ID) - -@client.event -async def on_message(message): - if message.author == client.user: - return - if message.guild is None: - author = message.author - await message.channel.send("Bonjour !\nJe suis un bot destiné à tester les nouvelles fonctionnalités de Discord. Pour le moment, je suis qu'en lecture seule.") - else: - await check_add_xp(message, client) - - -@client.event -async def on_raw_reaction_add(payload): - # On envoie tout le nécessaire à notre fonction dans role_logic.py - await check_role_reaction( - payload, - client, - MESSAGE_ID, - ROLE_ID, - EMOJI_VALIDATION - ) - -client.run(TOKEN) diff --git a/discord_integration/core/random_phrase.py b/discord_integration/core/random_phrase.py deleted file mode 100644 index e8cfea2..0000000 --- a/discord_integration/core/random_phrase.py +++ /dev/null @@ -1,32 +0,0 @@ -import discord -import random - -from discord import app_commands - -phrase = [ - "Yo !", - "Quoi de neuf ?", - "Je suis occupé à compter mes octets.", - "Vive la Belgique ! 🇧🇪", - "C’est pas un bug, c’est une fonctionnalité non documentée ! 🐛", - "Est-ce qu’on peut dire que mon code est une œuvre d’art ? Non ? Dommage.", - "Je ne plante pas, je fais une pause créative.", - "Quelqu’un a vu mon point-virgule ? Il a disparu depuis le dernier commit.", - "Je mangerais bien une mitraillette sauce andalouse, mais mon système digestif est en 404. 🍟", - "42. Voilà. Maintenant, pose-moi une vraie question.", - "On mange quoi ? Ah non, c'est vrai, je suis un robot... Tristesse infinie. 🤖", - "C'est écrit en Python, donc c'est forcément élégant, non ?", - "Un petit café ? Pour moi, une petite dose d'électricité suffira.", - "Je parie que tu n'as pas encore fait ton `git push` aujourd'hui. Je te surveille ! 👀", - "En Belgique, on n'a peut-être pas toujours du soleil, mais on a les meilleures frites ! 🇧🇪🍟", - "Il y a 10 types de personnes : celles qui comprennent le binaire, et les autres.", - "Mon processeur chauffe... soit je réfléchis trop, soit ton code est trop complexe !", - "Tout va bien, tant que personne ne touche au dossier `migrations` de Django...", - "Sais-tu pourquoi les développeurs détestent la nature ? Parce qu'il y a trop de bugs. 🌳", - "On n'est pas là pour trier des lentilles, une fois ! On code ou quoi ? 🇧🇪" -] - -@app_commands.command(name="random_phrase", description="Envoi une phrase aléatoire !") -async def get_random_phrase(interaction: discord.Interaction): - choice = random.choice(phrase) - await interaction.response.send_message(choice) \ No newline at end of file diff --git a/discord_integration/core/role_logic.py b/discord_integration/core/role_logic.py deleted file mode 100644 index 8dd2dbc..0000000 --- a/discord_integration/core/role_logic.py +++ /dev/null @@ -1,39 +0,0 @@ -import discord - - -async def check_role_reaction(payload, client, target_message_id, target_role_id, target_emoji): - # 1. On vérifie si c'est le bon message - if payload.message_id != target_message_id: - return # On ignore si ce n'est pas le bon message - - # 2. On vérifie si c'est le bon emoji - if str(payload.emoji) == target_emoji: - guild = client.get_guild(payload.guild_id) - if guild is None: - print("Erreur: Impossible de trouver le serveur (Guild is None).") - return - - member = guild.get_member(payload.user_id) - if member is None: - print("Erreur: Impossible de trouver le membre (Member is None).") - return - - role = guild.get_role(target_role_id) - if role is None: - print("Erreur : Le role n'existe pas.") - return - - try: - await member.add_roles(role) - print(f"🎉 SUCCÈS : Rôle donné à {member.name} !") - try: - await member.send("Bienvenue ! Tu as accès aux salons.") - except: - print("Note: MP bloqués par l'utilisateur.") - except discord.Forbidden: - print("⛔ ERREUR PERMISSION : Je n'ai pas le droit de donner ce rôle !") - print( - "👉 SOLUTION : Va dans Paramètres Serveur > Rôles. Glisse le rôle 'PartirDeZero Bot' AU-DESSUS du rôle 'Membres'.") - - except Exception as e: - print(f"❌ Erreur inconnue : {e}") \ No newline at end of file diff --git a/discord_integration/migrations/0001_initial.py b/discord_integration/migrations/0001_initial.py deleted file mode 100644 index 6c269a9..0000000 --- a/discord_integration/migrations/0001_initial.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 6.0 on 2025-12-18 08:23 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ] - - operations = [ - migrations.CreateModel( - name='DiscordNotification', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('is_announced', models.BooleanField(default=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ], - ), - ] diff --git a/discord_integration/migrations/0002_discordlevel.py b/discord_integration/migrations/0002_discordlevel.py deleted file mode 100644 index b35f4b8..0000000 --- a/discord_integration/migrations/0002_discordlevel.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 6.0 on 2025-12-18 11:57 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('discord_integration', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='DiscordLevel', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('discord_id', models.BigIntegerField()), - ('total_xp', models.PositiveIntegerField(default=0)), - ('level', models.PositiveIntegerField(default=1)), - ('rank', models.TextField(default='Nouveau membre')), - ('last_message', models.DateTimeField(auto_now_add=True)), - ], - ), - ] diff --git a/discord_integration/migrations/__init__.py b/discord_integration/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/discord_integration/models.py b/discord_integration/models.py deleted file mode 100644 index 614a096..0000000 --- a/discord_integration/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.contrib.contenttypes.fields import GenericForeignKey -from django.db import models -from django.contrib.contenttypes.models import ContentType - -class DiscordNotification(models.Model): - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') - is_announced = models.BooleanField(default=False) - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return f"Annonces pour {self.content_object} ({'✅' if self.is_announced else '⏳'})" - -class DiscordLevel(models.Model): - discord_id = models.BigIntegerField() - total_xp = models.PositiveIntegerField(default=0) - level = models.PositiveIntegerField(default=1) - rank = models.TextField(default="Nouveau membre") - last_message = models.DateTimeField(auto_now_add=True) \ No newline at end of file diff --git a/discord_integration/signals.py b/discord_integration/signals.py deleted file mode 100644 index cd5897e..0000000 --- a/discord_integration/signals.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver -from .models import DiscordNotification - -@receiver(post_save, sender="blog.Post") -def create_discord_notification_blog(sender, instance, created, **kwargs): - if created: - DiscordNotification.objects.create(content_object=instance) - -@receiver(post_save, sender="courses.Course") -def create_discord_notification_course(sender, instance, created, **kwargs): - if created: - DiscordNotification.objects.create(content_object=instance) - -@receiver(post_save, sender="courses.Lesson") -def create_discord_notification_lesson(sender, instance, created, **kwargs): - if created: - DiscordNotification.objects.create(content_object=instance) \ No newline at end of file diff --git a/discord_integration/tests.py b/discord_integration/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/discord_integration/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/discord_integration/views.py b/discord_integration/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/discord_integration/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/templates/partials/_footer.html b/templates/partials/_footer.html index 42cc419..934a5e3 100644 --- a/templates/partials/_footer.html +++ b/templates/partials/_footer.html @@ -29,7 +29,6 @@ {% if settings.contact_email %}