πŸ“¬ Command Handler

πŸ“¬ Command Handler

Concept

Here we will implement a Command Handler, this will help us to organize better our bot’s code.
The Command Handler will receive a message and check if it’s a valid function within our files, to do that we will use the
πŸ›„ Teleporter
code snippet as an example and we will modify it a little bit to fit in our Command Handler.
Here’s a quick video showing the Teleporter function working through this Command Handler:
Β 
Β 

Required Libraries

  • highrise-bot-sdk
  • os
  • importlib

Code

Here’s what we will need to import into our bot’s main file:
from highrise import * from highrise.models import * import os import importlib.util
Now we will make a new folder called β€œfunctions” where we will store our functions, see that inside of our new folder I created a file called β€œteleporter.py”:
Files in the workspace.
Files in the workspace.
Now let’s make some changes on our β€œteleporter function” to make sure it works from outside of the β€œBot class” (outside of the main file):
from highrise import * from highrise.models import * async def teleport(self: BaseBot, user: User, message: str)-> None: """ Teleports the user to the specified user or coordinate Usage: /teleport <username> <x,y,z> """ #separates the message into parts #part 1 is the command "/teleport" #part 2 is the name of the user to teleport to (if it exists) #part 3 is the coordinates to teleport to (if it exists) try: command, username, coordinate = message.split(" ") except: await self.highrise.chat("Incorrect format, please use /teleport <username> <x,y,z>") return #checks if the user is in the room room_users = (await self.highrise.get_room_users()).content for user, _ in room_users: if user.username.lower() == username.lower(): user_id = user.id break #if the user_id isn't defined, the user isn't in the room if "user_id" not in locals(): await self.highrise.chat("User not found, please specify a valid user and coordinate") return #checks if the coordinate is in the correct format (x,y,z) try: x, y, z = coordinate.split(",") except: await self.highrise.chat("Coordinate not found or incorrect format, please use x,y,z") return #teleports the user to the specified coordinate await self.highrise.teleport(user_id = user_id, dest = Position(float(x), float(y), float(z)))
Teleporter.py file.
Perfect! Now we can start making our handler, to do that we will first going to create a new function called β€œcommand_handler” inside of our main file that will be triggered if the user says a message that starts with β€œ/”:
from highrise import * from highrise.models import * import os import importlib.util class Bot(BaseBot): async def on_start(self, SessionMetadata: SessionMetadata)-> None: print (f"Starting: {SessionMetadata}") async def on_chat(self, user: User, message: str)-> None: print (f"Received: {message} from {user.username}") if message.startswith("/"): await self.command_handler(user, message) async def command_handler(self, user: User, message: str): pass
Main file, in my case is bot.py
Ok, now we will implement the operation of the command_handler in the main file:
async def command_handler(self, user: User, message: str): parts = message.split(" ") command = parts[0][1:] functions_folder = "functions" # Check if the function exists in the module for file_name in os.listdir(functions_folder): if file_name.endswith(".py"): module_name = file_name[:-3] # Remove the '.py' extension module_path = os.path.join(functions_folder, file_name) # Load the module spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # Check if the function exists in the module if hasattr(module, command) and callable(getattr(module, command)): function = getattr(module, command) await function(self, user, message) return # If no matching function is found return
Main file, in my case is bot.py
The command handler will perform any functions that take an User and a Message as a parameter, the function has to be in the functions folder and to use it the user only has to say β€œ/<name of the function”.
Β 
Our code is done, now let’s summarize how this code works!
  1. The function is defined with the name command_handler and takes two arguments: self , user,and message. The self parameter implies that this function is defined within a class.
  1. The message parameter is a string containing a command that the code needs to handle.
  1. parts = message.split(" "): The input message is split into parts using space (" ") as the delimiter. It assumes that the command is prefixed with a character, like "!command". So, this line separates the prefix from the actual command and any additional arguments.
  1. command = parts[0][1:]: After splitting, the first part of the message will be the command with the prefix. The command variable is then assigned the value of the command without the prefix, removing the first character.
  1. functions_folder = "functions": A variable functions_folder is assigned the value "functions", which is the name of the folder where the command functions are stored.
  1. The code iterates over all files in the functions folder to find a module that contains the appropriate command function.
  1. for file_name in os.listdir(functions_folder):: This loop iterates over the list of files in the functions folder.
  1. if file_name.endswith(".py"):: The code checks if the file is a Python file (ends with ".py").
  1. module_name = file_name[:-3]: It removes the last 3 characters (".py") from the file name to get the module name.
  1. module_path = os.path.join(functions_folder, file_name): The full path of the module is obtained by joining the functions_folder and file_name.
  1. spec = importlib.util.spec_from_file_location(module_name, module_path): A module specification is created using the module name and the path of the module.
  1. module = importlib.util.module_from_spec(spec): A new module is created using the module specification.
  1. spec.loader.exec_module(module): The module is executed, which means the code in the module is run, and it becomes available for use.
  1. The code checks if the loaded module (module) has the specified command (command) as an attribute and if that attribute is callable (meaning it's a function).
  1. if hasattr(module, command) and callable(getattr(module, command)):: If the command function is found in the module, the code proceeds to the next step.
  1. function = getattr(module, command): The command function is obtained from the module using the getattr function.
  1. await function(self, user, message): The command function is called with self user, and message as arguments. The await keyword indicates that this function is an asynchronous function, and it will wait for the command function to complete its execution.
  1. Finally, if no matching function is found, the function returns without doing anything (since the code is not raising an error or performing any other action in case no command is found).
This code is designed to dynamically load and execute command functions from separate Python files located in the "functions" folder based on the input command passed to the command_handler function. The assumption is that each command function has a corresponding Python file with the same name as the command in the "functions" folder.
Β 
Built with Potion.so