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
53 changes: 28 additions & 25 deletions bot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import csv
import pandas as pd
import discord
import logging
Expand All @@ -16,6 +17,11 @@
from comandos.archivar import Archivar
from comandos.enviar import Enviar

# Every cog to register on startup. Add a class here (and to the imports
# above) to wire up a new command/listener group - nothing else needs to
# change.
COGS = (Ping, Ayuda, Limpia, Archivar, Moderacion, FloodSpam, Enviar)

# Global instance of the server
guild = None

Expand All @@ -28,36 +34,39 @@

handler = logging.FileHandler(filename="bot.log", encoding="utf-8", mode="w")
discord.utils.setup_logging(level=logging.INFO, handler=handler)
logger = logging.getLogger(__name__)


@bot.event
async def on_message(message: discord.Message):
# Main log
with open(config.log_main_file, "a") as f:
# Main log. Uses csv.writer (not hand-built quoting) so a message
# containing a literal '"' or a newline - both common in real messages -
# doesn't silently corrupt the row.
with open(config.log_main_file, "a", newline="") as f:
date_str = f"{datetime.now()}"
line = (
f'"{date_str}";'
f'"{message.id}";'
f'"{message.channel}";'
f'"{message.author.id}";'
f'"{message.author}";'
f'"{message.content}"\n'
)
f.write(line)
csv.writer(f, delimiter=";").writerow([
date_str,
"", # command - this log covers every message, not just commands
message.id,
message.channel,
message.author.id,
message.author,
message.content,
])


@bot.event
async def on_ready():
print("Syncing tree...")
logger.info("Syncing tree...")
# await bot.tree.sync()


@bot.event
async def on_command_error(msg, error):
if isinstance(error, (commands.MissingRole, commands.MissingAnyRole)):
print(f"MissingRole ERROR: {error}")
logger.warning("MissingRole ERROR: %s", error)
else:
print(error)
logger.error("Unhandled command error", exc_info=error)


async def main():
Expand All @@ -73,21 +82,15 @@ async def main():
# keeping the data in the bot instance
bot.data_mod = data_mod[~data_mod["message_id"].isin(ready_ids)] # type: ignore[attr-defined]

await bot.add_cog(Ping(bot))
await bot.add_cog(Ayuda(bot))

await bot.add_cog(Limpia(bot))
await bot.add_cog(Archivar(bot))

await bot.add_cog(Moderacion(bot))
await bot.add_cog(FloodSpam(bot))
await bot.add_cog(Enviar(bot))
for cog_cls in COGS:
await bot.add_cog(cog_cls(bot))

# Removing the help command
# bot.remove_command("help")

print("Running...")
logger.info("Running...")
await bot.start(config.TOKEN)


asyncio.run(main())
if __name__ == "__main__":
asyncio.run(main())
21 changes: 21 additions & 0 deletions colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Shared Discord embed colors, used across all cogs.

Centralizing these avoids the same hex value being redefined under
different names in different modules (``WARNING_COLOR`` in flood.py and
``EMBED_COLOR`` in moderacion.py were both ``0x2B597B``), and gives every
other embed color in the project a name instead of leaving it as an
unexplained magic number.
"""

# The project's primary/brand color - used for most informational and
# warning embeds: moderation alerts, help text, moderation confirmations.
BRAND = 0x2B597B

# archivar.py's "channel archived" confirmation embed.
ARCHIVE = 0xFF0000

# limpia.py's "messages purged" confirmation embed.
SUCCESS = 0x178D38

# enviar.py's broadcast-message embeds.
BROADCAST = 0xFDC130
25 changes: 18 additions & 7 deletions comandos/archivar.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import logging
from datetime import datetime
from typing import List, Optional

import discord
from discord.ext import commands

import colors
from configuration import Config

config = Config()
logger = logging.getLogger(__name__)


class Archivar(commands.Cog):
Expand All @@ -28,15 +31,15 @@ async def archivar(self, ctx, *, channel: discord.TextChannel) -> Optional[disco
filename = f"{timestamp}_canal_{channel.name}.csv"
messages = [m async for m in channel.history(limit=None)]

status = self.archivar_canal(filename, messages)
status, archived_filename = self.archivar_canal(filename, messages)

if status:
e = discord.Embed(
title="\N{PAGE FACING UP} Canal Archivado",
description=f"El canal {channel.mention} tiene {len(messages)} mensajes",
colour=0xFF0000,
colour=colors.ARCHIVE,
)
await self.mod_channel.send(embed=e, file=discord.File(filename))
await self.mod_channel.send(embed=e, file=discord.File(archived_filename))
else:
await self.mod_channel.send(f"Error: Canal '{channel.name}' no fue archivado.")

Expand All @@ -53,6 +56,14 @@ async def archivar_categoria(self, ctx, *, category: discord.CategoryChannel):
await self.archivar(ctx, channel=channel)

def archivar_canal(self, filename: str, messages: List[discord.Message]):
"""Write ``messages`` to ``filename`` as CSV.

