Compare commits

..

No commits in common. "f15b12af2665a0de484a4181ee66754e0abcedbf" and "d955a7a80115978513a45f39bfe0a660e9ad63f2" have entirely different histories.

7 changed files with 94 additions and 162 deletions

View File

@ -1,33 +0,0 @@
name: Build and publish Docker image
on:
push:
branches:
- master
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive registry host and image name
id: registry
run: |
host="$(printf '%s' "${{ gitea.server_url }}" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#/.*$##')"
image="$(printf '%s' "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')"
echo "host=${host}" >> "$GITHUB_OUTPUT"
echo "image=${image}" >> "$GITHUB_OUTPUT"
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ steps.registry.outputs.host }}
username: ${{ gitea.repository_owner }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push image
run: |
docker build -t "${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.image }}:latest" .
docker push "${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.image }}:latest"

View File

@ -4,22 +4,29 @@ FROM python:3.9-slim
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app
# Install Python dependencies # Install git and clean up
COPY requirements.txt . RUN apt-get update -q && \
RUN pip install --no-cache-dir -r requirements.txt apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Copy application code # Create data directory with proper permissions
COPY main.py fouras.py rhymes.py database.py ./
COPY resources/ ./resources/
# Create data directory for persistent state
RUN mkdir -p /app/data && chmod 755 /app/data RUN mkdir -p /app/data && chmod 755 /app/data
# Ensure Python output is not buffered (useful for logs) # Set safe directory
ENV PYTHONUNBUFFERED=1 RUN git config --global --add safe.directory '*'
# Clone the repository
RUN git init /app && git remote add origin https://git.epicsparrow.com/Anselme/perefouras.git && git fetch --depth 1 origin master && git reset --hard FETCH_HEAD
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# give exec permissions to main
RUN chmod +x main.py
# Set data directory as volume mount point # Set data directory as volume mount point
VOLUME /app/data VOLUME /app/data
# Run the bot # Command to update the repo and run the script
CMD ["python", "main.py"] CMD ["sh", "-c", "cd /app && git fetch --depth 1 origin master && git reset --hard FETCH_HEAD && python main.py"]

View File

@ -3,7 +3,7 @@ import sqlite3
import random import random
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict, Tuple
DB_FILE = "data/database.db" DB_FILE = "data/database.db"
@ -52,7 +52,6 @@ SET nb_clues = nb_clues + 1
DELETE_RIDDLE_QUERY = "DELETE FROM active_riddles WHERE channel_id = ?" DELETE_RIDDLE_QUERY = "DELETE FROM active_riddles WHERE channel_id = ?"
def ensure_db() -> None: def ensure_db() -> None:
"""Initialize SQLite database and create tables if needed.""" """Initialize SQLite database and create tables if needed."""
Path(DB_FILE).parent.mkdir(parents=True, exist_ok=True) Path(DB_FILE).parent.mkdir(parents=True, exist_ok=True)
@ -114,13 +113,7 @@ def get_guild_state(guild_id: str) -> Dict[str, Any]:
def rhyme_configure_cooldown_ratio(guild_id: str, cooldown_ratio: float) -> bool: def rhyme_configure_cooldown_ratio(guild_id: str, cooldown_ratio: float) -> bool:
with get_connection() as conn: with get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute( cursor.execute(CONFIGURE_COOLDOWN_RATIO_QUERY, (cooldown_ratio, guild_id,))
CONFIGURE_COOLDOWN_RATIO_QUERY,
(
cooldown_ratio,
guild_id,
),
)
conn.commit() conn.commit()
@ -131,36 +124,25 @@ def rhyme_damage_self_control(guild_id: str) -> None:
conn.commit() conn.commit()
def rhyme_reset_cooldown(guild_id: str, ratio: float) -> None: def rhyme_reset_cooldown(
guild_id: str,
ratio: float
) -> None:
with get_connection() as conn: with get_connection() as conn:
# Calculate new cooldown duration # Calculate new cooldown duration
wait_time = ( wait_time = random.randint(900, 10800) if bool(random.getrandbits(1)) else random.randint(0, 900)
random.randint(900, 10800)
if bool(random.getrandbits(1))
else random.randint(0, 900)
)
wait_time = 0 if ratio < 0 else int(wait_time * ratio) wait_time = 0 if ratio < 0 else int(wait_time * ratio)
cooldown_until = datetime.now().replace(second=0, microsecond=0) + timedelta( cooldown_until = datetime.now().replace(second=0, microsecond=0) + timedelta(seconds=wait_time)
seconds=wait_time
)
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute( cursor.execute(RESET_COOLDOWN_QUERY, (cooldown_until.isoformat(), guild_id,))
RESET_COOLDOWN_QUERY,
(
cooldown_until.isoformat(),
guild_id,
),
)
conn.commit() conn.commit()
def get_active_riddle(channel_id: str) -> Any: def get_active_riddle(channel_id: str) -> Any:
with get_connection() as conn: with get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute( cursor.execute("SELECT * FROM active_riddles WHERE channel_id = ?", (str(channel_id),))
"SELECT * FROM active_riddles WHERE channel_id = ?", (str(channel_id),)
)
row = cursor.fetchone() row = cursor.fetchone()
if row: if row:
@ -185,20 +167,14 @@ def save_active_riddle(
) -> None: ) -> None:
with get_connection() as conn: with get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute( cursor.execute(SAVE_RIDDLE_QUERY, (channel_id, riddle_index, nb_clues, message_id, solver_id))
SAVE_RIDDLE_QUERY,
(channel_id, riddle_index, nb_clues, message_id, solver_id),
)
conn.commit() conn.commit()
def increment_clue(channel_id: str) -> None: def increment_clue(channel_id: str) -> None:
with get_connection() as conn: with get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute( cursor.execute(INCREMENT_CLUE_QUERY, (channel_id,),)
INCREMENT_CLUE_QUERY,
(channel_id,),
)
conn.commit() conn.commit()

