Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions .github/workflows/DiscordBot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,35 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Set up QEMU
uses: docker/setup-qemu-action@v1

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1


- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: plop91/plop_discord
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-

- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_SECRET }}

- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: plop91/plop_discord:nightly
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
22 changes: 11 additions & 11 deletions cogs/adminCog.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@ async def kill(self, ctx):
if str(ctx.author) in settings.info_json["admins"]:
settings.logger.info(f"kill from {ctx.author}!")
if str(ctx.message.channel) in settings.info_json["command_channels"]:
await self.client.logout()
await self.client.close()
else:
await ctx.channel.send(ctx.author)
await ctx.channel.send(settings.info_json["admins"])
await ctx.channel.send("you are not an admin")
# if the bot fails to log out kill it
await ctx.channel.send("You do not have permission to run this command")
settings.logger.warning(f"Unauthorized kill attempt by {ctx.author}")
# if the bot fails to close kill it
except Exception:
exit(1)

@commands.command(brief="Admin only command: Restart the bot.")
async def restart(self, ctx):
"""
Preforms a restart of the bot
Note: This command closes the bot. The bot should be managed by a process manager
(like systemd or docker) that will automatically restart it.
:arg ctx: context of the command
:return: None
"""
Expand All @@ -78,13 +79,12 @@ async def restart(self, ctx):
if str(ctx.author) in settings.info_json["admins"]:
settings.logger.info(f"restart from {ctx.author}!")
if str(ctx.message.channel) in settings.info_json["command_channels"]:
await self.client.logout()
await self.client.start(settings.info_json["token"])
await ctx.send("Restarting bot... (requires process manager)")
await self.client.close()
else:
await ctx.channel.send(ctx.author)
await ctx.channel.send(settings.info_json["admins"])
await ctx.channel.send("you are not an admin")
# if the bot fails to log out kill it
await ctx.channel.send("You do not have permission to run this command")
settings.logger.warning(f"Unauthorized restart attempt by {ctx.author}")
# if the bot fails to close kill it
except Exception:
exit(1)

Expand Down
34 changes: 28 additions & 6 deletions cogs/audioCog.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ async def on_message(self, message):
f"The audio is being downloaded and should be ready shortly the name of the clip will "
f"be: {filename.replace('.mp3', '')}")
await attachment.save(f"./soundboard/raw/{filename}")
audio_json = ffmpeg.probe(f"./soundboard/raw/{filename}")
# Run ffmpeg.probe in executor to avoid blocking event loop
loop = asyncio.get_event_loop()
audio_json = await loop.run_in_executor(None, ffmpeg.probe, f"./soundboard/raw/{filename}")

# If the clip is too long it needs to be reviewed
if float(audio_json['streams'][0]['duration']) >= 60:
Expand Down Expand Up @@ -240,14 +242,16 @@ async def play_clip(self, text_channel, voice_channel, filename):
f = filename.replace("soundboard/", "").replace(".mp3", "")

embed_var = discord.Embed(title="Play Command",
description=f"{text_channel.author} played a random clip: {f}",
description=f"Playing random clip: {f}",
color=0xffff00)
else:
embed_var = discord.Embed(title="Play Command",
description=f"{text_channel.author} played: {fn}",
description=f"Playing: {fn}",
color=0xffff00)

self.ghost_message[text_channel.guild.id] = await text_channel.channel.send(embed=embed_var)
# text_channel could be a Context object or a Channel object
channel = text_channel.channel if hasattr(text_channel, 'channel') else text_channel
self.ghost_message[text_channel.guild.id] = await channel.send(embed=embed_var)

except AttributeError:
settings.logger.info(f"Attribute Error: {traceback.format_exc()}")
Expand Down Expand Up @@ -473,8 +477,19 @@ async def get(self, ctx, sound: str):
:arg sound: sound to return
:return: None
"""
if os.path.isfile(os.path.join("soundboard", sound)):
await ctx.channel.send(sound, file=discord.File(sound + ".mp3", os.path.join("soundboard", sound)))
# Validate filename to prevent path traversal
if '..' in sound or sound.startswith('/') or sound.startswith('\\'):
await ctx.channel.send("Invalid filename")
return

filepath = os.path.join("soundboard", sound)
# Ensure the resolved path is within the soundboard directory
if not os.path.abspath(filepath).startswith(os.path.abspath("soundboard")):
await ctx.channel.send("Invalid filename")
return

if os.path.isfile(filepath):
await ctx.channel.send(sound, file=discord.File(filepath))

@commands.command(aliases=['SAY'],
brief="",
Expand All @@ -489,6 +504,13 @@ async def say(self, ctx, text, *, tts_file='say'):
"""
settings.logger.info(f"say from {ctx.author} text:{text}")
text = text.strip().lower()