Always returns a ``(success, filename)`` tuple - never just a bare
falsy value - so callers can safely do
``status, archived_filename = self.archivar_canal(...)`` and check
``status`` directly, instead of risking a non-empty-but-failed
tuple being treated as truthy.
"""
try:
with open(filename, "w") as f:
f.write(
Expand All @@ -63,7 +74,7 @@ def archivar_canal(self, filename: str, messages: List[discord.Message]):
for msg in messages:

if not isinstance(msg.channel, (discord.TextChannel, discord.Thread, discord.VoiceChannel)):
return
return False, None

m_id = msg.id
m_content = msg.content.strip().replace("\n", "\\n")
Expand All @@ -78,9 +89,9 @@ def archivar_canal(self, filename: str, messages: List[discord.Message]):
f"{m_id};{m_content};{m_channel_id};{m_channel_name};{m_channel_category};"
f"{m_author_id};{m_author_name}#{m_author_discriminator};{m_author_bot}\n"
)
print(f"File written: {filename}")
except Exception as e:
print(f"{type(e).__name__}: {e}")
logger.info("File written: %s", filename)
except Exception:
logger.exception("Failed to archive channel into %s", filename)
return False, None

return True, filename
40 changes: 3 additions & 37 deletions comandos/ayuda.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import discord
from discord.ext import commands

import colors
from configuration import Config
from utils import get_moderation_channel

config = Config()

Expand All @@ -17,21 +17,12 @@ async def mensaje_ayuda(self, ctx):
if ctx.author.id == config.BOT_ID:
return

# Check which channel combination we are using from the
# configuration information
channel_mod = get_moderation_channel(self.bot, ctx.channel.id)

if channel_mod:
e = self.get_mod_help()
await channel_mod.send(embed=e)
else:
e = self.get_main_help()
await ctx.channel.send(embed=e)
await ctx.channel.send(embed=self.get_mod_help())

def get_mod_help(self):
embed = discord.Embed(
title="Comandos Disponibles",
colour=0x2B597B,
colour=colors.BRAND,
)
embed.add_field(
name="`%mod`",
Expand Down Expand Up @@ -62,28 +53,3 @@ def get_mod_help(self):
inline=False,
)
return embed

def get_main_help(self):
embed = discord.Embed(
title="Comandos Disponibles",
colour=0x2B597B,
)
embed.add_field(
name='`%encuesta "pregunta"`',
value=(
"Para hacer preguntas de Sí y No.\n" 'Ejemplo:\n `%encuesta "¿Te gusta el té?"`'
),
inline=False,
)
embed.add_field(
name='`%encuesta "pregunta" "opción a" "opción b" ...`',
value=(
"Para hacer preguntas con varias opciones.\n"
'Ejemplo:\n `%encuesta "¿Lenguaje favorito?" "Inglés" "Español" "Python"`'
),
inline=False,
)
embed.set_footer(
text='Importante: La pregunta y opciones deben ir entre comillas dobles "..."'
)
return embed
6 changes: 3 additions & 3 deletions comandos/enviar.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from discord.ext import commands
from discord import TextChannel, Embed, app_commands

import colors
from configuration import Config

config = Config()
COLOR_MSG = 0xfdc130


class Enviar(commands.Cog):
Expand All @@ -22,7 +22,7 @@ async def enviar(self, ctx: commands.Context, channel: TextChannel, *, message:
reply_embed = Embed(
title=f"Mensaje enviado a {channel}",
description=f"{channel.mention}:\n{message}",
colour=COLOR_MSG,
colour=colors.BROADCAST,
)

try:
Expand All @@ -33,7 +33,7 @@ async def enviar(self, ctx: commands.Context, channel: TextChannel, *, message:
embed = Embed(
title="Mensaje de Coordinación",
description=message,
colour=COLOR_MSG,
colour=colors.BROADCAST,
)

# Send the command to the channel passed to the command
Expand Down
Loading
Loading