View File

@ -1,6 +1,8 @@
services: services:
perefouras: perefouras:
image: git.epicsparrow.com/anselme/perefouras:latest build:
context: .
dockerfile: Dockerfile
environment: environment:
- DISCORD_TOKEN=${FOURAS_DISCORD_TOKEN} - DISCORD_TOKEN=${FOURAS_DISCORD_TOKEN}
- MAINTAINER_ID=${FOURAS_MAINTAINER_ID:-151626081458192384} - MAINTAINER_ID=${FOURAS_MAINTAINER_ID:-151626081458192384}

View File

@ -166,7 +166,7 @@ def get_current_riddle(channel_id):
"nbClues": riddle_data["nb_clues"], "nbClues": riddle_data["nb_clues"],
"riddle": riddles[riddle_data["riddle_index"]].strip(), "riddle": riddles[riddle_data["riddle_index"]].strip(),
"answer": answers[riddle_data["riddle_index"]], "answer": answers[riddle_data["riddle_index"]],
"data": riddle_data, "data": riddle_data
} }
else: else:
return None return None
@ -174,7 +174,7 @@ def get_current_riddle(channel_id):
async def handle_riddle_solving(message, client: discord.Client) -> bool: async def handle_riddle_solving(message, client: discord.Client) -> bool:
current_riddle = get_current_riddle(message.channel.id) current_riddle = get_current_riddle(message.channel.id)
if not current_riddle: if(not current_riddle):
return False return False
answer = current_riddle["answer"] answer = current_riddle["answer"]
riddle_data = current_riddle["data"] riddle_data = current_riddle["data"]
@ -190,7 +190,9 @@ async def handle_riddle_solving(message, client: discord.Client) -> bool:
solver_id=solver_id, solver_id=solver_id,
) )
await message.reply(SUCCESS.format(user=message.author.mention, answer=answer)) await message.reply(
SUCCESS.format(user=message.author.mention, answer=answer)
)
try: try:
original_msg = await message.channel.fetch_message( original_msg = await message.channel.fetch_message(
@ -276,7 +278,7 @@ async def new_riddle(interaction: Interaction, index: int) -> None:
riddle_index=index, riddle_index=index,
nb_clues=-1, nb_clues=-1,
message_id=msg.message_id, message_id=msg.message_id,
solver_id=None, solver_id=None
) )
@ -290,20 +292,23 @@ async def update_riddle_message(channel, current_riddle):
def setup_commands(client: discord.Client, tree) -> None: def setup_commands(client: discord.Client, tree) -> None:
@tree.command(name="fouras", description="Demander une énigme au Père Fouras") @tree.command(
name="fouras",
description="Demander une énigme au Père Fouras"
)
async def fouras( async def fouras(
interaction: discord.Interaction, interaction: discord.Interaction,
): ):
if len(riddles) == 0: if len(riddles) == 0:
await interaction.response.send_message( await interaction.response.send_message("Désolé, mais j'ai oublié mes énigmes", ephemeral=True)
"Désolé, mais j'ai oublié mes énigmes", ephemeral=True
)
else: else:
index = random.randint(0, len(riddles) - 1) index = random.randint(0, len(riddles) - 1)
await new_riddle(interaction, index) await new_riddle(interaction, index)
@tree.command( @tree.command(
name="requete", description="Demander une énigme spécifique au Père Fouras" name="requete",
description="Demander une énigme spécifique au Père Fouras"
) )
@app_commands.describe(index="Numéro de l'énigme") @app_commands.describe(index="Numéro de l'énigme")
async def requete( async def requete(
@ -312,24 +317,21 @@ def setup_commands(client: discord.Client, tree) -> None:
): ):
index = index - 1 index = index - 1
if not (0 <= index < len(riddles)): if not (0 <= index < len(riddles)):
await interaction.response.send_message( await interaction.response.send_message(INVALID_ID.format(len=len(riddles)+1), ephemeral=True)
INVALID_ID.format(len=len(riddles) + 1), ephemeral=True
)
return return
await new_riddle(interaction, index) await new_riddle(interaction, index)
@tree.command( @tree.command(
name="repete", name="repete",
description="Demander au Père Fouras de répéter l'énigme en cours", description="Demander au Père Fouras de répéter l'énigme en cours"
) )
async def repete( async def repete(
interaction: discord.Interaction, interaction: discord.Interaction,
): ):
current_riddle = get_current_riddle(interaction.channel.id) current_riddle = get_current_riddle(interaction.channel.id)
if not current_riddle: if(not current_riddle):
await interaction.response.send_message( await interaction.response.send_message("Aucune énigme en cours", ephemeral=True)
"Aucune énigme en cours", ephemeral=True
)
return return
riddle_data = current_riddle["data"] riddle_data = current_riddle["data"]
msg = await interaction.response.send_message( msg = await interaction.response.send_message(
@ -343,17 +345,17 @@ def setup_commands(client: discord.Client, tree) -> None:
solver_id=riddle_data["solver_id"], solver_id=riddle_data["solver_id"],
) )
@tree.command( @tree.command(
name="indice", description="Demander un indice pour l'énigme en cours" name="indice",
description="Demander un indice pour l'énigme en cours"
) )
async def indice( async def indice(
interaction: discord.Interaction, interaction: discord.Interaction,
): ):
current_riddle = get_current_riddle(interaction.channel.id) current_riddle = get_current_riddle(interaction.channel.id)
if not current_riddle: if(not current_riddle):
await interaction.response.send_message( await interaction.response.send_message("Aucune énigme en cours", ephemeral=True)
"Aucune énigme en cours", ephemeral=True
)
return return
answer = current_riddle["answer"] answer = current_riddle["answer"]
riddle_data = current_riddle["data"] riddle_data = current_riddle["data"]
@ -364,7 +366,7 @@ def setup_commands(client: discord.Client, tree) -> None:
await interaction.response.send_message( await interaction.response.send_message(
"Perdu ! La réponse était : `{0}`".format(answer) "Perdu ! La réponse était : `{0}`".format(answer)
) )
if not await update_riddle_message(interaction.channel, current_riddle): if(not await update_riddle_message(interaction.channel, current_riddle)):
return return
await finish_riddle(interaction.channel, client) await finish_riddle(interaction.channel, client)
else: else:
@ -375,8 +377,6 @@ def setup_commands(client: discord.Client, tree) -> None:
message_id=riddle_data["message_id"], message_id=riddle_data["message_id"],
solver_id=riddle_data["solver_id"], solver_id=riddle_data["solver_id"],
) )
if not await update_riddle_message(interaction.channel, current_riddle): if(not await update_riddle_message(interaction.channel, current_riddle)):
return return
await interaction.response.send_message( await interaction.response.send_message("Nouvel indice : `{0}`".format(clue_string(answer, nb_clues)))
"Nouvel indice : `{0}`".format(clue_string(answer, nb_clues))
)

