Creating Bots ============= Basic Structure --------------- A minimal LXMFy bot involves: 1. Importing :code:`LXMFBot`. 2. Instantiating :code:`LXMFBot` with desired configuration. 3. Defining commands or event handlers. 4. Running the bot using :code:`bot.run()`. .. code-block:: python from lxmfy import LXMFBot # 1. Instantiate the bot bot = LXMFBot( name="SimpleBot", command_prefix="!", storage_path="simple_data" ) # 2. Define commands @bot.command(name="ping", description="Responds with pong") def ping_command(ctx): # ctx is a context object containing message info # ctx.sender: Sender's LXMF hash # ctx.content: Full message content # ctx.args: List of arguments after the command # ctx.reply(message): Function to send a reply # (can also take keyword arguments like title="My Title", lxmf_fields=some_fields) ctx.reply("Pong!") # For long-running tasks, you can use threaded commands: # import time # @bot.command(name="long_op", description="Performs a long operation in a separate thread", threaded=True) # def long_op_command(ctx): # ctx.reply("Starting long operation...") # time.sleep(10) # Simulate a long-running operation # ctx.reply("Long operation complete!") # Important: Threaded commands should not directly interact with RNS or lxmfy.transport.py. @bot.command(name="greet", description="Greets the user") def greet_command(ctx): if ctx.args: name = " ".join(ctx.args) ctx.reply(f"Hello, {name}!") else: ctx.reply("Hello there! Tell me your name: !greet ") # 3. Run the bot if __name__ == "__main__": print(f"Starting bot: {bot.config.name}") print(f"Bot LXMF Address: {bot.local.hash}") bot.run() Using Templates --------------- LXMFy provides several templates for common bot types. You can use the CLI to generate a bot file based on a template. .. code-block:: bash # Create an echo bot lxmfy create --template echo my_echo_bot # Create a reminder bot (uses SQLite storage) lxmfy create --template reminder my_reminder_bot # Create a note-taking bot (uses JSON storage) lxmfy create --template note my_note_bot # Create a meme bot (fetches memes from an API) lxmfy create --template meme my_meme_bot Running these commands creates a Python file (e.g., :code:`my_echo_bot.py`) that imports and runs the chosen template. You can then modify the generated file or the template code itself (:code:`lxmfy/templates/...`). **Example generated file (:code:`my_meme_bot.py`):** .. code-block:: python from lxmfy.templates import MemeBot if __name__ == "__main__": bot = MemeBot() # Creates an instance of the MemeBot template # You can optionally override the default name: # bot.bot.name = "My Awesome Meme Bot" bot.run() Bot Configuration ----------------- When creating an :code:`LXMFBot` instance, you can pass various keyword arguments to configure its behavior. See the :code:`BotConfig` section in the `API Reference `_ or the `Quick Start Guide `_ for a list of common options. .. code-block:: python from lxmfy import LXMFBot bot = LXMFBot( name="ConfiguredBot", announce=3600, # Announce every hour admins={"your_admin_hash_here"}, # Set admin user(s) command_prefix="$", # Use '$' as prefix storage_type="sqlite", # Use SQLite database storage_path="data/my_bot_data.db", # Specify DB file path rate_limit=10, # Allow 10 messages / minute cooldown=30, # Cooldown of 30 seconds permissions_enabled=True # Enable role-based permissions ) if __name__ == "__main__": # You can also modify config after instantiation # Note: some settings are best set during init bot.config.max_warnings = 5 bot.spam_protection.config.max_warnings = 5 # Update spam protector too bot.run() Setting a Bot Icon (LXMF Field) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can give your bot a custom icon that appears in compatible LXMF clients. This uses the :code:`LXMF.FIELD_ICON_APPEARANCE` and can be set when sending messages. First, ensure you have the necessary imports: .. code-block:: python from lxmfy import IconAppearance, pack_icon_appearance_field Then, you can define and use the icon: .. code-block:: python # In your bot class or setup icon_data = IconAppearance( icon_name="robot_2", # Choose from Material Symbols fg_color=b'\x00\xFF\x00', # Green bg_color=b'\x33\x33\x33' # Dark Grey ) self.bot_icon_field = pack_icon_appearance_field(icon_data) # When sending a message or replying: ctx.reply("Message from your bot!", lxmf_fields=self.bot_icon_field) # or # bot.send(destination, "Another message", lxmf_fields=self.bot_icon_field) This :code:`self.bot_icon_field` can be pre-calculated and reused for all messages sent by the bot. Using Cogs (Extensions) ----------------------- Cogs allow you to organize your commands and event listeners into separate files (modules), keeping your main bot file cleaner. 1. **Create a :code:`cogs` directory** (or whatever you set :code:`cogs_dir` to in :code:`BotConfig`). 2. **Create Python files** inside the :code:`cogs` directory (e.g., :code:`utility.py`). 3. **Define a class** that inherits from :code:`lxmfy.Cog` (optional but good practice) or is just a standard class. 4. **Define commands** as methods within the class using the :code:`@Command` decorator. 5. **Create a :code:`setup(bot)` function** in the cog file, which LXMFy will call to register the cog. **Example (:code:`cogs/utility.py`):** .. code-block:: python from lxmfy import Command, Cog # Import Cog if inheriting import time class UtilityCog: # Or class UtilityCog(Cog): def __init__(self, bot): self.bot = bot self.start_time = time.time() @Command(name="uptime", description="Shows bot uptime") # Note: Methods in cogs often take 'self' and 'ctx' def uptime_command(self, ctx): uptime_seconds = time.time() - self.start_time ctx.reply(f"Bot has been running for {uptime_seconds:.2f} seconds.") @Command(name="info", description="Shows bot info") def info_command(self, ctx): info = ( f"Bot Name: {self.bot.config.name}\n" f"Owner(s): {', '.join(self.bot.config.admins) or 'None'}\n" f"Prefix: {self.bot.config.command_prefix}" ) ctx.reply(info) @Command(name="threaded_cog_task", description="Performs a long task in a cog thread", threaded=True) def threaded_cog_task(self, ctx): ctx.reply("Starting a long cog task... this will run in a separate thread.") time.sleep(7) # Simulate a long-running operation ctx.reply("Long cog task completed!") # This function is required for the cog to be loaded def setup(bot): cog_instance = UtilityCog(bot) bot.add_cog(cog_instance) # Register the cog instance with the bot **Main Bot File (:code:`my_bot.py`):** .. code-block:: python from lxmfy import LXMFBot bot = LXMFBot( name="CogBot", cogs_enabled=True, # Make sure cogs are enabled (default) cogs_dir="cogs" # Point to the directory ) if __name__ == "__main__": # Cogs are loaded automatically during LXMFBot initialization # if cogs_enabled is True. bot.run() When the bot starts, it will automatically find :code:`utility.py`, call its :code:`setup` function, which creates an instance of :code:`UtilityCog` and registers it using :code:`bot.add_cog()`. The commands defined in the cog (:code:`uptime`, :code:`info`) will then be available. Handling Events --------------- You can register handlers for various bot events using the :code:`@bot.events.on()` decorator. .. code-block:: python from lxmfy import LXMFBot from lxmfy.events import EventPriority # Optional for priority bot = LXMFBot(name="EventBot") @bot.events.on("message_received") def log_message(event): # Event object contains details sender = event.data.get("sender") message_content = event.data.get("message").content.decode('utf-8', errors='ignore') print(f"Received message from {sender}: {message_content}") # You can cancel event processing (e.g., stop message handling) # if sender == "some_blocked_hash": # event.cancel() @bot.events.on("command_executed", priority=EventPriority.LOW) def log_command(event): # Example: event.data might contain {'command_name': 'ping', 'sender': '...', ...} command_name = event.data.get('command_name', 'unknown') sender = event.data.get('sender', 'unknown') print(f"Command '{command_name}' executed by {sender}") # You can define custom events too @bot.command(name="special") def special_command(ctx): ctx.reply("Doing something special!") # Dispatch a custom event bot.events.dispatch(Event("special_action_taken", data={"user": ctx.sender})) @bot.events.on("special_action_taken") def handle_special(event): user = event.data.get("user") print(f"Special action was taken by user: {user}") if __name__ == "__main__": bot.run() See :code:`lxmfy/events.py` for more details on the :code:`Event` structure and priorities. Storage ------- LXMFy provides JSON and SQLite storage backends. * **JSON:** Simple, human-readable. Good for small datasets. Configure with :code:`storage_type="json"` and :code:`storage_path="your_data_dir"`. * **SQLite:** More efficient for larger datasets or frequent writes. Configure with :code:`storage_type="sqlite"` and :code:`storage_path="your_db_file.db"`. You can access the storage interface via :code:`bot.storage`: .. code-block:: python # Save data bot.storage.set("user_prefs:" + ctx.sender, {"theme": "dark"}) # Get data (with a default value) prefs = bot.storage.get("user_prefs:" + ctx.sender, {}) theme = prefs.get("theme", "light") # Check if data exists if bot.storage.exists("some_key"): print("Key exists!") # Delete data bot.storage.delete("old_data_key") # Scan for keys with a prefix (useful for listing user data) user_keys = bot.storage.scan("user_prefs:") for key in user_keys: user_data = bot.storage.get(key) print(f"Data for {key}: {user_data}") See :code:`lxmfy/storage.py` and the API reference for more details. Permissions ----------- LXMFy includes an optional role-based permission system. Enable it with :code:`permissions_enabled=True` during :code:`LXMFBot` initialization. * **Roles:** Define roles with specific permissions (e.g., :code:`DefaultPerms.MANAGE_USERS`). * **Permissions:** Granular flags defined in :code:`DefaultPerms` (e.g., :code:`USE_COMMANDS`, :code:`BYPASS_SPAM`). * **Assignment:** Assign roles to user hashes. See :code:`lxmfy/permissions.py`, the API reference, and potentially example cogs (if any are created) for usage details. Signature Verification ---------------------- LXMFy supports cryptographic message signing and verification for enhanced security. When enabled, the bot will verify that incoming messages are signed with valid RNS identities. **Configuration:** Enable signature verification in your bot configuration: .. code-block:: python bot = LXMFBot( name="SecureBot", signature_verification_enabled=True, # Enable signature verification require_message_signatures=False # Set to True to reject unsigned messages ) **How It Works:** 1. **Outgoing Messages:** When signature verification is enabled, all outgoing messages are automatically signed using the bot's RNS identity. 2. **Incoming Messages:** The bot verifies signatures on incoming messages using the sender's RNS identity. 3. **Unsigned Messages:** If :code:`require_message_signatures=True`, unsigned messages are rejected. Otherwise, they're accepted but logged. 4. **Permission Integration:** Users with :code:`BYPASS_SPAM` permission can bypass signature verification. **CLI Management:** You can manage signature verification settings using the CLI: .. code-block:: bash # Test signature verification lxmfy signatures test # Enable signature verification lxmfy signatures enable # Disable signature verification lxmfy signatures disable **Signature Fields:** Signatures are stored in LXMF field :code:`0xFA`. The message canonicalization process ensures consistent signing by ordering message components deterministically.