Skip to content

Commit

Permalink
Better logging (#19)
Browse files Browse the repository at this point in the history
* add a bit more logging + docstrings

* add last error

* add new functions

* add a bit this and this :D

* better status

* add more logging

* impovements

* better error msg

* add

* ade compare better

* correct date

---------

Co-authored-by: Katze719 <katze719@users.noreply.github.com>
  • Loading branch information
Katze719 and Katze719 committed May 28, 2024
1 parent b31df7d commit 03fb333
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 70 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
Version 5.0.0 - 2024-05-28

### What's Changed
- **Neue Befehle:**
- `/use_new_plan_sending_method`
- `/use_old_plan_sending_method`

- Alle bisher experimentellen Funktionen sind jetzt vollständig integriert und erfordern nicht mehr den Befehl `/activate_experimental_features` zur Nutzung.

- Der Bot erfordert nun die Variable `class`, die mit deiner Klasse gesetzt werden muss, bevor er aktiviert werden kann. Verwende dazu folgenden Befehl:
- `/set class <meine Klasse>`
- **Hinweis:** Achte darauf, wie deine Klasse im Vertretungsplan geschrieben ist. Die meisten Klassen haben ein Leerzeichen zwischen Kürzel und Zahl, zum Beispiel `IT 22/5`.

- Du kannst den Bot weiterhin in der alten Version nutzen, um alle Änderungen zu erhalten. Verwende dazu den Befehl `/use_old_plan_sending_method`, bevor du den Bot mit `/activate` aktivierst.
- **Warnung:** Befehle, die die Variable `class` benötigen, können jetzt undefiniertes Verhalten verursachen. Sieh mit `/help` nach, welche Befehle eine Klasse benötigen.

- Der Befehl `/status` gibt nun auch aus ob Experimentelle Funktionen aktiv sind oder nicht.

---

Version 4.7.1-experimental.1 - 2024-05-22

### What's Changed
Expand Down
5 changes: 3 additions & 2 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@
USERNAME = "bsz-et-2324"
PASSWORD = "schulleiter#23"

CURRENT_VERSION = "v4.7.1-experimental.1"
CURRENT_VERSION = "v5.0.0"
CURRENT_VERSION_FILE = f"{os.getenv('SETTINGS_VOLUME')}/version.txt"

if __name__ == '__main__':
Plan.save_settings(SUBSTITUTION_PLAN_PDF_URL, USERNAME, PASSWORD)

@BSZ_BOT.event
async def on_ready():
log.logger.info(f'Bot is ready! Logged in as {BSZ_BOT.user}')
await BSZ_BOT.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=CURRENT_VERSION))
try:
log.logger.info("Syncing commands...")
synced = await BSZ_BOT.tree.sync()
log.logger.info(f"Synced {len(synced)} command(s)")
except Exception as e:
Expand Down
4 changes: 3 additions & 1 deletion bsz_bot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from .tasks import setup_tasks
from .core import BSZ_BOT
from .commands import ping, plan, get, set, reset, changelog, activate, deactivate, status, print_parsed_table_beta, activate_beta_features, deactivate_beta_features, news, feedback, help
from .commands import ping, plan, get, set, reset, changelog, activate, deactivate, status, print_parsed_table_beta, activate_beta_features, deactivate_beta_features, news, feedback, help, use_old_plan_sending_method, use_new_plan_sending_method
from .helpers import Plan, log, send_update_info