# Limit TTS text length to prevent abuse
MAX_TTS_LENGTH = 500
if len(text) > MAX_TTS_LENGTH:
await ctx.send(f"Text too long. Max {MAX_TTS_LENGTH} characters allowed")
return

gTTS(text).save(os.path.join("soundboard", tts_file + '.mp3'))
await self.play_clip(ctx, ctx.voice_client, tts_file)
await ctx.message.delete()
Expand Down
19 changes: 15 additions & 4 deletions cogs/gameCog.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,25 @@ async def teams(self, ctx, teams="2"):
:return: None
"""

iteams = int(teams)
try:
iteams = int(teams)
except ValueError:
await ctx.send("Invalid number of teams")
return

# get current voice channel of author
voice = ctx.author.voice.channel

if voice is not None:
# filter bots from list of members in channel
people = list(filter(lambda x: (not x.bot), voice.members))
settings.logger.info(f"{iteams} teams with members: ".join(m.name for m in people))
members_str = ", ".join(m.name for m in people)
settings.logger.info(f"{iteams} teams with members: {members_str}")

if iteams < 2:
iteams = 2


if len(people) < iteams:
settings.logger.info(f"Not enough players for {iteams} teams.")
await ctx.send(f"Not enough players for {iteams} teams.")
Expand All @@ -113,8 +119,13 @@ async def roll(self, ctx, sides, times="1"):
:arg times: number of times to roll
:return: None
"""
isides = int(sides)
itimes = int(times)
try:
isides = int(sides)
itimes = int(times)
except ValueError:
await ctx.send("Invalid number for sides or times")
return

