fix: gpt-oss:20b ollama, streaming, tableaux JSON, recherche flexible salons/categories

This commit is contained in:
pi 2026-06-13 14:46:02 +00:00
parent 14985f6dbb
commit 189d56026b
21 changed files with 2824 additions and 491 deletions

View file

@ -4,7 +4,7 @@ from datetime import datetime
from .config import config
class ActionLogger:
def __init__(self, log_file: str = 'actions.log'):
def __init__(self, log_file: str = 'data/actions.log'):
self.log_file = log_file
self.logger = logging.getLogger('ActionLogger')
self.logger.setLevel(logging.INFO)

View file

@ -1,15 +1,52 @@
import discord
import re
def strip_emoji(text):
"""Remove emoji characters for fuzzy matching."""
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF"
u"\U0001F1E0-\U0001F1FF\U00002702-\U000027B0\U000024C2-\U0001F251"
u"\U0000FE00-\U0000FE0F\U0000200D\U0000E0100-\U0000E01EFF"
u"\u20E3\u00A9\u00AE\u2122"
"]+", flags=re.UNICODE)
return emoji_pattern.sub('', text).strip()
async def find_category(guild, category_name_or_id):
"""Find a category by ID or name (exact then fuzzy)."""
# 1. Try by ID
try:
cat_id = int(category_name_or_id)
cat = guild.get_channel(cat_id)
if cat and isinstance(cat, discord.CategoryChannel):
return cat
except (ValueError, TypeError):
pass
# 2. Exact name
for cat in guild.categories:
if cat.name.casefold() == category_name_or_id.casefold():
return cat
# 3. Fuzzy (strip emoji)
clean_search = strip_emoji(category_name_or_id).lower().strip()
for cat in guild.categories:
clean_name = strip_emoji(cat.name).lower().strip()
if clean_name == clean_search or (clean_search and clean_search in clean_name):
return cat
return None
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
# Fallback for single category
categories = [params]
guild = message.guild
deleted_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
category = discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
category_name = category_params.get('category_name') or category_params.get('category_id')
if not category_name:
continue
category = await find_category(guild, category_name)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
continue
@ -19,4 +56,4 @@ async def execute(bot, params, message):
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour supprimer la catégorie '{category_name}'.")
if deleted_count > 0:
await message.channel.send(f"{deleted_count} catégorie(s) supprimée(s).")
await message.channel.send(f"{deleted_count} catégorie(s) supprimée(s).")

View file

@ -1,15 +1,66 @@
import discord
import re
def strip_emoji(text):
"""Remove emoji characters from channel/role names for fuzzy matching."""
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002702-\U000027B0" # dingbats
u"\U000024C2-\U0001F251" # misc
u"\U0000FE00-\U0000FE0F" # variation selectors
u"\U0000200D" # zero width joiner
u"\U0000E0100-\U0000E01EFF" # variation selectors supplement
u"\u20E3" # combining enclosing keycap
u"\u00A9\u00AE\u2122" # copyright, registered, trademark
"]+", flags=re.UNICODE)
return emoji_pattern.sub('', text).strip()
async def find_channel(guild, channel_name_or_id):
"""Find a channel by ID first, then by name (exact, then flexible)."""
# 1. Try by ID
try:
ch_id = int(channel_name_or_id)
channel = guild.get_channel(ch_id)
if channel:
return channel
except (ValueError, TypeError):
pass
# 2. Try exact name match (case-insensitive)
for ch in guild.channels:
if ch.name.casefold() == channel_name_or_id.casefold():
return ch
# 3. Try fuzzy match (strip emojis from both sides)
clean_search = strip_emoji(channel_name_or_id).lower().strip()
for ch in guild.channels:
clean_name = strip_emoji(ch.name).lower().strip()
if clean_name == clean_search:
return ch
# 4. If the search name is contained in the channel name
if clean_search and clean_search in clean_name:
return ch
# 5. Or the channel name is contained in the search
if clean_search and clean_name in clean_search:
return ch
return None
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
deleted_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
# Support both channel_id and channel_name
channel_name = channel_params.get('channel_name') or channel_params.get('channel_id')
if not channel_name:
continue # skip if no name or id
channel = await find_channel(guild, channel_name)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
continue

View file

@ -9,7 +9,7 @@ import json
import os
from datetime import datetime
WARNINGS_FILE = 'warnings.json'
WARNINGS_FILE = 'data/warnings.json'
def load_warnings():
"""Charge les avertissements depuis le fichier JSON"""

View file

@ -2,7 +2,7 @@ import discord
import json
import os
WARNINGS_FILE = 'warnings.json'
WARNINGS_FILE = 'data/warnings.json'
def load_warnings():
if os.path.exists(WARNINGS_FILE):

View file

@ -4,7 +4,7 @@ import os
import aiohttp
from datetime import datetime
WARNINGS_FILE = 'warnings.json'
WARNINGS_FILE = 'data/warnings.json'
def load_warnings():
if os.path.exists(WARNINGS_FILE):
@ -123,7 +123,9 @@ async def execute(bot, params, message):
target_mention = warn_params.get('target_user_mention')
original_reason = warn_params.get('reason', 'Aucune raison spécifiée')
# Rephrase the reason using Ollama
rephrased_reason = await rephrase_reason(original_reason, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
ollama_url = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") + "/api/generate"
ollama_model = os.environ.get("OLLAMA_MODEL", "gpt-oss:20b")
rephrased_reason = await rephrase_reason(original_reason, ollama_url, "", ollama_model, 30, 0.7)
reason = rephrased_reason if rephrased_reason else original_reason
# Parse mention or ID
if target_mention.startswith('<@') and target_mention.endswith('>'):
@ -168,7 +170,7 @@ async def execute(bot, params, message):
)
await message.channel.send(embed=embed)
# Generate full DM message using AI with original reason for polite reformulation
dm_message = await generate_dm_message(original_reason, warning_count, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
dm_message = await generate_dm_message(original_reason, warning_count, ollama_url, "", ollama_model, 30, 0.7)
dm_content = dm_message if dm_message else f"Avertissement : {reason}. Nombre total : {warning_count}."
# Send DM to user
try: