Skip to content

Commit

Permalink
add event system
Browse files Browse the repository at this point in the history
  • Loading branch information
Sudo-Ivan committed Jan 20, 2025
1 parent a9f220d commit 45855fb
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 15 deletions.
4 changes: 4 additions & 0 deletions lxmfy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .permissions import DefaultPerms, Role, PermissionManager
from .validation import validate_bot, format_validation_results
from .config import BotConfig
from .events import Event, EventManager, EventPriority

__all__ = [
"LXMFBot",
Expand All @@ -30,4 +31,7 @@
"validate_bot",
"format_validation_results",
"BotConfig",
"Event",
"EventManager",
"EventPriority",
]
12 changes: 11 additions & 1 deletion lxmfy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,14 @@ class BotConfig:
permissions_enabled: bool = False
storage_type: str = "json"
storage_path: str = "data"
first_message_enabled: bool = True
first_message_enabled: bool = True
event_logging_enabled: bool = True
max_logged_events: int = 1000
event_middleware_enabled: bool = True

def __post_init__(self):
if self.admins is None:
self.admins = set()

def __str__(self):
return f"BotConfig(name={self.name}, announce={self.announce}, announce_immediately={self.announce_immediately}, admins={self.admins}, hot_reloading={self.hot_reloading}, rate_limit={self.rate_limit}, cooldown={self.cooldown}, max_warnings={self.max_warnings}, warning_timeout={self.warning_timeout}, command_prefix={self.command_prefix}, cogs_dir={self.cogs_dir}, permissions_enabled={self.permissions_enabled}, storage_type={self.storage_type}, storage_path={self.storage_path}, first_message_enabled={self.first_message_enabled}, event_logging_enabled={self.event_logging_enabled}, max_logged_events={self.max_logged_events}, event_middleware_enabled={self.event_middleware_enabled})"
65 changes: 52 additions & 13 deletions lxmfy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from queue import Queue
from types import SimpleNamespace
from typing import Optional, Dict
import asyncio

# Reticulum and LXMF imports
import RNS
Expand All @@ -30,6 +31,7 @@
from .permissions import PermissionManager, DefaultPerms
from .config import BotConfig
from .validation import validate_bot, format_validation_results
from .events import EventManager, Event, EventPriority


class LXMFBot:
Expand Down Expand Up @@ -148,6 +150,12 @@ def __init__(self, **kwargs):
self.first_message_handlers = []
self.first_message_enabled = kwargs.get("first_message_enabled", True)

# Initialize event system
self.events = EventManager(self.storage)

# Register built-in events
self._register_builtin_events()

def command(self, *args, **kwargs):
def decorator(func):
if len(args) > 0:
Expand Down Expand Up @@ -187,13 +195,42 @@ def add_cog(self, cog):
def is_admin(self, sender):
return sender in self.admins

def _register_builtin_events(self):
"""Register built-in event handlers"""
@self.events.on("message_received", EventPriority.HIGHEST)
def handle_message(event):
message = event.data["message"]
sender = event.data["sender"]

# Check spam protection
if not self.permissions.has_permission(sender, DefaultPerms.BYPASS_SPAM):
allowed, msg = self.spam_protection.check_spam(sender)
if not allowed:
event.cancel()
self.send(sender, msg)
return

# Process message
self._process_message(message, sender)

def _message_received(self, message):
"""Handle received messages"""
sender = RNS.hexrep(message.source_hash, delimit=False)
receipt = RNS.hexrep(message.hash, delimit=False)

if receipt in self.receipts:
return


event = Event("message_received", {
"message": message,
"sender": sender,
"receipt": receipt
})

self.events.dispatch(event)
if event.cancelled:
return

# Check if this is user's first message
is_first_message = not self.storage.exists(f"user:{sender}")
if is_first_message:
Expand Down Expand Up @@ -339,17 +376,19 @@ def send(self, destination, message, title="Reply"):
self.queue.put(lxm)

def run(self, delay=10):
RNS.log(
f"LXMF Bot `{self.local.display_name}` reporting for duty and awaiting messages...",
RNS.LOG_INFO,
)

while True:
for i in list(self.queue.queue):
lxm = self.queue.get()
self.router.handle_outbound(lxm)
self._announce()
time.sleep(delay)
"""Run the bot"""
try:
while True:
# Process outbound queue
for i in list(self.queue.queue):
lxm = self.queue.get()
self.router.handle_outbound(lxm)

