Initial commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
settings.py
|
||||||
|
grammalecte.txt
|
||||||
|
wordlists
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.8.0
|
||||||
0
__init__.py
Normal file
0
__init__.py
Normal file
0
cambot/__init__.py
Normal file
0
cambot/__init__.py
Normal file
394
cambot/codenames.py
Normal file
394
cambot/codenames.py
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
import random
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
from collections import Counter
|
||||||
|
from itertools import cycle
|
||||||
|
from enum import auto
|
||||||
|
from .emojis import *
|
||||||
|
from .utils import serialize, EnumZero
|
||||||
|
from .settings import *
|
||||||
|
|
||||||
|
class Teams(EnumZero):
|
||||||
|
RED = auto()
|
||||||
|
BLUE = auto()
|
||||||
|
NEUTRAL = auto()
|
||||||
|
BOMB = auto()
|
||||||
|
|
||||||
|
class Roles(EnumZero):
|
||||||
|
CHOOSER = auto()
|
||||||
|
GUESSER = auto()
|
||||||
|
|
||||||
|
class Recipients(EnumZero):
|
||||||
|
EVERYONE = auto()
|
||||||
|
CHOOSERS = auto()
|
||||||
|
|
||||||
|
class Modes(EnumZero):
|
||||||
|
COOPERATION = auto()
|
||||||
|
COMPETITION = auto()
|
||||||
|
|
||||||
|
class Statuses(EnumZero):
|
||||||
|
RED_CHOOSER = auto()
|
||||||
|
RED_GUESSER = auto()
|
||||||
|
BLUE_CHOOSER = auto()
|
||||||
|
BLUE_GUESSER = auto()
|
||||||
|
NEUTRAL_CHOOSER = auto()
|
||||||
|
NEUTRAL_GUESSER = auto()
|
||||||
|
|
||||||
|
CYCLES = {Modes.COOPERATION: cycle([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER]),
|
||||||
|
Modes.COMPETITION: cycle([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER])}
|
||||||
|
|
||||||
|
CONFIGURATIONS = [Counter([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER]), # coop
|
||||||
|
Counter([Statuses.NEUTRAL_CHOOSER, Statuses.NEUTRAL_GUESSER, Statuses.NEUTRAL_GUESSER]), # coop
|
||||||
|
Counter([Statuses.RED_CHOOSER, Statuses.BLUE_CHOOSER, Statuses.NEUTRAL_GUESSER]),
|
||||||
|
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER]),
|
||||||
|
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER, Statuses.NEUTRAL_GUESSER]),
|
||||||
|
Counter([Statuses.RED_CHOOSER, Statuses.RED_GUESSER, Statuses.RED_GUESSER, Statuses.BLUE_CHOOSER, Statuses.BLUE_GUESSER, Statuses.BLUE_GUESSER])]
|
||||||
|
|
||||||
|
SYMBOLS = {Statuses.RED_CHOOSER: f"{REDDOT}{SPEAKING}",
|
||||||
|
Statuses.RED_GUESSER: f"{REDDOT}{GLASS}",
|
||||||
|
Statuses.BLUE_CHOOSER: f"{BLUEDOT}{SPEAKING}",
|
||||||
|
Statuses.BLUE_GUESSER: f"{BLUEDOT}{GLASS}",
|
||||||
|
Statuses.NEUTRAL_CHOOSER: f"{WHITEDOT}{SPEAKING}",
|
||||||
|
Statuses.NEUTRAL_GUESSER: f"{WHITEDOT}{GLASS}"}
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, channel):
|
||||||
|
self.channel = channel
|
||||||
|
self.reset(keep_players=False)
|
||||||
|
|
||||||
|
async def say(self, message):
|
||||||
|
await self.channel.send(message)
|
||||||
|
|
||||||
|
def get_player(self, client):
|
||||||
|
try:
|
||||||
|
player = next(p for p in self.players if p.client == client)
|
||||||
|
except StopIteration:
|
||||||
|
player = None
|
||||||
|
return player
|
||||||
|
|
||||||
|
def current_team(self):
|
||||||
|
return Teams(self.current_step.value // 2)
|
||||||
|
|
||||||
|
def current_role(self):
|
||||||
|
return Roles(self.current_step.value % 2)
|
||||||
|
|
||||||
|
def reset(self, keep_players=True):
|
||||||
|
self.playing = False
|
||||||
|
self.mode = None
|
||||||
|
self.steps = None
|
||||||
|
self.current_step = None
|
||||||
|
if not keep_players:
|
||||||
|
self.players = []
|
||||||
|
self.words = []
|
||||||
|
self.clue = None
|
||||||
|
self.number = None
|
||||||
|
self.turn = 0
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, client, team, role):
|
||||||
|
self.client = client
|
||||||
|
self.team = team
|
||||||
|
self.role = role
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
return Statuses((2 * self.team.value) + self.role.value)
|
||||||
|
|
||||||
|
class Word:
|
||||||
|
def __init__(self, label, team):
|
||||||
|
self.label = label
|
||||||
|
self.team = team
|
||||||
|
self.guessed = False
|
||||||
|
|
||||||
|
async def maybe_join(game, message, team):
|
||||||
|
if game.playing:
|
||||||
|
await game.say(f"{message.author.mention} Une partie est en cours, impossible de rejoindre une équipe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len([p for p in game.players if p.team == team]) > 0:
|
||||||
|
role = Roles.GUESSER
|
||||||
|
else:
|
||||||
|
role = Roles.CHOOSER
|
||||||
|
|
||||||
|
player = game.get_player(message.author)
|
||||||
|
if player:
|
||||||
|
if player.team == team:
|
||||||
|
await game.say(f"{message.author.mention} Tu fais déjà partie de cette équipe.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
player.team = team
|
||||||
|
player.role = role
|
||||||
|
else:
|
||||||
|
player = Player(client=message.author, team=team, role=role)
|
||||||
|
game.players.append(player)
|
||||||
|
|
||||||
|
await game.say(f"{message.author.mention} Équipe {('rouge', 'bleue', 'neutre')[team.value]} rejointe, rôle : {('choisisseur', 'devineur')[role.value]}.")
|
||||||
|
|
||||||
|
async def maybe_change_role(game, message):
|
||||||
|
wannabe = game.get_player(message.author)
|
||||||
|
|
||||||
|
if not wannabe:
|
||||||
|
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if game.playing:
|
||||||
|
await game.say(f"{message.author.mention} Une partie est en cours, impossible de changer de rôle.")
|
||||||
|
return
|
||||||
|
|
||||||
|
wannabe.role = Roles(int(not(wannabe.role.value)))
|
||||||
|
|
||||||
|
await game.say(f"{message.author.mention} est désormais {('choisisseur', 'devineur')[wannabe.role.value]} de l'équipe {('rouge', 'bleue', 'neutre')[wannabe.team.value]}.")
|
||||||
|
|
||||||
|
async def print_teams(game):
|
||||||
|
if not game.players:
|
||||||
|
await game.say(f"Aucun joueur inscrit.")
|
||||||
|
return
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
|
||||||
|
for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL):
|
||||||
|
output += f"{(REDDOT, BLUEDOT, WHITEDOT)[team.value]} Équipe {('rouge', 'bleue', 'neutre')[team.value]} :\n"
|
||||||
|
for player in [p for p in game.players if p.team == team]:
|
||||||
|
output += f"--- {(SPEAKING, GLASS)[player.role.value]} {player.client.name} ({('choisisseur', 'devineur')[player.role.value]})\n"
|
||||||
|
|
||||||
|
await game.say(output)
|
||||||
|
|
||||||
|
async def print_configurations(game, with_error=False):
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
|
||||||
|
if with_error:
|
||||||
|
output += "La configuration actuelle des joueurs ne correspond à aucune configuration de jeu possible.\n\n"
|
||||||
|
|
||||||
|
output += "__Configurations possibles__\n\n"
|
||||||
|
|
||||||
|
possible_nb_of_players = sorted(set(sum(c.values()) for c in CONFIGURATIONS))
|
||||||
|
|
||||||
|
for nb_players in possible_nb_of_players:
|
||||||
|
output += f"**{nb_players} joueur{'s' if nb_players > 1 else ''}**\n"
|
||||||
|
configurations = [c for c in CONFIGURATIONS if sum(c.values()) == nb_players]
|
||||||
|
for configuration in configurations:
|
||||||
|
statuses = list(configuration.elements())
|
||||||
|
competition = any(e in [0, 1, 2, 3] for e in statuses)
|
||||||
|
output += " + ".join([SYMBOLS[status] for status in statuses])
|
||||||
|
if competition:
|
||||||
|
output += f" (compétition {SWORDS})"
|
||||||
|
else:
|
||||||
|
output += f" (coopération {HANDSHAKE})"
|
||||||
|
output += "\n"
|
||||||
|
output += "\n"
|
||||||
|
|
||||||
|
await game.say(output)
|
||||||
|
|
||||||
|
async def print_grid(game, revealed=False, to=Recipients.EVERYONE):
|
||||||
|
output = ""
|
||||||
|
|
||||||
|
if to == Recipients.CHOOSERS:
|
||||||
|
output += "---------- Grille actuelle : ----------\n"
|
||||||
|
|
||||||
|
widths = []
|
||||||
|
for column in range(5):
|
||||||
|
words_in_column = [word for (idx, word) in enumerate(game.words) if idx % 5 == column]
|
||||||
|
widths.append(max(len(w.label) for w in words_in_column))
|
||||||
|
|
||||||
|
for y in range(5):
|
||||||
|
for x in range(5):
|
||||||
|
word = game.words[y*5 + x]
|
||||||
|
label = word.label + (" " * (widths[x] - len(word.label) + 5))
|
||||||
|
label = f"`{label}`"
|
||||||
|
if revealed and word.guessed:
|
||||||
|
label = f"~~{label}~~"
|
||||||
|
output += f"{(REDDOT, BLUEDOT, WHITEDOT, SKULL)[word.team.value] if (revealed or word.guessed) else QUESTION}{label}"
|
||||||
|
output += "\n"
|
||||||
|
|
||||||
|
if to == Recipients.EVERYONE:
|
||||||
|
await game.say(output)
|
||||||
|
elif to == Recipients.CHOOSERS:
|
||||||
|
choosers = [p for p in game.players if p.role == Roles.CHOOSER]
|
||||||
|
for chooser in choosers:
|
||||||
|
await chooser.client.send(output)
|
||||||
|
|
||||||
|
async def maybe_leave(game, message):
|
||||||
|
player = game.get_player(message.author)
|
||||||
|
|
||||||
|
if not player:
|
||||||
|
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if game.playing:
|
||||||
|
await game.say(f"{message.author.mention} Une partie est en cours, impossible quitter l'équipe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
team = player.team
|
||||||
|
game.players.remove(player)
|
||||||
|
await game.say(f"{message.author.mention} est parti de l'équipe {('rouge', 'bleue', 'neutre')[team.value]}.")
|
||||||
|
|
||||||
|
async def maybe_start(game):
|
||||||
|
if not game.players:
|
||||||
|
await game.say(f"Aucun joueur inscrit.")
|
||||||
|
return
|
||||||
|
|
||||||
|
configuration = Counter([p.status for p in game.players])
|
||||||
|
|
||||||
|
if not configuration in CONFIGURATIONS:
|
||||||
|
await print_configurations(game, with_error=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
game.playing = True
|
||||||
|
if any(p.team in (Teams.RED, Teams.BLUE) for p in game.players):
|
||||||
|
game.mode = Modes.COMPETITION
|
||||||
|
else:
|
||||||
|
game.mode = Modes.COOPERATION
|
||||||
|
game.steps = CYCLES[game.mode]
|
||||||
|
with open(CODENAMES_WORDS, "r") as f:
|
||||||
|
lexicon = f.read().splitlines()
|
||||||
|
game.words = [Word(label=word, team=Teams.NEUTRAL) for word in random.sample(lexicon, 25)]
|
||||||
|
if game.mode == Modes.COMPETITION:
|
||||||
|
for idx in range(0, 9):
|
||||||
|
game.words[idx].team = Teams.RED
|
||||||
|
for idx in range(9, 17):
|
||||||
|
game.words[idx].team = Teams.BLUE
|
||||||
|
game.words[17].team = Teams.BOMB
|
||||||
|
elif game.mode == Modes.COOPERATION:
|
||||||
|
for idx in range(0, 12):
|
||||||
|
game.words[idx].team = Teams.BOMB
|
||||||
|
random.shuffle(game.words)
|
||||||
|
|
||||||
|
await proceed(game)
|
||||||
|
|
||||||
|
async def proceed(game):
|
||||||
|
|
||||||
|
if game.current_step:
|
||||||
|
|
||||||
|
scores = [len([w for w in game.words if w.guessed and w.team == team]) for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL)]
|
||||||
|
if any(w for w in game.words if w.guessed and w.team == Teams.BOMB):
|
||||||
|
if game.mode == Modes.COOPERATION:
|
||||||
|
await game.say(f"{SAD} La bombe a gagné. Score final des neutres : **{scores[2]}/12** en {game.turn} tours.")
|
||||||
|
elif game.mode == Modes.COMPETITION:
|
||||||
|
winners = int(not(game.current_team()))
|
||||||
|
await game.say(f"{PARTY} L'équipe {('rouge', 'bleue')[winners]} a gagné pour cause d'explosion de l'équipe adverse !")
|
||||||
|
game.reset()
|
||||||
|
return
|
||||||
|
|
||||||
|
for team in (Teams.RED, Teams.BLUE, Teams.NEUTRAL):
|
||||||
|
if [w for w in game.words if w.team == team] and not any(w for w in game.words if w.guessed == False and w.team == team):
|
||||||
|
await game.say(f"{PARTY} L'équipe {('rouge', 'bleue', 'neutre')[team.value]} a gagné ! Elle a trouvé tous ses mots.")
|
||||||
|
game.reset()
|
||||||
|
return
|
||||||
|
|
||||||
|
game.current_step = next(game.steps)
|
||||||
|
current_players = [p.client.mention for p in game.players if p.status == game.current_step]
|
||||||
|
|
||||||
|
if game.current_role() == Roles.CHOOSER:
|
||||||
|
game.turn += 1
|
||||||
|
if game.turn == 1:
|
||||||
|
await print_grid(game, revealed=False, to=Recipients.EVERYONE)
|
||||||
|
await print_grid(game, revealed=True, to=Recipients.CHOOSERS)
|
||||||
|
await game.say(f"{SYMBOLS[game.current_step]}"
|
||||||
|
f" C'est au choisisseur de l'équipe"
|
||||||
|
f" {('rouge', 'bleue', 'neutre')[game.current_team().value]}"
|
||||||
|
f" ({', '.join(current_players)}) de m'indiquer"
|
||||||
|
f" **en privé** un indice et un nombre de mots souhaité.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if game.current_role() == Roles.GUESSER:
|
||||||
|
neutral_guessers = [p.client.mention for p in game.players if p.status == Statuses.NEUTRAL_GUESSER]
|
||||||
|
current_players += neutral_guessers
|
||||||
|
current_players = set(current_players)
|
||||||
|
plural = len(current_players) > 1
|
||||||
|
await game.say(f"{SYMBOLS[game.current_step]}"
|
||||||
|
f" C'est au{('','x')[plural]} devineur{('','s')[plural]} de l'équipe"
|
||||||
|
f" {('rouge', 'bleue', 'neutre')[game.current_team().value]}"
|
||||||
|
f" ({', '.join(current_players)}) de retrouver la sélection"
|
||||||
|
f" effectuée par le choisisseur. L'indice est **{game.clue}**."
|
||||||
|
f" Nombre de mots à trouver : **{game.number}**")
|
||||||
|
|
||||||
|
async def process_whisper(game, message):
|
||||||
|
player = game.get_player(message.author)
|
||||||
|
|
||||||
|
if game.current_step != player.status:
|
||||||
|
await message.author.send("Ce n'est pas à toi de me parler pour l'instant.")
|
||||||
|
return
|
||||||
|
|
||||||
|
content = message.content.strip()
|
||||||
|
|
||||||
|
if game.current_role() == Roles.CHOOSER:
|
||||||
|
if regex := re.search(r"^([^ ]+) (\d+)$", content, re.I):
|
||||||
|
maybe_number = int(regex.group(2))
|
||||||
|
number_max = len([w for w in game.words if w.guessed == False and w.team == game.current_team()])
|
||||||
|
if not (1 <= maybe_number <= number_max):
|
||||||
|
await message.author.send(f"Nombre invalide (doit être entre 1 et {number_max}).")
|
||||||
|
return
|
||||||
|
game.number = maybe_number
|
||||||
|
|
||||||
|
maybe_clue = regex.group(1)
|
||||||
|
if any(w for w in game.words if serialize(w.label) == serialize(maybe_clue)):
|
||||||
|
await message.author.send(f"L'indice ne peut pas être un des mots de la grille !")
|
||||||
|
return
|
||||||
|
game.clue = maybe_clue
|
||||||
|
|
||||||
|
await message.author.send(f"OK, ça continue sur {game.channel.mention} !")
|
||||||
|
await proceed(game)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await message.author.send("Choisis secrètement un ou plusieurs mots, puis envoie (ici)"
|
||||||
|
" `<indice> <nombre>`, par exemple `caisson 3`."
|
||||||
|
" L'indice doit être un seul mot, et il ne doit pas être de"
|
||||||
|
" la même famille qu'un mot de la grille !")
|
||||||
|
return
|
||||||
|
|
||||||
|
if game.current_role() == Roles.GUESSER:
|
||||||
|
await message.author.send(f"Tu dois deviner le mot, ça se passe en public sur {game.channel.mention}.")
|
||||||
|
|
||||||
|
async def maybe_guess(game, message, content):
|
||||||
|
|
||||||
|
if not game.playing:
|
||||||
|
await game.say("Aucune partie en cours.")
|
||||||
|
return
|
||||||
|
|
||||||
|
player = game.get_player(message.author)
|
||||||
|
if not player:
|
||||||
|
await game.say(f"{message.author.mention} Tu n'es dans aucune équipe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if player.team != game.current_team() and player.team != Teams.NEUTRAL:
|
||||||
|
await game.say(f"{message.author.mention} Ce n'est pas à ton équipe de jouer.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if player.role == Roles.CHOOSER:
|
||||||
|
await game.say(f"{message.author.mention} Tu es choisisseur, pas devineur.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if game.current_role() == Roles.CHOOSER:
|
||||||
|
await game.say(f"{message.author.mention} Le choisisseur n'a pas encore donné son indice.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
word = next(w for w in game.words if serialize(w.label) == serialize(content))
|
||||||
|
except StopIteration:
|
||||||
|
await game.say(f"{message.author.mention} Je ne reconnais pas ce mot dans la grille.")
|
||||||
|
return
|
||||||
|
if word.guessed:
|
||||||
|
await game.say(f"{message.author.mention} Ce mot a déjà été deviné et révélé.")
|
||||||
|
return
|
||||||
|
|
||||||
|
word.guessed = True
|
||||||
|
await print_grid(game, revealed=False, to=Recipients.EVERYONE)
|
||||||
|
if word.team == game.current_team():
|
||||||
|
await game.say(f"{SMILE} Bien ouej, c'est un mot de ton équipe !")
|
||||||
|
game.number -= 1
|
||||||
|
if game.number > 0:
|
||||||
|
await game.say(f"Nombre de mots à trouver encore : {game.number}")
|
||||||
|
else:
|
||||||
|
await game.say(f"Le nombre de mots indiqué par le choisisseur a été atteint, bravo ! Fin du tour.")
|
||||||
|
await proceed(game)
|
||||||
|
else:
|
||||||
|
game.number = 0
|
||||||
|
if word.team == Teams.NEUTRAL:
|
||||||
|
await game.say(f"{MEH} Oups, c'est un mot neutre... Fin du tour.")
|
||||||
|
elif word.team == Teams.BOMB:
|
||||||
|
await game.say(f"{BLOWN} **BOUM !** C'est {('une','la')[game.mode.value]} bombe ! Fin du jeu.")
|
||||||
|
else:
|
||||||
|
await game.say(f"{SAD} Mince... c'est un mot de l'équipe adverse... Fin du tour.")
|
||||||
|
|
||||||
|
await proceed(game)
|
||||||
16
cambot/emojis.py
Normal file
16
cambot/emojis.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
PUSHPIN = chr(128204)
|
||||||
|
REDDOT = chr(128308)
|
||||||
|
BLUEDOT = chr(128309)
|
||||||
|
WHITEDOT = chr(9898)
|
||||||
|
SPEAKING = chr(128483)
|
||||||
|
GLASS = chr(128269)
|
||||||
|
HANDSHAKE = chr(129309)
|
||||||
|
SWORDS = chr(9876)
|
||||||
|
EXPLOSION = chr(128165)
|
||||||
|
QUESTION = chr(10068)
|
||||||
|
SKULL = chr(9760)
|
||||||
|
SMILE = chr(128515)
|
||||||
|
MEH = chr(128533)
|
||||||
|
SAD = chr(128543)
|
||||||
|
BLOWN = chr(129327)
|
||||||
|
PARTY = chr(129395)
|
||||||
92
cambot/ephemeris.py
Normal file
92
cambot/ephemeris.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from discord import Embed, Color
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import locale
|
||||||
|
from bs4 import BeautifulSoup as bs
|
||||||
|
from .settings import *
|
||||||
|
from .saints import SAINTS
|
||||||
|
|
||||||
|
def citation():
|
||||||
|
try:
|
||||||
|
ts = datetime.now().strftime("%s")
|
||||||
|
req = requests.get(f"https://fr.wikiquote.org/wiki/Wikiquote:Accueil?r={ts}")
|
||||||
|
soup = bs(req.text, features="html.parser")
|
||||||
|
citation = soup.find(lambda tag:tag.name=="i" and "Lumière sur" in tag.text).parent.parent.parent.findAll("div")[1].text.strip()
|
||||||
|
return f"*{citation}*"
|
||||||
|
except Exception:
|
||||||
|
return "Impossible de trouver la citation du jour. Bouuuh !"
|
||||||
|
|
||||||
|
def saint():
|
||||||
|
today = datetime.now().strftime("%m/%d")
|
||||||
|
saint = SAINTS[today]
|
||||||
|
return saint
|
||||||
|
|
||||||
|
def weather_emoji(_id):
|
||||||
|
emojis = {re.compile(r"2.."):":thunder_cloud_rain:",
|
||||||
|
re.compile(r"3.."):":white_sun_rain_cloud:",
|
||||||
|
re.compile(r"5.."):":cloud_rain:",
|
||||||
|
re.compile(r"6.."):":snowflake:",
|
||||||
|
re.compile(r"7.."):":interrobang:",
|
||||||
|
re.compile(r"800"):":sunny:",
|
||||||
|
re.compile(r"801"):":white_sun_small_cloud:",
|
||||||
|
re.compile(r"80[23]"):":white_sun_cloud:",
|
||||||
|
re.compile(r"804"):":cloud:"}
|
||||||
|
try:
|
||||||
|
code = next(emoji[1] for emoji in emojis.items() if emoji[0].match(str(_id)))
|
||||||
|
except StopIteration:
|
||||||
|
code = ":negative_squared_cross_mark:"
|
||||||
|
return code
|
||||||
|
|
||||||
|
def weather(lat, lon):
|
||||||
|
r = requests.get(f"https://api.openweathermap.org/data/2.5/"
|
||||||
|
f"onecall?lat={lat}&lon={lon}&appid={OWM_KEY}&lang=fr&units=metric")
|
||||||
|
j = json.loads(r.text)
|
||||||
|
|
||||||
|
next_hours = []
|
||||||
|
for i in (2, 8, 14):
|
||||||
|
hourly = j["hourly"][i]
|
||||||
|
time = datetime.fromtimestamp(hourly["dt"]).strftime("%H:%M")
|
||||||
|
temp = round(hourly["temp"])
|
||||||
|
feels_like = hourly["feels_like"]
|
||||||
|
humidity = hourly["humidity"]
|
||||||
|
wind_speed = round(hourly["wind_speed"] * 3.6)
|
||||||
|
description = hourly["weather"][0]["description"]
|
||||||
|
emoji = weather_emoji(hourly["weather"][0]["id"])
|
||||||
|
next_hours.append({"time":time,
|
||||||
|
"emoji":emoji,
|
||||||
|
"description":description,
|
||||||
|
"temp":temp,
|
||||||
|
"wind_speed":wind_speed
|
||||||
|
})
|
||||||
|
|
||||||
|
return next_hours
|
||||||
|
|
||||||
|
def digest():
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, "fr_CH.utf-8")
|
||||||
|
except locale.Error:
|
||||||
|
pass
|
||||||
|
now = datetime.now()
|
||||||
|
d = now.strftime("%A %-d %B")
|
||||||
|
c = citation()
|
||||||
|
s = saint()
|
||||||
|
w_strings = []
|
||||||
|
for w in weather(46.5196661, 6.6325467):
|
||||||
|
w_strings.append(f"{w['emoji']} {w['description']} ({w['temp']}°C, {w['wind_speed']} km/h)")
|
||||||
|
|
||||||
|
embed = Embed()
|
||||||
|
embed.title = "Bonjour !"
|
||||||
|
embed.description = f"Nous sommes le {d} ({s})\n\n{c}\n\u200B"
|
||||||
|
colors = [Color.red(), Color.gold(), Color.orange(), Color.blue(), Color.green(), Color.magenta(), Color.purple()]
|
||||||
|
embed.color = colors[now.weekday()]
|
||||||
|
embed.add_field(name="Matin", value=w_strings[0])
|
||||||
|
embed.add_field(name="Après-midi", value=w_strings[1])
|
||||||
|
embed.add_field(name="Soir", value=w_strings[2])
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(citation())
|
||||||
379
cambot/saints.py
Normal file
379
cambot/saints.py
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
SAINTS = {
|
||||||
|
"01/01": "Jour de l'An",
|
||||||
|
"01/02": "Saint Basile",
|
||||||
|
"01/03": "Sainte Geneviève",
|
||||||
|
"01/04": "Saint Odilon",
|
||||||
|
"01/05": "Saint Edouard",
|
||||||
|
"01/06": "Saint André",
|
||||||
|
"01/07": "Saint Raymond",
|
||||||
|
"01/08": "Saint Lucien",
|
||||||
|
"01/09": "Sainte Alix",
|
||||||
|
"01/10": "Saint Guillaume",
|
||||||
|
"01/11": "Saint Paulin",
|
||||||
|
"01/12": "Sainte Tatiana",
|
||||||
|
"01/13": "Sainte Yvette",
|
||||||
|
"01/14": "Sainte Nina",
|
||||||
|
"01/15": "Saint Rémi",
|
||||||
|
"01/16": "Saint Marcel",
|
||||||
|
"01/17": "Sainte Roseline",
|
||||||
|
"01/18": "Sainte Prisca",
|
||||||
|
"01/19": "Saint Marius",
|
||||||
|
"01/20": "Saint Sébastien",
|
||||||
|
"01/21": "Sainte Agnès",
|
||||||
|
"01/22": "Saint Vincent",
|
||||||
|
"01/23": "Saint Barnard",
|
||||||
|
"01/24": "Saint François",
|
||||||
|
"01/25": "Saint Paul",
|
||||||
|
"01/26": "Sainte Paule",
|
||||||
|
"01/27": "Sainte Angèle",
|
||||||
|
"01/28": "Saint Thomas",
|
||||||
|
"01/29": "Saint Gildas",
|
||||||
|
"01/30": "Sainte Martine",
|
||||||
|
"01/31": "Sainte Marcelle",
|
||||||
|
|
||||||
|
"02/01": "Sainte Ella",
|
||||||
|
"02/02": "Saint Théophane",
|
||||||
|
"02/03": "Saint Blaise",
|
||||||
|
"02/04": "Sainte Véronique",
|
||||||
|
"02/05": "Sainte Agathe",
|
||||||
|
"02/06": "Saint Gaston",
|
||||||
|
"02/07": "Sainte Eugénie",
|
||||||
|
"02/08": "Sainte Jacqueline",
|
||||||
|
"02/09": "Sainte Apolline",
|
||||||
|
"02/10": "Saint Arnaud",
|
||||||
|
"02/11": "Saint Séverin",
|
||||||
|
"02/12": "Saint Félix",
|
||||||
|
"02/13": "Sainte Béatrice",
|
||||||
|
"02/14": "Saint Valentin",
|
||||||
|
"02/15": "Saint Claude",
|
||||||
|
"02/16": "Sainte Julienne",
|
||||||
|
"02/17": "Saint Alexis",
|
||||||
|
"02/18": "Sainte Bernadette",
|
||||||
|
"02/19": "Saint Gabin",
|
||||||
|
"02/20": "Sainte Aimée",
|
||||||
|
"02/21": "Saint Damien",
|
||||||
|
"02/22": "Sainte Isabelle",
|
||||||
|
"02/23": "Saint Lazare",
|
||||||
|
"02/24": "Saint Modeste",
|
||||||
|
"02/25": "Saint Roméo",
|
||||||
|
"02/26": "Saint Nestor",
|
||||||
|
"02/27": "Sainte Honorine",
|
||||||
|
"02/28": "Saint Romain",
|
||||||
|
"02/29": "Saint Auguste",
|
||||||
|
|
||||||
|
"03/01": "Saint Aubin",
|
||||||
|
"03/02": "Saint Charles",
|
||||||
|
"03/03": "Saint Gwenolé",
|
||||||
|
"03/04": "Saint Casimir",
|
||||||
|
"03/05": "Saint Olive",
|
||||||
|
"03/06": "Sainte Colette",
|
||||||
|
"03/07": "Sainte Félicité",
|
||||||
|
"03/08": "Saint Jean",
|
||||||
|
"03/09": "Sainte Françoise",
|
||||||
|
"03/10": "Saint Vivien",
|
||||||
|
"03/11": "Sainte Rosine",
|
||||||
|
"03/12": "Sainte Justine",
|
||||||
|
"03/13": "Saint Rodrigue",
|
||||||
|
"03/14": "Sainte Mathilde",
|
||||||
|
"03/15": "Sainte Louise",
|
||||||
|
"03/16": "Sainte Bénédicte",
|
||||||
|
"03/17": "Saint Patrick",
|
||||||
|
"03/18": "Saint Cyrille",
|
||||||
|
"03/19": "Saint Joseph",
|
||||||
|
"03/20": "Saint Herbert",
|
||||||
|
"03/21": "Sainte Clémence",
|
||||||
|
"03/22": "Sainte Léa",
|
||||||
|
"03/23": "Saint Victorien",
|
||||||
|
"03/24": "Sainte Catherine",
|
||||||
|
"03/25": "Saint Humbert",
|
||||||
|
"03/26": "Sainte Larissa",
|
||||||
|
"03/27": "Saint Habib",
|
||||||
|
"03/28": "Saint Gontran",
|
||||||
|
"03/29": "Sainte Gwladys",
|
||||||
|
"03/30": "Saint Amédée",
|
||||||
|
"03/31": "Saint Benjamin",
|
||||||
|
|
||||||
|
"04/01": "Saint Hugues",
|
||||||
|
"04/02": "Sainte Sandrine",
|
||||||
|
"04/03": "Saint Richard",
|
||||||
|
"04/04": "Saint Isidore",
|
||||||
|
"04/05": "Sainte Irène",
|
||||||
|
"04/06": "Saint Marcellin",
|
||||||
|
"04/07": "Saint Jean-Baptiste",
|
||||||
|
"04/08": "Sainte Julie",
|
||||||
|
"04/09": "Saint Gautier",
|
||||||
|
"04/10": "Saint Fulbert",
|
||||||
|
"04/11": "Saint Stanislas",
|
||||||
|
"04/12": "Saint Jules 1er",
|
||||||
|
"04/13": "Sainte Ida",
|
||||||
|
"04/14": "Saint Maxime",
|
||||||
|
"04/15": "Saint Paterne",
|
||||||
|
"04/16": "Saint Benoît",
|
||||||
|
"04/17": "Saint Etienne",
|
||||||
|
"04/18": "Saint Parfait",
|
||||||
|
"04/19": "Sainte Emma",
|
||||||
|
"04/20": "Sainte Odette",
|
||||||
|
"04/21": "Saint Anselme",
|
||||||
|
"04/22": "Saint Alexandre",
|
||||||
|
"04/23": "Saint Georges",
|
||||||
|
"04/24": "Saint Fidèle",
|
||||||
|
"04/25": "Saint Marc",
|
||||||
|
"04/26": "Sainte Alida",
|
||||||
|
"04/27": "Sainte Zita",
|
||||||
|
"04/28": "Sainte Valérie",
|
||||||
|
"04/29": "Sainte Catherine",
|
||||||
|
"04/30": "Saint Robert",
|
||||||
|
|
||||||
|
"05/01": "Saint Joseph",
|
||||||
|
"05/02": "Saint Boris",
|
||||||
|
"05/03": "Saint Jacques",
|
||||||
|
"05/04": "Saint Sylvain",
|
||||||
|
"05/05": "Sainte Judith",
|
||||||
|
"05/06": "Sainte Prudence",
|
||||||
|
"05/07": "Sainte Gisèle",
|
||||||
|
"05/08": "Saint Désiré",
|
||||||
|
"05/09": "Sainte Pacôme",
|
||||||
|
"05/10": "Sainte Solange",
|
||||||
|
"05/11": "Sainte Estelle",
|
||||||
|
"05/12": "Saint Achille",
|
||||||
|
"05/13": "Sainte Rolande",
|
||||||
|
"05/14": "Saint Matthias",
|
||||||
|
"05/15": "Sainte Denise",
|
||||||
|
"05/16": "Saint Honoré",
|
||||||
|
"05/17": "Saint Pascal",
|
||||||
|
"05/18": "Saint Eric",
|
||||||
|
"05/19": "Saint Yves",
|
||||||
|
"05/20": "Saint Bernardin",
|
||||||
|
"05/21": "Saint Constantin",
|
||||||
|
"05/22": "Saint Emile",
|
||||||
|
"05/23": "Saint Didier",
|
||||||
|
"05/24": "Saint Donatien",
|
||||||
|
"05/25": "Sainte Sophie",
|
||||||
|
"05/26": "Saint Bérenger",
|
||||||
|
"05/27": "Saint Augustin",
|
||||||
|
"05/28": "Saint Germain",
|
||||||
|
"05/29": "Saint Aymard",
|
||||||
|
"05/30": "Saint Ferdinand",
|
||||||
|
"05/31": "Sainte Perrine",
|
||||||
|
|
||||||
|
"06/01": "Saint Justin",
|
||||||
|
"06/02": "Sainte Blandine",
|
||||||
|
"06/03": "Saint Charles",
|
||||||
|
"06/04": "Sainte Clotilde",
|
||||||
|
"06/05": "Saint Igor",
|
||||||
|
"06/06": "Saint Norbert",
|
||||||
|
"06/07": "Saint Gilbert",
|
||||||
|
"06/08": "Saint Médard",
|
||||||
|
"06/09": "Sainte Diane",
|
||||||
|
"06/10": "Saint Landry",
|
||||||
|
"06/11": "Saint Barnabé",
|
||||||
|
"06/12": "Saint Guy",
|
||||||
|
"06/13": "Saint Antoine",
|
||||||
|
"06/14": "Saint Elisée",
|
||||||
|
"06/15": "Sainte Germaine",
|
||||||
|
"06/16": "Saint Jean-François",
|
||||||
|
"06/17": "Saint Hervé",
|
||||||
|
"06/18": "Saint Léonce",
|
||||||
|
"06/19": "Saint Romuald",
|
||||||
|
"06/20": "Saint Silvère",
|
||||||
|
"06/21": "Saint Rodolphe",
|
||||||
|
"06/22": "Saint Alban",
|
||||||
|
"06/23": "Sainte Audrey",
|
||||||
|
"06/24": "Saint Jean-Baptiste",
|
||||||
|
"06/25": "Saint Prosper",
|
||||||
|
"06/26": "Saint Anthelme",
|
||||||
|
"06/27": "Saint Fernand",
|
||||||
|
"06/28": "Saint Irénée",
|
||||||
|
"06/29": "Saint Pierre",
|
||||||
|
"06/30": "Saint Martial",
|
||||||
|
|
||||||
|
"07/01": "Saint Thierry",
|
||||||
|
"07/02": "Saint Martinien",
|
||||||
|
"07/03": "Saint Thomas",
|
||||||
|
"07/04": "Saint Florent",
|
||||||
|
"07/05": "Saint Antoine-Marie",
|
||||||
|
"07/06": "Sainte Marietta",
|
||||||
|
"07/07": "Saint Raoul",
|
||||||
|
"07/08": "Saint Thibaud",
|
||||||
|
"07/09": "Sainte Amandine",
|
||||||
|
"07/10": "Saint Ulric",
|
||||||
|
"07/11": "Saint Benoît",
|
||||||
|
"07/12": "Saint Olivier",
|
||||||
|
"07/13": "Saint Henri",
|
||||||
|
"07/14": "Saint Camille",
|
||||||
|
"07/15": "Saint Donald",
|
||||||
|
"07/16": "Sainte Elvire",
|
||||||
|
"07/17": "Sainte Charlotte",
|
||||||
|
"07/18": "Saint Frédéric",
|
||||||
|
"07/19": "Saint Arsène",
|
||||||
|
"07/20": "Sainte Marina",
|
||||||
|
"07/21": "Saint Victor",
|
||||||
|
"07/22": "Sainte Marie-Madeleine",
|
||||||
|
"07/23": "Sainte Brigitte",
|
||||||
|
"07/24": "Sainte Christine",
|
||||||
|
"07/25": "Saint Jacques",
|
||||||
|
"07/26": "Sainte Anne",
|
||||||
|
"07/27": "Sainte Nathalie",
|
||||||
|
"07/28": "Saint Samson",
|
||||||
|
"07/29": "Sainte Marthe",
|
||||||
|
"07/30": "Sainte Juliette",
|
||||||
|
"07/31": "Saint Ignace",
|
||||||
|
|
||||||
|
"08/01": "Saint Alphonse-Marie",
|
||||||
|
"08/02": "Saint Pierre-Julien",
|
||||||
|
"08/03": "Sainte Lydie",
|
||||||
|
"08/04": "Saint Jean-Marie",
|
||||||
|
"08/05": "Saint Abel",
|
||||||
|
"08/06": "Saint Octavien",
|
||||||
|
"08/07": "Saint Gaétan",
|
||||||
|
"08/08": "Saint Dominique",
|
||||||
|
"08/09": "Saint Amour",
|
||||||
|
"08/10": "Saint Laurent",
|
||||||
|
"08/11": "Sainte Claire",
|
||||||
|
"08/12": "Sainte Clarisse",
|
||||||
|
"08/13": "Saint Hippolyte",
|
||||||
|
"08/14": "Saint Evrard",
|
||||||
|
"08/15": "Sainte Marie",
|
||||||
|
"08/16": "Saint Armel",
|
||||||
|
"08/17": "Saint Hyacinthe",
|
||||||
|
"08/18": "Sainte Hélène",
|
||||||
|
"08/19": "Saint Jean-Eudes",
|
||||||
|
"08/20": "Saint Bernard",
|
||||||
|
"08/21": "Saint Christophe",
|
||||||
|
"08/22": "Saint Fabrice",
|
||||||
|
"08/23": "Sainte Rose",
|
||||||
|
"08/24": "Saint Barthélémy",
|
||||||
|
"08/25": "Saint Louis",
|
||||||
|
"08/26": "Sainte Natacha",
|
||||||
|
"08/27": "Sainte Monique",
|
||||||
|
"08/28": "Saint Augustin",
|
||||||
|
"08/29": "Sainte Sabine",
|
||||||
|
"08/30": "Saint Fiacre",
|
||||||
|
"08/31": "Saint Aristide",
|
||||||
|
|
||||||
|
"09/01": "Saint Gilles",
|
||||||
|
"09/02": "Sainte Ingrid",
|
||||||
|
"09/03": "Saint Grégoire",
|
||||||
|
"09/04": "Sainte Rosalie",
|
||||||
|
"09/05": "Sainte Raïssa",
|
||||||
|
"09/06": "Saint Bertrand",
|
||||||
|
"09/07": "Sainte Reine",
|
||||||
|
"09/08": "Saint Adrien",
|
||||||
|
"09/09": "Saint Alain",
|
||||||
|
"09/10": "Sainte Inès",
|
||||||
|
"09/11": "Saint Adelphe",
|
||||||
|
"09/12": "Saint Apollinaire",
|
||||||
|
"09/13": "Saint Aimé",
|
||||||
|
"09/14": "la Croix Glorieuse",
|
||||||
|
"09/15": "Saint Roland",
|
||||||
|
"09/16": "Sainte Edith",
|
||||||
|
"09/17": "Saint Renaud",
|
||||||
|
"09/18": "Sainte Nadège",
|
||||||
|
"09/19": "Sainte Emilie",
|
||||||
|
"09/20": "Saint Davy",
|
||||||
|
"09/21": "Saint Matthieu",
|
||||||
|
"09/22": "Saint Maurice",
|
||||||
|
"09/23": "Saint Constant",
|
||||||
|
"09/24": "Sainte Thècle",
|
||||||
|
"09/25": "Saint Hermann",
|
||||||
|
"09/26": "Sts Côme et Damien",
|
||||||
|
"09/27": "Saint Vincent",
|
||||||
|
"09/28": "Saint Venceslas",
|
||||||
|
"09/29": "Saint Michel",
|
||||||
|
"09/30": "Saint Jérôme",
|
||||||
|
|
||||||
|
"10/01": "Sainte Thérèse",
|
||||||
|
"10/02": "Saint Léger",
|
||||||
|
"10/03": "Saint Gérard",
|
||||||
|
"10/04": "Saint François",
|
||||||
|
"10/05": "Sainte Fleur",
|
||||||
|
"10/06": "Saint Bruno",
|
||||||
|
"10/07": "Saint Serge",
|
||||||
|
"10/08": "Sainte Pélagie",
|
||||||
|
"10/09": "Saint Denis",
|
||||||
|
"10/10": "Saint Ghislain",
|
||||||
|
"10/11": "Saint Firmin",
|
||||||
|
"10/12": "Saint Wilfrid",
|
||||||
|
"10/13": "Saint Géraud",
|
||||||
|
"10/14": "Saint Juste",
|
||||||
|
"10/15": "Sainte Thérèse",
|
||||||
|
"10/16": "Sainte Edwige",
|
||||||
|
"10/17": "Saint Baudouin",
|
||||||
|
"10/18": "Saint Luc",
|
||||||
|
"10/19": "Saint René",
|
||||||
|
"10/20": "Sainte Adeline",
|
||||||
|
"10/21": "Sainte Céline",
|
||||||
|
"10/22": "Sainte Elodie",
|
||||||
|
"10/23": "Saint Jean",
|
||||||
|
"10/24": "Saint Florentin",
|
||||||
|
"10/25": "Sainte Doria",
|
||||||
|
"10/26": "Saint Dimitri",
|
||||||
|
"10/27": "Sainte Emeline",
|
||||||
|
"10/28": "Saint Simon",
|
||||||
|
"10/29": "Saint Narcisse",
|
||||||
|
"10/30": "Sainte Bienvenue",
|
||||||
|
"10/31": "Saint Quentin",
|
||||||
|
|
||||||
|
"11/01": "La Toussaint",
|
||||||
|
"11/02": "Les Défunts",
|
||||||
|
"11/03": "Saint Hubert",
|
||||||
|
"11/04": "Saint Charles",
|
||||||
|
"11/05": "Sainte Sylvie",
|
||||||
|
"11/06": "Sainte Bertille",
|
||||||
|
"11/07": "Sainte Carine",
|
||||||
|
"11/08": "Saint Geoffroy",
|
||||||
|
"11/09": "Saint Théodore",
|
||||||
|
"11/10": "Saint Léon",
|
||||||
|
"11/11": "Saint Martin",
|
||||||
|
"11/12": "Saint Christian",
|
||||||
|
"11/13": "Saint Brice",
|
||||||
|
"11/14": "Saint Sidoine",
|
||||||
|
"11/15": "Saint Albert",
|
||||||
|
"11/16": "Sainte Marguerite",
|
||||||
|
"11/17": "Sainte Elisabeth",
|
||||||
|
"11/18": "Sainte Aude",
|
||||||
|
"11/19": "Saint Tanguy",
|
||||||
|
"11/20": "Saint Edmond",
|
||||||
|
"11/21": "Saint Albert",
|
||||||
|
"11/22": "Sainte Cécile",
|
||||||
|
"11/23": "Saint Clément",
|
||||||
|
"11/24": "Sainte Flora",
|
||||||
|
"11/25": "Sainte Catherine",
|
||||||
|
"11/26": "Sainte Delphine",
|
||||||
|
"11/27": "Saint Séverin",
|
||||||
|
"11/28": "Saint Jacques",
|
||||||
|
"11/29": "Saint Saturnin",
|
||||||
|
"11/30": "Saint André",
|
||||||
|
|
||||||
|
"12/01": "Sainte Florence",
|
||||||
|
"12/02": "Sainte Viviane",
|
||||||
|
"12/03": "Saint François-Xavier",
|
||||||
|
"12/04": "Sainte Barbara",
|
||||||
|
"12/05": "Saint Gérald",
|
||||||
|
"12/06": "Saint Nicolas",
|
||||||
|
"12/07": "Saint Ambroise",
|
||||||
|
"12/08": "Sainte Elfie",
|
||||||
|
"12/09": "Saint Pierre",
|
||||||
|
"12/10": "Saint Romaric",
|
||||||
|
"12/11": "Saint Daniel",
|
||||||
|
"12/12": "Sainte Jeanne-Françoise",
|
||||||
|
"12/13": "Sainte Lucie",
|
||||||
|
"12/14": "Sainte Odile",
|
||||||
|
"12/15": "Sainte Ninon",
|
||||||
|
"12/16": "Sainte Alice",
|
||||||
|
"12/17": "Saint Gaël",
|
||||||
|
"12/18": "Saint Gatien",
|
||||||
|
"12/19": "Saint Urbain",
|
||||||
|
"12/20": "Saint Théophile",
|
||||||
|
"12/21": "Saint Pierre",
|
||||||
|
"12/22": "Sainte Françoise-Xavière",
|
||||||
|
"12/23": "Saint Armand",
|
||||||
|
"12/24": "Sainte Adèle",
|
||||||
|
"12/25": "Jour de Noël",
|
||||||
|
"12/26": "Saint Etienne",
|
||||||
|
"12/27": "Saint Jean",
|
||||||
|
"12/28": "Saints Innocents",
|
||||||
|
"12/29": "Saint David",
|
||||||
|
"12/30": "Saint Roger",
|
||||||
|
"12/31": "Saint Sylvestre"
|
||||||
|
}
|
||||||
25
cambot/settings_example.py
Normal file
25
cambot/settings_example.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from datetime import time
|
||||||
|
|
||||||
|
DISCORD_TOKEN = "xxx"
|
||||||
|
OWM_KEY = "xxx"
|
||||||
|
|
||||||
|
CHANNEL1 = 111111111111111111
|
||||||
|
CHANNEL2 = 222222222222222222
|
||||||
|
CHANNEL3 = 333333333333333333
|
||||||
|
EPHEMERIS_CHANNEL_IDS = (CHANNEL1,)
|
||||||
|
CODENAMES_CHANNEL_IDS = (CHANNEL1, CHANNEL2)
|
||||||
|
WORDLE_CHANNEL_IDS = (CHANNEL2, CHANNEL3)
|
||||||
|
|
||||||
|
WORDLE_VALID_WORDS = "cambot/wordlists/valid_words.txt"
|
||||||
|
WORDLE_TARGET_WORDS = "cambot/wordlists/target_words.txt"
|
||||||
|
WORDLE_POINTS = (1, 3, 6)
|
||||||
|
WORDLE_SLOWMODE = 0
|
||||||
|
WORDLE_MINLENGTH = 8
|
||||||
|
WORDLE_FORCE_ALTERNATION = True
|
||||||
|
|
||||||
|
HEARTBEAT = 60
|
||||||
|
EPHEMERIS = 0
|
||||||
|
WORDLE = 1
|
||||||
|
EVENTS = ((EPHEMERIS, time(8)),
|
||||||
|
(WORDLE, time(8)),
|
||||||
|
(WORDLE, time(20)))
|
||||||
9
cambot/utils.py
Normal file
9
cambot/utils.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import unidecode
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
def serialize(string):
|
||||||
|
return unidecode.unidecode(string.lower().strip().replace(" ", "").replace("-", "").replace("'", "").replace(".", ""))
|
||||||
|
|
||||||
|
class EnumZero(Enum):
|
||||||
|
def _generate_next_value_(name, start, count, last_values):
|
||||||
|
return count
|
||||||
98
cambot/wordle.py
Normal file
98
cambot/wordle.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import random
|
||||||
|
from unidecode import unidecode
|
||||||
|
from collections import defaultdict
|
||||||
|
from .settings import *
|
||||||
|
|
||||||
|
with open(WORDLE_VALID_WORDS, "r") as f:
|
||||||
|
valid_words = set(f.read().splitlines())
|
||||||
|
|
||||||
|
with open(WORDLE_TARGET_WORDS, "r") as f:
|
||||||
|
target_words = set(f.read().splitlines())
|
||||||
|
|
||||||
|
valid_words = valid_words.union(target_words)
|
||||||
|
|
||||||
|
def validate(target, guess):
|
||||||
|
assert len(target) == len(guess)
|
||||||
|
|
||||||
|
copy = list(target)
|
||||||
|
output = [0] * len(target)
|
||||||
|
|
||||||
|
# Look for the green squares :
|
||||||
|
|
||||||
|
for idx in range(len(target)):
|
||||||
|
if target[idx] == guess[idx]:
|
||||||
|
output[idx] = 2
|
||||||
|
copy[idx] = None
|
||||||
|
|
||||||
|
# Look for the yellow squares :
|
||||||
|
|
||||||
|
for idx in range(len(target)):
|
||||||
|
if target[idx] == guess[idx]:
|
||||||
|
continue # ignore the letters that are green
|
||||||
|
if guess[idx] in copy:
|
||||||
|
left_most_yellow_idx = copy.index(guess[idx])
|
||||||
|
output[idx] = 1
|
||||||
|
copy[left_most_yellow_idx] = None # mark ONE of the corresponding letters as done
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, channel):
|
||||||
|
self.channel = channel
|
||||||
|
self.target = None
|
||||||
|
self.winner = None
|
||||||
|
self.tries = 0
|
||||||
|
self.last_player = None
|
||||||
|
self.scores = None
|
||||||
|
self.tried = None
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.target = random.choice(tuple(x for x in target_words if len(x) >= WORDLE_MINLENGTH))
|
||||||
|
self.winner = None
|
||||||
|
self.tries = 0
|
||||||
|
self.last_player = None
|
||||||
|
self.scores = defaultdict(int)
|
||||||
|
self.tried = set()
|
||||||
|
await self.channel.edit(slowmode_delay=WORDLE_SLOWMODE)
|
||||||
|
await self.channel.send(f"Il y a un nouveau mot à deviner ! Il fait {len(self.target)} lettres.")
|
||||||
|
|
||||||
|
async def parse(self, message):
|
||||||
|
content = message.content.strip()
|
||||||
|
if (len(content) >= 1 and content[0] == "-") or self.winner or not self.target: # special char, or somebody won, or never initialized
|
||||||
|
return
|
||||||
|
guess = unidecode(content).upper()
|
||||||
|
if WORDLE_FORCE_ALTERNATION and message.author == self.last_player:
|
||||||
|
await self.channel.send(f"{message.author.mention} Laisse un peu jouer les autres !")
|
||||||
|
return
|
||||||
|
if len(guess) != len(self.target):
|
||||||
|
await self.channel.send(f"{message.author.mention} Le mot à deviner fait {len(self.target)} lettres (ça ne compte pas comme un tour).")
|
||||||
|
return
|
||||||
|
if guess not in valid_words:
|
||||||
|
await self.channel.send(f"{message.author.mention} `{guess}` n'est pas dans mon dictionnaire (ça ne compte pas comme un tour).")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.tries += 1
|
||||||
|
self.last_player = message.author
|
||||||
|
result = validate(self.target, guess)
|
||||||
|
points = 0
|
||||||
|
for idx, letter in enumerate(guess):
|
||||||
|
if (idx, letter) in self.tried:
|
||||||
|
continue
|
||||||
|
points += WORDLE_POINTS[result[idx]]
|
||||||
|
self.tried.add((idx, letter))
|
||||||
|
|
||||||
|
output_word = " ".join(f":regional_indicator_{x.lower()}:" for x in guess)
|
||||||
|
output_squares = " ".join((":white_large_square:", ":yellow_square:", ":green_square:")[x] for x in result)
|
||||||
|
|
||||||
|
output = f"{message.author.mention} (essai {self.tries}, {points} point{'s' if points > 1 else ''})\n{output_word}\n{output_squares}"
|
||||||
|
self.scores[message.author] += points
|
||||||
|
|
||||||
|
if guess == self.target:
|
||||||
|
self.winner = message.author
|
||||||
|
await self.channel.edit(slowmode_delay=0)
|
||||||
|
output += "\n\n:trophy: YOUPI :trophy:\n\nScores :\n\n"
|
||||||
|
for idx, score in enumerate(sorted(self.scores.items(), key=lambda x:x[1], reverse=True)):
|
||||||
|
player, points = score
|
||||||
|
output += f"{idx+1}) {player.display_name} ({points} point{'s' if points > 1 else ''})\n"
|
||||||
|
|
||||||
|
await self.channel.send(output)
|
||||||
295
poetry.lock
generated
Normal file
295
poetry.lock
generated
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Async http client/server framework (asyncio)"
|
||||||
|
name = "aiohttp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.3"
|
||||||
|
version = "3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
async-timeout = ">=3.0,<4.0"
|
||||||
|
attrs = ">=17.3.0"
|
||||||
|
chardet = ">=2.0,<4.0"
|
||||||
|
multidict = ">=4.5,<5.0"
|
||||||
|
yarl = ">=1.0,<2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
speedups = ["aiodns", "brotlipy", "cchardet"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Timeout context manager for asyncio programs"
|
||||||
|
name = "async-timeout"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.3"
|
||||||
|
version = "3.0.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Classes Without Boilerplate"
|
||||||
|
name = "attrs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "20.2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
|
||||||
|
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||||
|
tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||||
|
tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Screen-scraping library"
|
||||||
|
name = "beautifulsoup4"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "4.9.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
[package.dependencies.soupsieve]
|
||||||
|
python = ">=3.0"
|
||||||
|
version = ">1.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
html5lib = ["html5lib"]
|
||||||
|
lxml = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Dummy package for Beautiful Soup"
|
||||||
|
name = "bs4"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
beautifulsoup4 = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
name = "certifi"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2020.6.20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Universal encoding detector for Python 2 and 3"
|
||||||
|
name = "chardet"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.0.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A mirror package for discord.py. Please install that instead."
|
||||||
|
name = "discord"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
"discord.py" = ">=1.0.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A Python wrapper for the Discord API"
|
||||||
|
name = "discord.py"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.3"
|
||||||
|
version = "1.4.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiohttp = ">=3.6.0,<3.7.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (1.8.5)", "sphinxcontrib-trio (1.1.1)", "sphinxcontrib-websupport"]
|
||||||
|
voice = ["PyNaCl (1.3.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
name = "idna"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "multidict implementation"
|
||||||
|
name = "multidict"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "4.7.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
name = "requests"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "2.24.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
chardet = ">=3.0.2,<4"
|
||||||
|
idna = ">=2.5,<3"
|
||||||
|
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
||||||
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||||
|
marker = "python_version >= \"3.0\""
|
||||||
|
name = "soupsieve"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "2.0.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "ASCII transliterations of Unicode text"
|
||||||
|
name = "unidecode"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.1.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
name = "urllib3"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||||
|
version = "1.25.11"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotlipy (>=0.6.0)"]
|
||||||
|
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||||
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Yet another URL library"
|
||||||
|
name = "yarl"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.6.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.0"
|
||||||
|
multidict = ">=4.0"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
content-hash = "1c7fce4481730b6cbd329be4db35653cf7d827df9ac031227bbb6376d480bf8f"
|
||||||
|
python-versions = "^3.8"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
aiohttp = [
|
||||||
|
{file = "aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e"},
|
||||||
|
{file = "aiohttp-3.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654"},
|
||||||
|
{file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"},
|
||||||
|
{file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"},
|
||||||
|
]
|
||||||
|
async-timeout = [
|
||||||
|
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
|
||||||
|
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
|
||||||
|
]
|
||||||
|
attrs = [
|
||||||
|
{file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"},
|
||||||
|
{file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"},
|
||||||
|
]
|
||||||
|
beautifulsoup4 = [
|
||||||
|
{file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
|
||||||
|
{file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
|
||||||
|
{file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
|
||||||
|
]
|
||||||
|
bs4 = [
|
||||||
|
{file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"},
|
||||||
|
]
|
||||||
|
certifi = [
|
||||||
|
{file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
|
||||||
|
{file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
|
||||||
|
]
|
||||||
|
chardet = [
|
||||||
|
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||||
|
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||||
|
]
|
||||||
|
discord = [
|
||||||
|
{file = "discord-1.0.1-py3-none-any.whl", hash = "sha256:9d4debb4a37845543bd4b92cb195bc53a302797333e768e70344222857ff1559"},
|
||||||
|
{file = "discord-1.0.1.tar.gz", hash = "sha256:ff6653655e342e7721dfb3f10421345fd852c2a33f2cca912b1c39b3778a9429"},
|
||||||
|
]
|
||||||
|
"discord.py" = [
|
||||||
|
{file = "discord.py-1.4.1-py3-none-any.whl", hash = "sha256:98ea3096a3585c9c379209926f530808f5fcf4930928d8cfb579d2562d119570"},
|
||||||
|
{file = "discord.py-1.4.1.tar.gz", hash = "sha256:f9decb3bfa94613d922376288617e6a6f969260923643e2897f4540c34793442"},
|
||||||
|
]
|
||||||
|
idna = [
|
||||||
|
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||||
|
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||||
|
]
|
||||||
|
multidict = [
|
||||||
|
{file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"},
|
||||||
|
{file = "multidict-4.7.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a"},
|
||||||
|
{file = "multidict-4.7.6-cp35-cp35m-win32.whl", hash = "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5"},
|
||||||
|
{file = "multidict-4.7.6-cp35-cp35m-win_amd64.whl", hash = "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3"},
|
||||||
|
{file = "multidict-4.7.6-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87"},
|
||||||
|
{file = "multidict-4.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2"},
|
||||||
|
{file = "multidict-4.7.6-cp36-cp36m-win32.whl", hash = "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7"},
|
||||||
|
{file = "multidict-4.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463"},
|
||||||
|
{file = "multidict-4.7.6-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"},
|
||||||
|
{file = "multidict-4.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255"},
|
||||||
|
{file = "multidict-4.7.6-cp37-cp37m-win32.whl", hash = "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507"},
|
||||||
|
{file = "multidict-4.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c"},
|
||||||
|
{file = "multidict-4.7.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b"},
|
||||||
|
{file = "multidict-4.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7"},
|
||||||
|
{file = "multidict-4.7.6-cp38-cp38-win32.whl", hash = "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d"},
|
||||||
|
{file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"},
|
||||||
|
{file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"},
|
||||||
|
]
|
||||||
|
requests = [
|
||||||
|
{file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
|
||||||
|
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
|
||||||
|
]
|
||||||
|
soupsieve = [
|
||||||
|
{file = "soupsieve-2.0.1-py3-none-any.whl", hash = "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55"},
|
||||||
|
{file = "soupsieve-2.0.1.tar.gz", hash = "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"},
|
||||||
|
]
|
||||||
|
unidecode = [
|
||||||
|
{file = "Unidecode-1.1.1-py2.py3-none-any.whl", hash = "sha256:1d7a042116536098d05d599ef2b8616759f02985c85b4fef50c78a5aaf10822a"},
|
||||||
|
{file = "Unidecode-1.1.1.tar.gz", hash = "sha256:2b6aab710c2a1647e928e36d69c21e76b453cd455f4e2621000e54b2a9b8cce8"},
|
||||||
|
]
|
||||||
|
urllib3 = [
|
||||||
|
{file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"},
|
||||||
|
{file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"},
|
||||||
|
]
|
||||||
|
yarl = [
|
||||||
|
{file = "yarl-1.6.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1"},
|
||||||
|
{file = "yarl-1.6.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188"},
|
||||||
|
{file = "yarl-1.6.0-cp35-cp35m-win32.whl", hash = "sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580"},
|
||||||
|
{file = "yarl-1.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc"},
|
||||||
|
{file = "yarl-1.6.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a"},
|
||||||
|
{file = "yarl-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a"},
|
||||||
|
{file = "yarl-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e"},
|
||||||
|
{file = "yarl-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2"},
|
||||||
|
{file = "yarl-1.6.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e"},
|
||||||
|
{file = "yarl-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a"},
|
||||||
|
{file = "yarl-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131"},
|
||||||
|
{file = "yarl-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d"},
|
||||||
|
{file = "yarl-1.6.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921"},
|
||||||
|
{file = "yarl-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1"},
|
||||||
|
{file = "yarl-1.6.0-cp38-cp38-win32.whl", hash = "sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5"},
|
||||||
|
{file = "yarl-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020"},
|
||||||
|
{file = "yarl-1.6.0.tar.gz", hash = "sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b"},
|
||||||
|
]
|
||||||
18
pyproject.toml
Normal file
18
pyproject.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "cambot"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Simon Junod <sj@simonjunod.ch>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.8"
|
||||||
|
discord = "^1.0.1"
|
||||||
|
unidecode = "^1.1.1"
|
||||||
|
requests = "^2.24.0"
|
||||||
|
bs4 = "^0.0.1"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry>=0.12"]
|
||||||
|
build-backend = "poetry.masonry.api"
|
||||||
221
run.py
Executable file
221
run.py
Executable file
@@ -0,0 +1,221 @@
|
|||||||
|
import sys
|
||||||
|
import discord
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
import cambot.codenames as codenames
|
||||||
|
import cambot.wordle as wordle
|
||||||
|
import cambot.ephemeris as ephemeris
|
||||||
|
from cambot.emojis import *
|
||||||
|
from cambot.settings import *
|
||||||
|
|
||||||
|
bot = discord.Client()
|
||||||
|
codenames_games = {}
|
||||||
|
wordle_games = {}
|
||||||
|
|
||||||
|
# Startup
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f"Connecté, nom {bot.user.name}, id {bot.user.id}")
|
||||||
|
for channel_id in CODENAMES_CHANNEL_IDS:
|
||||||
|
channel = bot.get_channel(channel_id)
|
||||||
|
codenames_game = codenames.Game(channel)
|
||||||
|
codenames_games[channel_id] = codenames_game
|
||||||
|
print(f"Écoute pour Codenames sur {channel.guild} > {channel.name}")
|
||||||
|
for channel_id in WORDLE_CHANNEL_IDS:
|
||||||
|
channel = bot.get_channel(channel_id)
|
||||||
|
wordle_game = wordle.Game(channel)
|
||||||
|
wordle_games[channel_id] = wordle_game
|
||||||
|
print(f"Écoute pour Wordle sur {channel.guild} > {channel.name}")
|
||||||
|
|
||||||
|
current_date = None
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(HEARTBEAT)
|
||||||
|
now = datetime.now()
|
||||||
|
if now.date() != current_date: # the bot was just started, OR it's a new day
|
||||||
|
events_done = [event[1] < now.time() for event in EVENTS] # mark events in the past as done
|
||||||
|
current_date = now.date()
|
||||||
|
for idx, event in enumerate(EVENTS):
|
||||||
|
if event[1] <= now.time() and not events_done[idx]:
|
||||||
|
events_done[idx] = True
|
||||||
|
if event[0] == EPHEMERIS:
|
||||||
|
embed = ephemeris.digest()
|
||||||
|
for channel_id in EPHEMERIS_CHANNEL_IDS:
|
||||||
|
await bot.get_channel(channel_id).send(embed=embed)
|
||||||
|
elif event[0] == WORDLE:
|
||||||
|
for wordle_game in wordle_games.values():
|
||||||
|
await wordle_game.reset()
|
||||||
|
|
||||||
|
# Receiving a message
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message(message):
|
||||||
|
|
||||||
|
# Ignore own messages
|
||||||
|
|
||||||
|
if message.author == bot.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
content = message.content.strip()
|
||||||
|
content_lowercase = content.lower()
|
||||||
|
|
||||||
|
# Private messages
|
||||||
|
|
||||||
|
if isinstance(message.channel, discord.channel.DMChannel):
|
||||||
|
games_entered = [codenames_game for codenames_game in codenames_games.values() if codenames_game.get_player(message.author)]
|
||||||
|
|
||||||
|
if content_lowercase.startswith("say ") and message.author.name == "Biganon" and message.author.discriminator == "0001":
|
||||||
|
arguments = content[4:].split(" ")
|
||||||
|
channel_id = int(arguments[0])
|
||||||
|
to_say = " ".join(arguments[1:])
|
||||||
|
await bot.get_channel(channel_id).send(to_say)
|
||||||
|
|
||||||
|
if content_lowercase == "target" and message.author.name == "Biganon" and message.author.discriminator == "0001":
|
||||||
|
output = ""
|
||||||
|
for wordle_game in wordle_games.values():
|
||||||
|
output += f"{wordle_game.channel.guild} > {wordle_game.channel.name} : {wordle_game.target}\n"
|
||||||
|
await message.author.send(output)
|
||||||
|
|
||||||
|
if len(games_entered) == 1:
|
||||||
|
await codenames.process_whisper(games_entered[0], message)
|
||||||
|
elif len(games_entered) > 1:
|
||||||
|
await message.author.send(f"Tu es inscrit dans plusieurs parties en même temps, je ne peux pas travailler dans ces conditions.")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Help
|
||||||
|
|
||||||
|
if re.search(r"^!(aide|help|commandes)$", content_lowercase):
|
||||||
|
await print_help(message.channel)
|
||||||
|
|
||||||
|
# Judge something
|
||||||
|
|
||||||
|
if regex := re.search(r"^!juger (.+)$", content_lowercase):
|
||||||
|
subject = regex.group(1).strip()
|
||||||
|
score = int(hashlib.md5(bytes(subject.lower(), "utf-8")).hexdigest(), 16) % 2
|
||||||
|
opinion = ("c'est hyper bien", "c'est tellement pas OK")[score]
|
||||||
|
output = '{}, {}'.format(subject, opinion)
|
||||||
|
await message.channel.send(output)
|
||||||
|
|
||||||
|
# Dice
|
||||||
|
|
||||||
|
if regex := re.search(r"^!(?P<number>\d+)?d[eé]s?(?P<maximum>\d+)?$", content_lowercase):
|
||||||
|
thrower = message.author.display_name
|
||||||
|
maximum = 6
|
||||||
|
try:
|
||||||
|
maximum = int(regex.group("maximum"))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
number = 1
|
||||||
|
try:
|
||||||
|
number = int(regex.group("number"))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for n in range(number):
|
||||||
|
result = random.randint(1, maximum)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
output = f"Résultat du lancer de {thrower} : **{sum(results)}**"
|
||||||
|
if len(results) > 1:
|
||||||
|
output += f" ({', '.join(map(str, results))})"
|
||||||
|
|
||||||
|
await message.channel.send(output)
|
||||||
|
|
||||||
|
# Codenames commands
|
||||||
|
|
||||||
|
if message.channel.id in codenames_games.keys():
|
||||||
|
|
||||||
|
game = codenames_games[message.channel.id]
|
||||||
|
|
||||||
|
if re.search(r"^!rouges?$", content_lowercase):
|
||||||
|
await codenames.maybe_join(game, message, codenames.Teams.RED)
|
||||||
|
|
||||||
|
elif re.search(r"^!bleus?$", content_lowercase):
|
||||||
|
await codenames.maybe_join(game, message, codenames.Teams.BLUE)
|
||||||
|
|
||||||
|
elif re.search(r"^!neutres?$", content_lowercase):
|
||||||
|
await codenames.maybe_join(game, message, codenames.Teams.NEUTRAL)
|
||||||
|
|
||||||
|
elif re.search(r"^!r[oô]les?$", content_lowercase):
|
||||||
|
await codenames.maybe_change_role(game, message)
|
||||||
|
|
||||||
|
elif re.search(r"^![eé]quipes?$", content_lowercase):
|
||||||
|
await codenames.print_teams(game)
|
||||||
|
|
||||||
|
elif re.search(r"^!config(uration)?s?$", content_lowercase):
|
||||||
|
await codenames.print_configurations(game)
|
||||||
|
|
||||||
|
elif re.search(r"^!(partir|quitter)$", content_lowercase):
|
||||||
|
await codenames.maybe_leave(game, message)
|
||||||
|
|
||||||
|
elif re.search(r"^!(jouer|play|start)$", content_lowercase):
|
||||||
|
await codenames.maybe_start(game)
|
||||||
|
|
||||||
|
elif regex := re.search(r"^!deviner (.+)$", content_lowercase):
|
||||||
|
await codenames.maybe_guess(game, message, regex.group(1))
|
||||||
|
|
||||||
|
# Wordle
|
||||||
|
|
||||||
|
if message.channel.id in wordle_games.keys():
|
||||||
|
await wordle_games[message.channel.id].parse(message)
|
||||||
|
|
||||||
|
# Help
|
||||||
|
|
||||||
|
async def print_help(channel):
|
||||||
|
output = """__Commandes de CamBot__
|
||||||
|
|
||||||
|
**Divers**
|
||||||
|
!juger `truc` : demander à Camille/CamBot de juger `truc`
|
||||||
|
!XdésY : lancer X dé(s) à Y faces. Le "s" de "dés" est optionnel. Si omis, X vaut 1 et Y vaut 6
|
||||||
|
|
||||||
|
**Codenames**
|
||||||
|
!rouge : rejoindre l'équipe rouge
|
||||||
|
!bleu : rejoindre l'équipe bleue
|
||||||
|
!neutre : rejoindre l'équipe neutre
|
||||||
|
!role : changer de rôle dans son équipe
|
||||||
|
!equipes : afficher la composition des équipes
|
||||||
|
!configurations : afficher les configurations de jeu possibles
|
||||||
|
!partir : quitter une équipe
|
||||||
|
!jouer : lancer la partie
|
||||||
|
!deviner `mot` : deviner le mot `mot`
|
||||||
|
|
||||||
|
**Divers**
|
||||||
|
!aide : afficher ce message
|
||||||
|
"""
|
||||||
|
await channel.send(output)
|
||||||
|
|
||||||
|
# Let unprivileged users pin messages
|
||||||
|
|
||||||
|
async def reaction_changed(payload):
|
||||||
|
emoji = payload.emoji
|
||||||
|
if not emoji.is_unicode_emoji():
|
||||||
|
return
|
||||||
|
if(len(emoji.name) != 1): # flags, Fitzpatrick skin tone, variation selectors...
|
||||||
|
return
|
||||||
|
if emoji.name != PUSHPIN:
|
||||||
|
return
|
||||||
|
event_type = payload.event_type
|
||||||
|
message = await bot.get_channel(payload.channel_id).fetch_message(payload.message_id)
|
||||||
|
if event_type == "REACTION_ADD":
|
||||||
|
await message.pin()
|
||||||
|
elif event_type == "REACTION_REMOVE":
|
||||||
|
await message.unpin()
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_raw_reaction_add(payload):
|
||||||
|
await reaction_changed(payload)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_raw_reaction_remove(payload):
|
||||||
|
await reaction_changed(payload)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
bot.run(DISCORD_TOKEN)
|
||||||
20
scripts/extract_from_grammalecte.py
Normal file
20
scripts/extract_from_grammalecte.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
with open("grammalecte.txt", "r") as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
|
||||||
|
lines = lines[16:]
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
(id_, fid, flexion, lemme, etiquettes, metagraphe, metaphone,
|
||||||
|
notes, semantique, etymologie, sous_dictionnaire, google_1_grams,
|
||||||
|
wikipedia, wikisource, litterature, total, doublons, multiples,
|
||||||
|
frequence, indice) = line.split("\t")
|
||||||
|
|
||||||
|
etiquettes = etiquettes.split()
|
||||||
|
if "nom" in etiquettes:
|
||||||
|
print(flexion)
|
||||||
|
elif "adj" in etiquettes:
|
||||||
|
print(flexion)
|
||||||
|
elif "adv" in etiquettes:
|
||||||
|
print(flexion)
|
||||||
|
elif "infi" in etiquettes:
|
||||||
|
print(flexion)
|
||||||
23
scripts/wordlize.py
Normal file
23
scripts/wordlize.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import sys
|
||||||
|
import re
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
file = sys.argv[1]
|
||||||
|
|
||||||
|
if file == "-":
|
||||||
|
lines = sys.stdin
|
||||||
|
else:
|
||||||
|
with open(file, "r") as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
|
||||||
|
output = set()
|
||||||
|
for line in lines:
|
||||||
|
wordlized = unidecode(line).strip().upper()
|
||||||
|
if not re.match(r"^[A-Z]*$", wordlized): # ignore words with dashes, apostrophes...
|
||||||
|
continue
|
||||||
|
output.add(wordlized)
|
||||||
|
|
||||||
|
output = sorted(list(output))
|
||||||
|
|
||||||
|
for line in output:
|
||||||
|
print(line)
|
||||||
Reference in New Issue
Block a user