View File

@ -76,12 +76,7 @@ async def handle_rhyme_logic(message, client) -> bool:
message_content = message.content.lower() message_content = message.content.lower()
last_word = get_last_word(message_content) last_word = get_last_word(message_content)
if ( if not message.author.bot and message.author.id != client.user.id and message.guild and last_word:
not message.author.bot
and message.author.id != client.user.id
and message.guild
and last_word
):
rhyme = find_rhyme(last_word) rhyme = find_rhyme(last_word)
guild_id = str(message.guild.id) guild_id = str(message.guild.id)
@ -115,27 +110,23 @@ async def handle_message(message, client: discord.Client) -> bool:
return await handle_rhyme_logic(message, client) return await handle_rhyme_logic(message, client)
def setup_rhymes_commands( def setup_rhymes_commands(client: discord.Client, tree: discord.app_commands.CommandTree) -> None:
client: discord.Client, tree: discord.app_commands.CommandTree
) -> None:
@tree.command( @tree.command(
name="config", name="config",
description="Permet de configurer à quel point le Père Fouras est relou", description="Permet de configurer à quel point le Père Fouras est relou"
) )
@app_commands.default_permissions(manage_guild=True) @app_commands.default_permissions(manage_guild=True)
@app_commands.checks.has_permissions(manage_guild=True) @app_commands.checks.has_permissions(manage_guild=True)
@app_commands.describe(ratio="ratio de cooldown (0 = )") @app_commands.describe(ratio="ratio de cooldown (0 = )")
async def config(interaction: discord.Interaction, ratio: float): async def config(
interaction: discord.Interaction,
ratio: float
):
guild_id: str = str(interaction.guild_id) guild_id: str = str(interaction.guild_id)
guild_state = database.get_guild_state(guild_id) guild_state = database.get_guild_state(guild_id)
database.rhyme_configure_cooldown_ratio(guild_id, ratio) database.rhyme_configure_cooldown_ratio(guild_id, ratio)
msg = f"Le ratio de cooldown passe de {guild_state['cooldown_ratio']:.2f} à {ratio:.2f} pour le serveur {interaction.guild.name}" msg = f"Le ratio de cooldown passe de {guild_state['cooldown_ratio']:.2f} à {ratio:.2f} pour le serveur {interaction.guild.name}"
if ratio < 0: if ratio < 0:
await interaction.response.send_message( await interaction.response.send_message(f"{msg}\nLes \"poil au\" sont désormais désactivés.", ephemeral=True)
f'{msg}\nLes "poil au" sont désormais désactivés.', ephemeral=True
)
else: else:
await interaction.response.send_message( await interaction.response.send_message(f"{msg}\nLes \"poil au\" ont désormais un cooldown entre 0 et {int(180*ratio)} minutes.", ephemeral=True)
f'{msg}\nLes "poil au" ont désormais un cooldown entre 0 et {int(180 * ratio)} minutes.',
ephemeral=True,
)