self._announce()
time.sleep(delay)

except KeyboardInterrupt:
self.transport.cleanup()

def received(self, function):
self.delivery_callbacks.append(function)
Expand Down
125 changes: 125 additions & 0 deletions lxmfy/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Event system module for LXMFy.
This module provides a comprehensive event handling system including:
- Custom event creation and dispatching
- Event middleware support
- Event logging and monitoring
"""

from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List
from enum import Enum
import logging
from datetime import datetime

logger = logging.getLogger(__name__)

class EventPriority(Enum):
"""Priority levels for event handlers"""
LOWEST = 0
LOW = 1
NORMAL = 2
HIGH = 3
HIGHEST = 4
MONITOR = 5

@dataclass
class Event:
"""Base event class"""
name: str
data: Dict[str, Any] = field(default_factory=dict)
cancelled: bool = False
timestamp: datetime = field(default_factory=datetime.now)

def cancel(self):
"""Cancel the event"""
self.cancelled = True

@dataclass
class EventHandler:
"""Event handler container"""
callback: Callable
priority: EventPriority = EventPriority.NORMAL
middleware: List[Callable] = field(default_factory=list)

class EventManager:
"""Manages event registration, dispatching and middleware"""

def __init__(self, storage=None):
self.handlers: Dict[str, List[EventHandler]] = {}
self.middleware: List[Callable] = []
self.storage = storage
self.logger = logging.getLogger(__name__)

def on(self, event_name: str, priority: EventPriority = EventPriority.NORMAL):
"""Decorator to register an event handler"""
def decorator(func):
self.register_handler(event_name, func, priority)
return func
return decorator

def register_handler(self, event_name: str, callback: Callable,
priority: EventPriority = EventPriority.NORMAL):
"""Register an event handler"""
if event_name not in self.handlers:
self.handlers[event_name] = []

handler = EventHandler(callback=callback, priority=priority)
self.handlers[event_name].append(handler)

# Sort handlers by priority
self.handlers[event_name].sort(key=lambda h: h.priority.value, reverse=True)

def use(self, middleware: Callable):
"""Add middleware to the event pipeline"""
self.middleware.append(middleware)

def dispatch(self, event: Event) -> Event:
"""Dispatch an event through middleware and to handlers"""
try:
# Run through middleware
for mw in self.middleware:
event = mw(event)
if event.cancelled:
return event

if event.name in self.handlers:
for handler in self.handlers[event.name]:
try:
# Run handler middleware
for mw in handler.middleware:
event = mw(event)
if event.cancelled:
return event

# Execute handler
handler.callback(event)
if event.cancelled:
break

except Exception as e:
self.logger.error(f"Error in event handler: {str(e)}")

# Log event if storage is configured
if self.storage:
self._log_event(event)

return event

except Exception as e:
self.logger.error(f"Error dispatching event: {str(e)}")
raise

def _log_event(self, event: Event):
"""Log event to storage"""
try:
events = self.storage.get("events:log", [])
events.append({
"name": event.name,
"timestamp": event.timestamp.isoformat(),
"cancelled": event.cancelled,
"data": event.data
})
self.storage.set("events:log", events[-1000:]) # Keep last 1000 events
except Exception as e:
self.logger.error(f"Error logging event: {str(e)}")
8 changes: 7 additions & 1 deletion lxmfy/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ class DefaultPerms(Flag):
BYPASS_SPAM = auto()
VIEW_ADMIN_COMMANDS = auto()

# Event system permissions
VIEW_EVENTS = auto()
MANAGE_EVENTS = auto()
BYPASS_EVENT_CHECKS = auto()

# Combined permissions
ALL = (USE_BOT | SEND_MESSAGES | USE_COMMANDS |
MANAGE_MESSAGES | MANAGE_COMMANDS | MANAGE_USERS |
BYPASS_RATELIMIT | BYPASS_SPAM | VIEW_ADMIN_COMMANDS)
BYPASS_RATELIMIT | BYPASS_SPAM | VIEW_ADMIN_COMMANDS |
VIEW_EVENTS | MANAGE_EVENTS | BYPASS_EVENT_CHECKS)


@dataclass
Expand Down

0 comments on commit 45855fb

Please sign in to comment.