settings.logger.info(f"roll from {ctx.author}: {isides} sides")
if isides > 1 and itimes > 0:
await ctx.message.channel.send(
Expand Down
6 changes: 5 additions & 1 deletion cogs/generalCog.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ async def repeat(self, ctx, times: int, content='repeating...'):
:arg content: content to repeat
:return: None
"""
MAX_REPEATS = 10
if times > MAX_REPEATS:
await ctx.send(f"Max {MAX_REPEATS} repeats allowed")
return
for i in range(times):
await ctx.send(content)

Expand All @@ -112,7 +116,7 @@ async def status(self, ctx):
await ctx.channel.send(embed=embed_var)
await ctx.message.delete()

@tasks.loop(seconds=0, minutes=30, hours=1)
@tasks.loop(hours=1)
async def change_status(self):
"""
changes the bot to a randomly provided status.
Expand Down
80 changes: 75 additions & 5 deletions cogs/openAiCog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,31 @@

global logger

blacklist = []
BLACKLIST_FILE = "openai_blacklist.json"


def load_blacklist():
"""Load blacklist from file"""
try:
if os.path.exists(BLACKLIST_FILE):
with open(BLACKLIST_FILE, 'r') as f:
return json.load(f)
return []
except Exception as e:
settings.logger.error(f"Error loading blacklist: {e}")
return []


def save_blacklist(blacklist_data):
"""Save blacklist to file"""
try:
with open(BLACKLIST_FILE, 'w') as f:
json.dump(blacklist_data, f, indent=4)
except Exception as e:
settings.logger.error(f"Error saving blacklist: {e}")


blacklist = load_blacklist()


def blacklisted(user):
Expand Down Expand Up @@ -70,6 +94,7 @@ async def on_ready(self):

@commands.command(pass_context=True, aliases=["genimg", "genimage", "gen_image"],
brief="generate an image from a prompt using openai")
@commands.cooldown(1, 60, commands.BucketType.user)
async def gen_img(self, ctx, *args):
"""
Generate an image from a prompt using openai
Expand Down Expand Up @@ -108,14 +133,26 @@ async def gen_img(self, ctx, *args):

@commands.command(pass_context=True, aliases=["editimg", "editimage", "edit_image"],
brief="edit an image from a prompt using openai")
<<<<<<< HEAD
async def edit_img(self, ctx):
=======
@commands.cooldown(1, 60, commands.BucketType.user)
async def edit_img(self, ctx, *args):
>>>>>>> f372db8 (Fix critical bugs and security issues from code review)
"""
Edit an image from a prompt using openai
:arg ctx: Context
:return: None
"""

<<<<<<< HEAD
if not blacklisted(ctx.author):
=======
if not blackisted(ctx.author):
if not ctx.message.attachments:
await ctx.send("No image attached")
return
>>>>>>> f372db8 (Fix critical bugs and security issues from code review)
if ctx.message.attachments[0] is None:
await ctx.send("No image attached")
return
Expand All @@ -127,11 +164,27 @@ async def edit_img(self, ctx):
png = png.resize((1024, 1024))
png.save("temp.png", 'png', quality=100)
settings.logger.info(f"editing image")
<<<<<<< HEAD
response = self.openai_client.images.create_variation(
image=open("temp.png", "rb"),
n=1,
size="1024x1024"
)
=======
# response = openai.Image.create_edit(
# image=open("temp.png", "rb"),
# mask=open("mask.png", "rb"),
# prompt=prompt,
# n=1,
# size="1024x1024"
# )
with open("temp.png", "rb") as image_file:
response = self.openai_client.images.create_variation(
image=image_file,
n=1,
size="1024x1024"
)
>>>>>>> f372db8 (Fix critical bugs and security issues from code review)
os.remove("temp.png")
image_url = response['data'][0]['url']
image_filename = wget.download(image_url)
Expand Down Expand Up @@ -184,6 +237,7 @@ async def list_assistants(self, ctx):

@commands.command(pass_context=True, aliases=["cra", "createassistant"],
brief="Create an assistant from a prompt using openai")
@commands.cooldown(1, 60, commands.BucketType.user)
async def create_assistant(self, ctx, name, *args):
"""
Create an assistant from a prompt using openai
Expand Down Expand Up @@ -348,6 +402,7 @@ async def handle_tool_call(self, ctx, run, thread_id):

@commands.command(pass_context=True, aliases=["ca", "chatassistant"],
brief="chat with an assistant using openai")
@commands.cooldown(1, 60, commands.BucketType.user)
async def chat_assistant(self, ctx, name, *args):
"""
Chat with an assistant using openai
Expand Down Expand Up @@ -397,6 +452,7 @@ async def chat_assistant(self, ctx, name, *args):
run_id=run.id
)

<<<<<<< HEAD
current_time = time.time()
if current_time - start_time > 60:
# TODO: if the assistant times out, deduct from the user's usage, then cancel the run
Expand All @@ -412,6 +468,10 @@ async def chat_assistant(self, ctx, name, *args):
return

if "completed" in run.status:
=======
if run.status == "completed":
# await ctx.send("Assistant complete")
>>>>>>> f372db8 (Fix critical bugs and security issues from code review)
break
elif run.status == "queued":
pass
Expand Down Expand Up @@ -472,8 +532,13 @@ async def openai_ban(self, ctx, *user):
:return: None
"""
if ctx.author in settings.info_json["admins"]:
blacklist.append(str(user).strip().lower())
await ctx.send(f"{user} has been banned from using the openai cog")
user_str = str(user).strip().lower()
if user_str not in blacklist:
blacklist.append(user_str)
save_blacklist(blacklist)
await ctx.send(f"{user} has been banned from using the openai cog")
else:
await ctx.send(f"{user} is already banned")
else:
await ctx.send(f"{ctx.author} is not an admin and cannot ban someone from using the openai cog")

Expand All @@ -487,8 +552,13 @@ async def openai_unban(self, ctx, *user):
:return: None
"""
if ctx.author in settings.info_json["admins"]:
blacklist.remove(str(user).strip().lower())
await ctx.send(f"{user} has been unbanned from using the openai cog")
user_str = str(user).strip().lower()
if user_str in blacklist:
blacklist.remove(user_str)
save_blacklist(blacklist)
await ctx.send(f"{user} has been unbanned from using the openai cog")
else:
await ctx.send(f"{user} is not in the blacklist")
else:
await ctx.send(f"{user} is not an admin and cannot be unbanned from using the openai cog")

Expand Down
Loading
Loading