View File

@ -1,58 +1,47 @@
```python
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json import json
riddles = [] riddles = []
def scrape_year_data(scrap_year): def scrape_year_data(scrap_year):
base_url = "https://www.fan-fortboyard.fr/pages/fanzone/enigmes-du-pere-fouras/" base_url = 'https://www.fan-fortboyard.fr/pages/fanzone/enigmes-du-pere-fouras/'
if scrap_year > 2019: if(scrap_year > 2019):
url = f"{base_url}enigmes-{scrap_year}.html" # Replace with the actual URL pattern url = f'{base_url}enigmes-{scrap_year}.html' # Replace with the actual URL pattern
else: else:
url = f"{base_url}{scrap_year}.html" # Replace with the actual URL pattern url = f'{base_url}{scrap_year}.html' # Replace with the actual URL pattern
response = requests.get(url) response = requests.get(url)
if response.status_code == 200: if response.status_code == 200:
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, 'html.parser')
paragraphs = soup.find_all("p") paragraphs = soup.find_all('p')
for paragraph in paragraphs: for paragraph in paragraphs:
span = paragraph.find("span") span = paragraph.find('span')
if span and span.find("strong"): if span and span.find('strong'):
strong_tag = span.find("strong") strong_tag = span.find('strong')
if strong_tag and strong_tag.text.strip().isdigit(): if strong_tag and strong_tag.text.strip().isdigit():
riddle_count = strong_tag.text.strip() riddle_count = strong_tag.text.strip()
answer_span = paragraph.find_all("span", style=True) answer_span = paragraph.find_all('span', style=True)
if answer_span: if answer_span:
answer_text = answer_span[-1].get_text(strip=True) answer_text = answer_span[-1].get_text(strip=True)
riddle_count = strong_tag.text.strip() riddle_count = strong_tag.text.strip()
riddle_text = paragraph.decode_contents() riddle_text = paragraph.decode_contents()
riddle_text = riddle_text.replace("<br/>", "\n").replace( riddle_text = riddle_text.replace('<br/>', '\n').replace('<br>', '\n')
"<br>", "\n" riddle_text = BeautifulSoup(riddle_text, 'html.parser').get_text(strip=True)
) riddle_text = riddle_text.split(')', 1)[-1].strip()
riddle_text = BeautifulSoup(
riddle_text, "html.parser"
).get_text(strip=True)
riddle_text = riddle_text.split(")", 1)[-1].strip()
if "RÉPONSE" in riddle_text: if "RÉPONSE" in riddle_text:
riddle_text = riddle_text.split("RÉPONSE", 1)[0].strip() riddle_text = riddle_text.split("RÉPONSE", 1)[0].strip()
riddles.append( riddles.append({'riddle': riddle_text, 'answer': answer_text, 'count': riddle_count, 'year': scrap_year})
{
"riddle": riddle_text,
"answer": answer_text,
"count": riddle_count,
"year": scrap_year,
}
)
else: else:
print(f"No answer found : {scrap_year}-{riddle_count}") print(f'No answer found : {scrap_year}-{riddle_count}')
else: else:
print(f"Failed to retrieve data for year {year}: {response.status_code}") print(f'Failed to retrieve data for year {year}: {response.status_code}')
for year in range(2007, 2023): for year in range(2007, 2023):
scrape_year_data(year) scrape_year_data(year)
with open("riddles.json", "w", encoding="utf-8") as json_file: with open('riddles.json', 'w', encoding='utf-8') as json_file:
json.dump(riddles, json_file, indent=4, ensure_ascii=False) json.dump(riddles, json_file, indent=4, ensure_ascii=False)
```