BSZ_BOT.tree.add_command(use_old_plan_sending_method)
BSZ_BOT.tree.add_command(use_new_plan_sending_method)
BSZ_BOT.tree.add_command(deactivate_beta_features)
BSZ_BOT.tree.add_command(print_parsed_table_beta)
BSZ_BOT.tree.add_command(activate_beta_features)
Expand Down
4 changes: 3 additions & 1 deletion bsz_bot/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
from .deactivate_beta_features import deactivate_beta_features
from .news import news
from .feedback import feedback
from .help import help
from .help import help
from .use_new_plan_sending_method import use_new_plan_sending_method
from .use_old_plan_sending_method import use_old_plan_sending_method
1 change: 1 addition & 0 deletions bsz_bot/commands/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@discord.app_commands.command(name="activate", description="Activates the bot")
@admin_required
@needs_class
async def activate(ctx : discord.Interaction):
"""
Activates the bot.
Expand Down
1 change: 0 additions & 1 deletion bsz_bot/commands/feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ async def async_copy(source, destination, buffer_size=1024*1024):
await dst.write(chunk)

@discord.app_commands.command(name="feedback", description="Write feedback to the Active Developers.")
@experimental
async def feedback(ctx : discord.Interaction, msg : str):
hash = secure_random_string(6)

Expand Down
33 changes: 18 additions & 15 deletions bsz_bot/commands/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
from ..helpers import *

commands_info = [
{"command": "/help", "description": "Prints a list of available commands", "experimental": False, "admin": False},
{"command": "/activate", "description": "Activates the bot", "experimental": False, "admin": True},
{"command": "/deactivate", "description": "Deactivates the bot", "experimental": False, "admin": True},
{"command": "/deactivate_experimental_features", "description": "Deactivates experimental features", "experimental": False, "admin": True},
{"command": "/activate_experimental_features", "description": "Activates experimental features", "experimental": False, "admin": True},
{"command": "/set <variable_name> <value>", "description": "Sets the specified variable to the given value", "experimental": False, "admin": True},
{"command": "/reset", "description": "Resets all variables to their default values", "experimental": False, "admin": True},
{"command": "/ping", "description": "Checks the bot's responsiveness", "experimental": False, "admin": False},
{"command": "/plan", "description": "Retrieves the current plan", "experimental": False, "admin": False},
{"command": "/get <variable_name>", "description": "Retrieves the value of the specified variable", "experimental": False, "admin": False},
{"command": "/status", "description": "Checks if the bot is currently active", "experimental": False, "admin": False},
{"command": "/feedback <message>", "description": "Submits user feedback", "experimental": False, "admin": False},
{"command": "/print_parsed_table_experimental", "description": "Displays the parsed table", "experimental": True, "admin": False},
{"command": "/news_experimental", "description": "Retrieves the latest news", "experimental": True, "admin": False},
{"command": "/help", "description": "Prints a list of available commands", "experimental": False, "admin": False, "needs_class": False},
{"command": "/activate", "description": "Activates the bot", "experimental": False, "admin": True, "needs_class": True},
{"command": "/deactivate", "description": "Deactivates the bot", "experimental": False, "admin": True, "needs_class": False},
{"command": "/deactivate_experimental_features", "description": "Deactivates experimental features", "experimental": False, "admin": True, "needs_class": False},
{"command": "/activate_experimental_features", "description": "Activates experimental features", "experimental": False, "admin": True, "needs_class": True},
{"command": "/set <variable_name> <value>", "description": "Sets the specified variable to the given value", "experimental": False, "admin": True, "needs_class": False},
{"command": "/reset", "description": "Resets all variables to their default values", "experimental": False, "admin": True, "needs_class": False},
{"command": "/ping", "description": "Checks the bot's responsiveness", "experimental": False, "admin": False, "needs_class": False},
{"command": "/plan", "description": "Retrieves the current plan", "experimental": False, "admin": False, "needs_class": False},
{"command": "/get <variable_name>", "description": "Retrieves the value of the specified variable", "experimental": False, "admin": False, "needs_class": False},
{"command": "/status", "description": "Checks if the bot is currently active", "experimental": False, "admin": False, "needs_class": False},
{"command": "/feedback <message>", "description": "Submits user feedback", "experimental": False, "admin": False, "needs_class": False},
{"command": "/print_parsed_table", "description": "Displays the parsed table", "experimental": False, "admin": False, "needs_class": True},
{"command": "/news", "description": "Retrieves the latest news", "experimental": False, "admin": False, "needs_class": True},
{"command": "/use_new_plan_sending_method", "description": "Use the new plan sending method", "experimental": False, "admin": True, "needs_class": True},
{"command": "/use_old_plan_sending_method", "description": "Use the old plan sending method", "experimental": False, "admin": True, "needs_class": False},
]

@discord.app_commands.command(name="help", description="Print a list of available commands.")
Expand All @@ -25,6 +27,7 @@ async def help(ctx : discord.Interaction):
for info in commands_info:
experimental_note = " (Experimental)" if info["experimental"] else ""
admin_note = " (Admin only)" if info["admin"] else ""
embed.add_field(name=f"{info['command']}{admin_note}{experimental_note}", value=f"{info['description']}", inline=False)
needs_class = " (Requires class)" if info["needs_class"] else ""
embed.add_field(name=f"{info['command']}{admin_note}{needs_class}{experimental_note}", value=f"{info['description']}", inline=False)

await ctx.response.send_message(embed=embed, ephemeral=True)
5 changes: 2 additions & 3 deletions bsz_bot/commands/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
import os
from ..helpers import *

@discord.app_commands.command(name="news_experimental", description="Get the latest news for your class.")
@experimental
@discord.app_commands.command(name="news", description="Get the latest news for your class.")
@needs_class
async def news(ctx : discord.Interaction):
parsed_plan = parse_table(f'{os.getenv("SETTINGS_VOLUME")}/{Plan(ctx.guild).get_file_name()}.pdf')

msg = ''

for event in parsed_plan:
if GuildSettings(ctx.guild).get("class") in event["class"]:
if GuildSettings(ctx.guild).get("class").replace(" ", "") in event["class"].replace(" ", ""):
msg += f"```txt\nAm {event['date']} {event['day']}\nStunde: {event["hours"]}\nLehrer: {event["teacher"]}\nFach: {event["subject"]}\nRaum: {event["room"]}\nInfo: {event["info"]}\n```\n"

if msg != '':
Expand Down
20 changes: 16 additions & 4 deletions bsz_bot/commands/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,22 @@ async def plan(ctx : discord.Interaction):
error_code = await plan.download()
if error_code != 200:
s = GuildSettings(ctx.guild)
await ctx.response.send_message(embed=simple_embed(f"request: {error_code}", f"""ERROR: the pdf document could not be downloaded with:
File URL: {s.get('file_url')}
Username: {s.get('username')}
Password: {s.get('password')}"""))
await ctx.response.send_message(embed=simple_embed(f"Error: {error_code}", f"""ERROR: the pdf document could not be downloaded with:
`File URL: {s.get('file_url')}`
`Username: {s.get('username')}`
`Password: {s.get('password')}`
Debugging tips:
- try `/get all` to see all variables
- try changing the `file_url` with `/set file_url <new value>`
- try changing the `username` with `/set username <new value>`
- try changing the `password` with `/set password <new value>`
- if you want to change a value back to default, use\n`/set <variable> (use default)` or `/reset`\n
If that doesn't work, please open an issue on the GitHub repository.
https://github.com/Katze719/BSZET_IT_BOT
"""))
return

file = discord.File(f"{plan.get_file_name()}.png")
Expand Down
3 changes: 1 addition & 2 deletions bsz_bot/commands/print_parsed_table_beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import os
from ..helpers import *

@discord.app_commands.command(name="print_parsed_table_experimental", description="print the parsed table")
@experimental
@discord.app_commands.command(name="print_parsed_table", description="print the parsed table")
async def print_parsed_table_beta(ctx : discord.Interaction):
parsed_dict = parse_table(f'{os.getenv("SETTINGS_VOLUME")}/{GuildSettings(ctx.guild).get("output_name")}.pdf')

Expand Down
1 change: 1 addition & 0 deletions bsz_bot/commands/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async def set(ctx : discord.Interaction, variable_name: str, value: str):
s.set('routine_channel_id', ctx.channel.id)
await ctx.response.send_message(embed=simple_embed(f'Set {variable_name} and routine_channel_id', f"{variable_name} was set to {value} and routine_channel_id was set to {ctx.channel.id}"), ephemeral=True)
return

await ctx.response.send_message(embed=simple_embed(f'Set {variable_name}', f"{variable_name} was set to {value}"), ephemeral=True)
return
await ctx.response.send_message(embed=simple_embed(f'Set {variable_name}', f"{variable_name} Does not exist!"), ephemeral=True)
9 changes: 6 additions & 3 deletions bsz_bot/commands/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
@discord.app_commands.command(name="status", description="Get the bot status.")
async def status(ctx : discord.Interaction):
s = GuildSettings(ctx.guild)
if s.get("routine") == "True" and s.get("routine_channel_id") != 0:
await ctx.response.send_message(embed=simple_embed(f'Status: Active', "The bot is currently active."))
beta_features = "Experimental Features: Active" if s.get("beta_programm") == True else "Experimental Features: Inactive"
if s.get("routine") == True and s.get("routine_channel_id") != 0:


await ctx.response.send_message(embed=simple_embed(f'Status: Active', f"The bot is currently active.\n{beta_features}\nRoutine Channel: {s.get("routine_channel_id")}\nClass: {s.get("class")}"))
else:
await ctx.response.send_message(embed=simple_embed(f'Status: Inactive', "The bot is currently inactive."))
await ctx.response.send_message(embed=simple_embed(f'Status: Inactive', f"The bot is currently inactive.\n{beta_features}\nRoutine Channel: {s.get("routine_channel_id")}\nClass: {s.get("class")}"))
14 changes: 14 additions & 0 deletions bsz_bot/commands/use_new_plan_sending_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import discord
from ..helpers import *

@discord.app_commands.command(name="use_new_plan_sending_method", description="Use the new plan sending method.")
@admin_required
@needs_class
async def use_new_plan_sending_method(ctx : discord.Interaction):
if GuildSettings(ctx.guild).get("class") == "unknown":
await ctx.response.send_message(embed=simple_embed('Error', "Class is not set.\n Set it with `/set class <classname>`"))
return

GuildSettings(ctx.guild).set("use_old_plan_function", False)

await ctx.response.send_message(embed=simple_embed(f'Using new plan sending method!'))
9 changes: 9 additions & 0 deletions bsz_bot/commands/use_old_plan_sending_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import discord
from ..helpers import *

@discord.app_commands.command(name="use_old_plan_sending_method", description="Use the old plan sending method.")
@admin_required
async def use_old_plan_sending_method(ctx : discord.Interaction):
GuildSettings(ctx.guild).set("use_old_plan_function", True)
GuildSettings(ctx.guild).set("class", "unknown")
await ctx.response.send_message(embed=simple_embed(f'Using old plan sending method!'))
63 changes: 61 additions & 2 deletions bsz_bot/helpers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,102 @@
T = TypeVar('T', bound=Callable[..., Coroutine[Any, Any, Any]])

def admin_required(func: T) -> T:
"""
Decorator that checks if the user invoking the command is an admin.
Parameters:
func (T): The function to be decorated.
Returns:
T: The decorated function.
Raises:
ValueError: If the context parameter is missing or not the first argument.
Notes:
- This decorator checks if the user invoking the command has the 'administrator' permission in the guild.
- If the user is not an admin, it sends an error message to the user and returns early.
- If the user is an admin, it calls the decorated function with the given arguments and returns its result.
"""
@wraps(func)
async def wrapper(*args, **kwargs) -> Any:
ctx = args[0]
if not isinstance(ctx, discord.Interaction):
raise ValueError("Context parameter missing or not first argument.")


logger.info(f"Checking if {ctx.user} is an admin...")
if not ctx.user.guild_permissions.administrator:
logger.info(f"{ctx.user} is not an admin.")
await ctx.response.send_message(embed=simple_embed("Error", "You are not an admin."), ephemeral=True)
return
logger.info(f"{ctx.user} is an admin.")
return await func(*args, **kwargs)
return wrapper

def experimental(func: T) -> T:
"""
Decorator that checks if the experimental features are activated for the guild.
Parameters:
func (T): The function to be decorated.
Returns:
T: The decorated function.
Raises:
ValueError: If the context parameter is missing or not the first argument.
Notes:
- This decorator checks if the experimental features are activated for the guild by checking the "beta_programm" setting in the GuildSettings.
- If the experimental features are not activated, it sends an error message to the user and returns early.
- If the experimental features are activated, it calls the decorated function with the given arguments and returns its result.
"""
@wraps(func)
async def wrapper(*args, **kwargs) -> Any:
ctx = args[0]
if not isinstance(ctx, discord.Interaction):
raise ValueError("Context parameter missing or not first argument.")

logger.info(f"Checking if {ctx.guild} has activated experimental features...")
if GuildSettings(ctx.guild).get("beta_programm") != True:
logger.info(f"{ctx.guild} has not activated experimental features.")
await ctx.response.send_message(embed=simple_embed('Error', "Experimental Features are not activated."))
return

logger.info(f"{ctx.guild} has activated experimental features.")
return await func(*args, **kwargs)
return wrapper

def needs_class(func: T) -> T:
"""
Decorator that checks if the guild has set a class.
Parameters:
func (T): The function to be decorated.
Returns:
T: The decorated function.
Raises:
ValueError: If the context parameter is missing or not the first argument.
Notes:
- This decorator checks if the guild has set a class by checking the "class" setting in the GuildSettings.
- If the guild has not set a class, it sends an error message to the user and returns early.
- If the guild has set a class, it calls the decorated function with the given arguments and returns its result.
"""
@wraps(func)
async def wrapper(*args, **kwargs) -> Any:
ctx = args[0]
if not isinstance(ctx, discord.Interaction):
raise ValueError("Context parameter missing or not first argument.")

logger.info(f"Checking if {ctx.guild} has set a class...")
if not GuildSettings(ctx.guild).get("class"):
logger.info(f"{ctx.guild} has not set a class.")
await ctx.response.send_message(embed=simple_embed('Error', "Class is not set.\n Set it with `/set class <classname>`"))
return


logger.info(f"{ctx.guild} has set a class.")
return await func(*args, **kwargs)
return wrapper
6 changes: 6 additions & 0 deletions bsz_bot/helpers/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def __init__(self, guild : discord.Guild):
self.set("output_name", f"{guild.id}")
self.set("beta_programm", False)
self.set("class", False)
self.set("error_last_time", False)
self.set("use_old_plan_function", False)

self.__backwards_compatibility_check(guild)

Expand Down Expand Up @@ -66,6 +68,10 @@ def __backwards_compatibility_check(self, guild : discord.Guild):
self.set("beta_programm", False)
if not "class" in self.settings:
self.set("class", False)
if not "error_last_time" in self.settings:
self.set("error_last_time", False)
if not "use_old_plan_function" in self.settings:
self.set("use_old_plan_function", False)


def load_settings(self):
Expand Down
Loading

0 comments on commit 03fb333

Please sign in to comment.