Compare commits

...

2 commits

5 changed files with 244 additions and 147 deletions

View file

@ -1,13 +1,15 @@
import logging
import pickle
import random
import tomllib
import discord
from botbotbot.ai import AIBot
from botbotbot.shuffle import Shuffler
from botbotbot.text import TextBot
from botbotbot.tts import CambAI
from botbotbot.voice import VoiceBot
from botbotbot.wordlist import Wordlist
def main() -> None:
@ -19,10 +21,10 @@ def main() -> None:
with open("config.toml", "rb") as config_file:
config = tomllib.load(config_file)
with open("wordlist.pickle", "rb") as word_file:
word_list = pickle.load(word_file)
guild_ids = config.get("guild_ids")
delay = config.get("delay", 60)
if not (isinstance(guild_ids, list) and all(isinstance(i, int) for i in guild_ids)):
logger.error("Guild IDs must be a list of integers.")
guild_ids = []
system_prompt = """Tu es une intelligence artificelle qui répond en français.
Tu dois faire un commentaire pertinent en lien avec ce qui te sera dit.
@ -50,166 +52,35 @@ def main() -> None:
intents.voice_states = True
bot = discord.Bot(description=description, intents=intents)
shf = Shuffler()
wl = Wordlist(bot, guild_ids)
wl.init_events()
text_bot = TextBot(bot, aibot, word_list)
text_bot = TextBot(bot, wl, aibot=aibot, shuffler=shf)
text_bot.init_events()
shuffle_tasks = set()
voice_bot = VoiceBot(bot, cambai, shuffler=shf)
voice_bot.init_events()
@bot.listen("on_ready")
async def on_ready() -> None:
logger.info(f"We have logged in as {bot.user}")
@bot.listen("on_reaction_add")
async def add_more_reaction(
reaction: discord.Reaction, user: discord.Member | discord.User
) -> None:
if random.random() < 50 / 100:
logger.info(f"Copy reaction from {user}")
await reaction.message.add_reaction(reaction.emoji)
@bot.listen("on_message_edit")
async def react_message_edit(
before: discord.Message, after: discord.Message
) -> None:
if (
before.content != after.content
and after.author != bot.user
and random.random() < 50 / 100
):
logger.info(f"React to edit from {after.author}")
await after.add_reaction("👀")
@bot.listen("on_message")
async def rando_shuffle(message: discord.Message) -> None:
if not message.flags.ephemeral and random.random() < 5 / 100 and message.guild:
logger.info(f"Message shuffle after message from {message.author}")
await try_shuffle(message.guild)
def save_wordlist() -> None:
logger.info("Saving updated wordlist")
with open("wordlist.pickle", "wb") as word_file:
pickle.dump(word_list, word_file)
@bot.slash_command(
name="bibl", guild_ids=guild_ids, description="Ajouter une phrase"
)
async def bibl(ctx: discord.ApplicationContext, phrase: str) -> None:
logger.info(f"BIBL {ctx.author} {phrase}")
word_list.append(phrase)
embed = discord.Embed(
title="BIBL", description=phrase, color=discord.Colour.green()
)
await ctx.respond(embed=embed)
save_wordlist()
logger.info("FIN BIBL")
@bot.slash_command(
name="tabl", guild_ids=guild_ids, description="Lister les phrases"
)
async def tabl(ctx: discord.ApplicationContext) -> None:
logger.info(f"TABL {ctx.author}")
embed = discord.Embed(
title="TABL", description="\n".join(word_list), color=discord.Colour.green()
)
await ctx.respond(embed=embed, ephemeral=True, delete_after=delay)
@bot.slash_command(
name="enle", guild_ids=guild_ids, description="Enlever une phrase"
)
async def enle(ctx: discord.ApplicationContext, phrase: str) -> None:
logger.info(f"ENLE {ctx.author} {phrase}")
try:
word_list.remove(phrase)
except ValueError:
embed = discord.Embed(
title="ERRE ENLE", description=phrase, color=discord.Colour.red()
)
await ctx.respond(embed=embed)
logger.info("ERRE ENLE")
else:
embed = discord.Embed(
title="ENLE", description=f"~~{phrase}~~", color=discord.Colour.green()
)
await ctx.respond(embed=embed, ephemeral=True, delete_after=delay)
save_wordlist()
logger.info("FIN ENLE")
async def try_shuffle(guild: discord.Guild) -> bool:
if guild.id in shuffle_tasks:
return False
shuffle_tasks.add(guild.id)
await shuffle_nicks(guild)
shuffle_tasks.discard(guild.id)
return True
async def shuffle_nicks(guild: discord.Guild) -> None:
logger.info("Shuffle")
members = guild.members
if guild.owner:
members.remove(guild.owner)
nicks = [member.nick for member in members]
random.shuffle(nicks)
for member, nick in zip(members, nicks):
logger.info(f"{member} {nick}")
await member.edit(nick=nick)
logger.info("Shuffle done")
@bot.slash_command(
name="alea", guild_ids=guild_ids, description="Modifier les pseudos"
)
async def alea(ctx: discord.ApplicationContext) -> None:
logger.info(f"ALEA {ctx.author}")
await ctx.defer()
if await try_shuffle(ctx.guild):
if await shf.try_shuffle(ctx.guild):
embed = discord.Embed(title="ALEA", color=discord.Colour.green())
await ctx.respond(embed=embed, ephemeral=True, delete_after=delay)
await ctx.respond(embed=embed, ephemeral=True, delete_after=30)
logger.info("FIN ALEA")
else:
embed = discord.Embed(title="ERRE ALEA", color=discord.Colour.red())
await ctx.respond(embed=embed)
logger.info("ERRE ALEA")
@bot.listen("on_voice_state_update")
async def on_voice_state_update(
member: discord.Member, before: discord.VoiceState, after: discord.VoiceState
) -> None:
if before.channel is None and random.random() < 5 / 100:
logger.info(f"Voice shuffle from {member}")
await try_shuffle(member.guild)
logger.debug("Voice state update")
logger.debug(before.channel)
logger.debug(after.channel)
if after.channel:
logger.debug(after.channel.members)
if (
cambai is not None
and before.channel is None
and after.channel is not None
and bot not in after.channel.members
and bot.user
and member.id != bot.user.id
and random.random() < 5 / 100
):
logger.info("Generating tts")
script = random.choice(
[
"Salut la jeunesse !",
f"Salut {member.display_name}, ça va bien ?",
"Allo ? À l'huile !",
]
)
source = await discord.FFmpegOpusAudio.from_probe(cambai.tts(script))
vo: discord.VoiceClient = await after.channel.connect()
await vo.play(source, wait_finish=True)
await vo.disconnect()
@bot.slash_command(
name="indu", guild_ids=guild_ids, description="Poser une question à MistralAI"
)

34
botbotbot/shuffle.py Normal file
View file

@ -0,0 +1,34 @@
import logging
import random
import discord
logger = logging.getLogger(__name__)
class Shuffler:
def __init__(self) -> None:
self.shuffle_tasks: set[int] = set()
async def try_shuffle(self, guild: discord.Guild) -> bool:
if guild.id in self.shuffle_tasks:
return False
self.shuffle_tasks.add(guild.id)
await self.shuffle_nicks(guild)
self.shuffle_tasks.discard(guild.id)
return True
async def shuffle_nicks(self, guild: discord.Guild) -> None:
logger.info("Shuffle")
members = guild.members
if guild.owner:
members.remove(guild.owner)
nicks = [member.nick for member in members]
random.shuffle(nicks)
for member, nick in zip(members, nicks):
logger.info(f"{member} {nick}")
await member.edit(nick=nick)
logger.info("Shuffle done")

View file

@ -6,6 +6,8 @@ import discord
import emoji
from botbotbot.ai import AIBot
from botbotbot.shuffle import Shuffler
from botbotbot.wordlist import Wordlist
logger = logging.getLogger(__name__)
@ -16,17 +18,22 @@ class TextBot:
def __init__(
self,
bot: discord.Bot,
wordlist: Wordlist,
aibot: AIBot | None = None,
wordlist: list[str] = [],
shuffler: Shuffler | None = None,
rnd_weights: list[float] = [10, 5, 10],
) -> None:
self.bot = bot
self.aibot = aibot
self.word_list = wordlist
self.wl = wordlist
self._rnd_weights = rnd_weights
self.shf = shuffler
def init_events(self) -> None:
self.bot.add_listener(self.on_message, "on_message")
self.bot.add_listener(self.add_more_reaction, "on_reaction_add")
self.bot.add_listener(self.react_message_edit, "on_message_edit")
self.bot.add_listener(self.rando_shuffle, "on_message")
@property
def rnd_weights(self) -> list[float]:
@ -77,8 +84,8 @@ class TextBot:
)[0]
content = random.choice(
(
f"{mention}, {random.choice(self.word_list)}",
f"{random.choice(self.word_list)}",
f"{mention}, {self.wl.random()}",
f"{self.wl.random()}",
)
)
@ -144,3 +151,31 @@ class TextBot:
webhook = await channel.create_webhook(name="BotbotbotHook")
await webhook.send(content=content, username=name, avatar_url=avatar_url)
async def add_more_reaction(
self, reaction: discord.Reaction, user: discord.Member | discord.User
) -> None:
if random.random() < 20 / 100:
logger.info(f"Copy reaction from {user}")
await reaction.message.add_reaction(reaction.emoji)
async def react_message_edit(
self, before: discord.Message, after: discord.Message
) -> None:
if (
before.content != after.content
and after.author != self.bot.user
and random.random() < 20 / 100
):
logger.info(f"React to edit from {after.author}")
await after.add_reaction("👀")
async def rando_shuffle(self, message: discord.Message) -> None:
if (
self.shf
and not message.flags.ephemeral
and random.random() < 5 / 100
and message.guild
):
logger.info(f"Message shuffle after message from {message.author}")
await self.shf.try_shuffle(message.guild)

63
botbotbot/voice.py Normal file
View file

@ -0,0 +1,63 @@
import logging
import random
import discord
from botbotbot.shuffle import Shuffler
from botbotbot.tts import CambAI
logger = logging.getLogger(__name__)
class VoiceBot:
def __init__(
self,
bot: discord.Bot,
cambai: CambAI | None = None,
shuffler: Shuffler | None = None,
) -> None:
self.bot = bot
self.cambai = cambai
self.shf = shuffler
def init_events(self) -> None:
self.bot.add_listener(self.on_voice_state_update, "on_voice_state_update")
async def on_voice_state_update(
self,
member: discord.Member,
before: discord.VoiceState,
after: discord.VoiceState,
) -> None:
logger.debug("Voice state update")
logger.debug(before.channel)
logger.debug(after.channel)
if after.channel:
logger.debug(after.channel.members)
if (
self.cambai is not None
and before.channel is None
and after.channel is not None
and self.bot not in after.channel.members
and self.bot.user
and member.id != self.bot.user.id
and random.random() < 5 / 100
):
logger.info("Generating tts")
script = random.choice(
[
"Salut la jeunesse !",
f"Salut {member.display_name}, ça va bien ?",
"Allo ? À l'huile !",
]
)
source = await discord.FFmpegOpusAudio.from_probe(self.cambai.tts(script))
vo: discord.VoiceClient = await after.channel.connect()
await vo.play(source, wait_finish=True)
await vo.disconnect()
if self.shf and before.channel is None and random.random() < 5 / 100:
logger.info(f"Voice shuffle from {member}")
await self.shf.try_shuffle(member.guild)

94
botbotbot/wordlist.py Normal file
View file

@ -0,0 +1,94 @@
import logging
import pathlib
import pickle
import random
import discord
logger = logging.getLogger(__name__)
class Wordlist:
def __init__(
self,
bot: discord.Bot,
guild_ids: list[int] = [],
path: pathlib.Path = pathlib.Path("wordlist.pickle"),
):
self.path = path
if self.path.exists():
with self.path.open("rb") as w_file:
self.word_list: list[str] = pickle.load(w_file)
self.bot = bot
self.guild_ids = guild_ids
def init_events(self) -> None:
self.bot.add_application_command(
discord.SlashCommand(
self.bibl,
name="bibl",
description="Ajouter une phrase",
guild_ids=self.guild_ids,
)
)
self.bot.add_application_command(
discord.SlashCommand(
self.tabl,
name="tabl",
description="Lister les phrases",
guild_ids=self.guild_ids,
)
)
self.bot.add_application_command(
discord.SlashCommand(
self.enle,
name="enle",
description="Enlever une phrase",
guild_ids=self.guild_ids,
)
)
def random(self) -> str:
return random.choice(self.word_list)
def save(self) -> None:
logger.info("Saving updated wordlist")
with open(self.path, "wb") as w_file:
pickle.dump(self.word_list, w_file)
async def bibl(self, ctx: discord.ApplicationContext, phrase: str) -> None:
logger.info(f"BIBL {ctx.author} {phrase}")
self.word_list.append(phrase)
embed = discord.Embed(
title="BIBL", description=phrase, color=discord.Colour.green()
)
await ctx.respond(embed=embed)
self.save()
logger.info("FIN BIBL")
async def tabl(self, ctx: discord.ApplicationContext) -> None:
logger.info(f"TABL {ctx.author}")
embed = discord.Embed(
title="TABL",
description="\n".join(self.word_list),
color=discord.Colour.green(),
)
await ctx.respond(embed=embed, ephemeral=True, delete_after=30)
async def enle(self, ctx: discord.ApplicationContext, phrase: str) -> None:
logger.info(f"ENLE {ctx.author} {phrase}")
try:
self.word_list.remove(phrase)
except ValueError:
embed = discord.Embed(
title="ERRE ENLE", description=phrase, color=discord.Colour.red()
)
await ctx.respond(embed=embed)
logger.info("ERRE ENLE")
else:
embed = discord.Embed(
title="ENLE", description=f"~~{phrase}~~", color=discord.Colour.green()
)
await ctx.respond(embed=embed, ephemeral=True, delete_after=30)
self.save()
logger.info("FIN ENLE")