various plugin refactors and fixes
This commit is contained in:
@@ -20,7 +20,7 @@ from plugins.config import FunguyConfig
|
||||
|
||||
# Rate limiter settings
|
||||
RATE_LIMIT_WINDOW = 5.0 # seconds
|
||||
MAX_COMMANDS_PER_WINDOW = 5
|
||||
MAX_COMMANDS_PER_WINDOW = 3
|
||||
|
||||
|
||||
class FunguyBot:
|
||||
@@ -118,22 +118,78 @@ class FunguyBot:
|
||||
toml.dump(existing_config, f)
|
||||
|
||||
def _check_rate_limit(self, sender: str) -> bool:
|
||||
"""Return True if the sender is allowed to proceed, False if rate limited."""
|
||||
"""Return True if the sender is allowed to proceed.
|
||||
Admin is always allowed."""
|
||||
# Admin bypass
|
||||
if sender == self.config.admin_user:
|
||||
return True
|
||||
|
||||
now = time.monotonic()
|
||||
bucket = self._rate_limit_buckets[sender]
|
||||
# Prune old entries
|
||||
bucket = [t for t in bucket if now - t < RATE_LIMIT_WINDOW]
|
||||
self._rate_limit_buckets[sender] = bucket
|
||||
|
||||
if len(bucket) >= MAX_COMMANDS_PER_WINDOW:
|
||||
logging.debug("Rate limit hit for %s", sender)
|
||||
return False
|
||||
|
||||
bucket.append(now)
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# New: load/unload a single plugin at runtime
|
||||
# ------------------------------------------------------------------
|
||||
async def load_plugin(self, plugin_name: str) -> bool:
|
||||
"""Dynamically load a plugin module, add to PLUGINS, and call its setup()."""
|
||||
if plugin_name in self.PLUGINS:
|
||||
logging.info(f"Plugin '{plugin_name}' is already loaded.")
|
||||
return False
|
||||
try:
|
||||
module = importlib.import_module(f"{self.PLUGINS_DIR}.{plugin_name}")
|
||||
self.PLUGINS[plugin_name] = module
|
||||
logging.info(f"Loaded plugin: {plugin_name}")
|
||||
# Call setup if the bot is already running
|
||||
if self.bot is not None and hasattr(module, "setup") and callable(module.setup):
|
||||
module.setup(self.bot)
|
||||
logging.info(f"Setup called for newly loaded plugin: {plugin_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading plugin {plugin_name}: {e}")
|
||||
return False
|
||||
|
||||
async def unload_plugin(self, plugin_name: str) -> bool:
|
||||
"""Remove a plugin from PLUGINS and unload its module."""
|
||||
if plugin_name not in self.PLUGINS:
|
||||
logging.info(f"Plugin '{plugin_name}' is not loaded.")
|
||||
return False
|
||||
try:
|
||||
del self.PLUGINS[plugin_name]
|
||||
module_path = f"{self.PLUGINS_DIR}.{plugin_name}"
|
||||
if module_path in sys.modules:
|
||||
del sys.modules[module_path]
|
||||
logging.info(f"Unloaded plugin: {plugin_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error unloading plugin {plugin_name}: {e}")
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# New: restart the bot process
|
||||
# ------------------------------------------------------------------
|
||||
async def restart_bot(self, room_id):
|
||||
await self.bot.api.send_text_message(room_id, "🔄 Restarting bot...")
|
||||
await asyncio.sleep(1)
|
||||
logging.info("Restart command received – exiting.")
|
||||
sys.exit(0)
|
||||
|
||||
async def handle_commands(self, room, message):
|
||||
match = botlib.MessageMatch(room, message, self.bot, self.config.prefix)
|
||||
|
||||
# Rate limit check (applies to all commands)
|
||||
sender = str(message.sender)
|
||||
is_admin = (sender == self.config.admin_user)
|
||||
|
||||
# Rate limit check (applies to all non‑admin commands)
|
||||
if not self._check_rate_limit(sender):
|
||||
await self.bot.api.send_text_message(
|
||||
room.room_id,
|
||||
@@ -143,45 +199,81 @@ class FunguyBot:
|
||||
|
||||
# Admin commands
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("reload"):
|
||||
if sender == self.config.admin_user:
|
||||
if is_admin:
|
||||
self.reload_plugins()
|
||||
await self.bot.api.send_text_message(room.room_id, "Plugins reloaded successfully")
|
||||
await self.bot.api.send_text_message(room.room_id, "All plugins reloaded successfully.")
|
||||
else:
|
||||
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.")
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("load"):
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "⛔ Admin only.")
|
||||
return
|
||||
args = match.args()
|
||||
if len(args) != 1:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !load <plugin>")
|
||||
return
|
||||
success = await self.load_plugin(args[0])
|
||||
msg = f"✅ Plugin '{args[0]}' loaded." if success else f"❌ Could not load '{args[0]}'. See logs for details."
|
||||
await self.bot.api.send_text_message(room.room_id, msg)
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("unload"):
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "⛔ Admin only.")
|
||||
return
|
||||
args = match.args()
|
||||
if len(args) != 1:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !unload <plugin>")
|
||||
return
|
||||
success = await self.unload_plugin(args[0])
|
||||
msg = f"✅ Plugin '{args[0]}' unloaded." if success else f"❌ Could not unload '{args[0]}'. See logs for details."
|
||||
await self.bot.api.send_text_message(room.room_id, msg)
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("disable"):
|
||||
if sender == self.config.admin_user:
|
||||
args = match.args()
|
||||
if len(args) != 2:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !disable <plugin> <room_id>")
|
||||
else:
|
||||
plugin_name, room_id = args
|
||||
await self.disable_plugin(room_id, plugin_name)
|
||||
await self.bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' disabled for room '{room_id}'")
|
||||
else:
|
||||
await self.bot.api.send_text_message(room.room_id, "You are not authorized to disable plugins.")
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "⛔ Admin only.")
|
||||
return
|
||||
args = match.args()
|
||||
if len(args) != 1:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !disable <plugin>")
|
||||
return
|
||||
plugin_name = args[0]
|
||||
room_id = room.room_id
|
||||
await self.disable_plugin(room_id, plugin_name)
|
||||
await self.bot.api.send_text_message(room.room_id, f"🚫 Plugin '{plugin_name}' disabled in this room.")
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("enable"):
|
||||
if sender == self.config.admin_user:
|
||||
args = match.args()
|
||||
if len(args) != 2:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !enable <plugin> <room_id>")
|
||||
else:
|
||||
plugin_name, room_id = args
|
||||
await self.enable_plugin(room_id, plugin_name)
|
||||
await self.bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' enabled for room '{room_id}'")
|
||||
else:
|
||||
await self.bot.api.send_text_message(room.room_id, "You are not authorized to enable plugins.")
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "⛔ Admin only.")
|
||||
return
|
||||
args = match.args()
|
||||
if len(args) != 1:
|
||||
await self.bot.api.send_text_message(room.room_id, "Usage: !enable <plugin>")
|
||||
return
|
||||
plugin_name = args[0]
|
||||
room_id = room.room_id
|
||||
await self.enable_plugin(room_id, plugin_name)
|
||||
await self.bot.api.send_text_message(room.room_id, f"✅ Plugin '{plugin_name}' enabled in this room.")
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("restart"):
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "⛔ Admin only.")
|
||||
return
|
||||
await self.restart_bot(room.room_id)
|
||||
return
|
||||
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("rehash"):
|
||||
if sender == self.config.admin_user:
|
||||
self.rehash_config()
|
||||
await self.bot.api.send_text_message(room.room_id, "Config rehashed")
|
||||
else:
|
||||
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.")
|
||||
if not is_admin:
|
||||
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload config.")
|
||||
return
|
||||
self.load_config()
|
||||
self.load_disabled_plugins()
|
||||
await self.bot.api.send_text_message(room.room_id, "🔄 Configuration rehashed.")
|
||||
return
|
||||
|
||||
# Dispatch to active plugins
|
||||
@@ -192,10 +284,6 @@ class FunguyBot:
|
||||
except Exception as e:
|
||||
logging.error(f"Error in plugin {plugin_name}: {e}", exc_info=True)
|
||||
|
||||
def rehash_config(self):
|
||||
del self.config
|
||||
self.config = FunguyConfig()
|
||||
|
||||
async def disable_plugin(self, room_id, plugin_name):
|
||||
if room_id not in self.disabled_plugins:
|
||||
self.disabled_plugins[room_id] = []
|
||||
|
||||
Reference in New Issue
Block a user