config.py fixed for admin, updated .gitignore
This commit is contained in:
+134
-78
@@ -1,10 +1,13 @@
|
||||
"""
|
||||
Custom configuration class for the Funguy bot.
|
||||
Security‑hardened: only the configured admin user can read or change settings.
|
||||
Save operation preserves extra sections (plugins.disabled, etc.).
|
||||
"""
|
||||
# plugins/config.py
|
||||
|
||||
import os
|
||||
import logging
|
||||
import toml
|
||||
import simplematrixbotlib as botlib
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -23,13 +26,14 @@ class FunguyConfig(botlib.Config):
|
||||
|
||||
# Load configuration from file
|
||||
self.load_toml(config_file)
|
||||
# Store the actual file path used (so save_config can find it)
|
||||
self._config_file = config_file
|
||||
logging.info(f"Loaded configuration from {config_file}")
|
||||
|
||||
_admin_user: str = ""
|
||||
_prefix: str = ""
|
||||
_config_file: str= ""
|
||||
|
||||
# Define getters and setters for custom configuration options
|
||||
@property
|
||||
def admin_user(self):
|
||||
return self._admin_user
|
||||
@@ -54,123 +58,175 @@ class FunguyConfig(botlib.Config):
|
||||
def config_file(self, value):
|
||||
self._config_file = value
|
||||
|
||||
|
||||
# Method to load configuration from file
|
||||
def load_config(self, config_file):
|
||||
"""
|
||||
Load configuration options from a TOML file.
|
||||
|
||||
Args:
|
||||
config_file (str): Path to the configuration file.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
"""Load configuration from a TOML file."""
|
||||
self.load_toml(config_file)
|
||||
self._config_file = config_file
|
||||
logging.info(f"Loaded configuration from {config_file}")
|
||||
|
||||
|
||||
# Method to save configuration to file
|
||||
def save_config(self, config_file):
|
||||
def save_config(self, config_file=None):
|
||||
"""
|
||||
Save configuration options to a TOML file.
|
||||
Save configuration to a TOML file, **preserving** any extra sections
|
||||
(e.g., plugins.disabled) not managed by the base library.
|
||||
|
||||
Args:
|
||||
config_file (str): Path to the configuration file.
|
||||
|
||||
Returns:
|
||||
None
|
||||
If config_file is not provided, the instance's stored config_file is used.
|
||||
"""
|
||||
self.save_toml(config_file)
|
||||
logging.info(f"Saved configuration to {config_file}")
|
||||
if config_file is None:
|
||||
config_file = self._config_file
|
||||
if not config_file:
|
||||
raise ValueError("No config file path set for saving.")
|
||||
|
||||
# 1. Let the library write its portion to a temporary file
|
||||
tmp_file = config_file + ".tmp"
|
||||
try:
|
||||
self.save_toml(tmp_file)
|
||||
|
||||
# 2. Read the temporary file (library's view of the config)
|
||||
with open(tmp_file, 'r') as f:
|
||||
new_config = toml.load(f)
|
||||
|
||||
# 3. Read the current config file (if it exists) to preserve extra sections
|
||||
original = {}
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
original = toml.load(f)
|
||||
|
||||
# 4. Merge: keep everything from the original, then overlay
|
||||
# the library's config table(s). This leaves any top-level
|
||||
# sections not produced by the library exactly as they were.
|
||||
merged = original.copy()
|
||||
for key, value in new_config.items():
|
||||
merged[key] = value
|
||||
|
||||
# 5. Write back the merged result
|
||||
with open(config_file, 'w') as f:
|
||||
toml.dump(merged, f)
|
||||
|
||||
logging.info(f"Configuration saved to {config_file} (extra sections preserved)")
|
||||
except Exception as e:
|
||||
logging.error(f"Error saving config to {config_file}: {e}")
|
||||
raise
|
||||
finally:
|
||||
# Always remove the temp file
|
||||
if os.path.exists(tmp_file):
|
||||
os.remove(tmp_file)
|
||||
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Function to handle commands related to bot configuration.
|
||||
|
||||
Args:
|
||||
room (Room): The Matrix room where the command was invoked.
|
||||
message (RoomMessage): The message object containing the command.
|
||||
bot (Bot): The bot instance.
|
||||
PREFIX (str): The bot command prefix.
|
||||
config (FunguyConfig): The bot configuration instance.
|
||||
|
||||
Returns:
|
||||
None
|
||||
Handle commands related to bot configuration.
|
||||
All sub‑commands require the sender to be the configured admin_user.
|
||||
"""
|
||||
# Check if the message matches the command pattern and is not from this bot
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.is_not_from_this_bot() and match.prefix() and match.command("set"):
|
||||
# If the command is 'set', check if it has exactly two arguments
|
||||
args = match.args()
|
||||
|
||||
if not match.is_not_from_this_bot() or not match.prefix():
|
||||
return
|
||||
|
||||
cmd = match.command()
|
||||
if cmd not in ("set", "get", "saveconf", "loadconf", "show", "reset"):
|
||||
return
|
||||
|
||||
sender = str(message.sender)
|
||||
if sender != config.admin_user:
|
||||
logging.warning(
|
||||
"Unauthorized config command attempt by %s in room %s: %s",
|
||||
sender, room.room_id, cmd
|
||||
)
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"⛔ You are not authorized to use configuration commands."
|
||||
)
|
||||
return
|
||||
|
||||
args = match.args()
|
||||
|
||||
if cmd == "set":
|
||||
if len(args) != 2:
|
||||
await bot.api.send_text_message(room.room_id, "Usage: !set <config_option> <value>")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
"Usage: !set <config_option> <value>")
|
||||
return
|
||||
option, value = args
|
||||
# Set the specified configuration option based on the provided value
|
||||
if option == "admin_user":
|
||||
config.admin_user = value
|
||||
await bot.api.send_text_message(room.room_id, f"Admin user set to {value}")
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"❌ Changing 'admin_user' via !set is not allowed for security reasons."
|
||||
)
|
||||
return
|
||||
elif option == "prefix":
|
||||
config.prefix = value
|
||||
await bot.api.send_text_message(room.room_id, f"Prefix set to {value}")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
f"Prefix set to `{value}`")
|
||||
else:
|
||||
await bot.api.send_text_message(room.room_id, "Invalid configuration option")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
"Invalid configuration option.")
|
||||
|
||||
# If the command is 'get', retrieve the value of the specified configuration option
|
||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("get"):
|
||||
args = match.args()
|
||||
elif cmd == "get":
|
||||
if len(args) != 1:
|
||||
await bot.api.send_text_message(room.room_id, "Usage: !get <config_option>")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
"Usage: !get <config_option>")
|
||||
return
|
||||
option = args[0]
|
||||
if option == "admin_user":
|
||||
await bot.api.send_text_message(room.room_id, f"Admin user: {config.admin_user}")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
f"Admin user: {config.admin_user}")
|
||||
elif option == "prefix":
|
||||
await bot.api.send_text_message(room.room_id, f"Prefix: {config.prefix}")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
f"Prefix: {config.prefix}")
|
||||
else:
|
||||
await bot.api.send_text_message(room.room_id, "Invalid configuration option")
|
||||
await bot.api.send_text_message(room.room_id,
|
||||
"Invalid configuration option.")
|
||||
|
||||
# If the command is 'saveconf', save the current configuration
|
||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("saveconf"):
|
||||
config.save_config(config.config_file)
|
||||
await bot.api.send_text_message(room.room_id, "Configuration saved")
|
||||
elif cmd == "show":
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
f"Admin user: {config.admin_user}\nPrefix: {config.prefix}"
|
||||
)
|
||||
|
||||
# If the command is 'loadconf', load the saved configuration
|
||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("loadconf"):
|
||||
elif cmd == "saveconf":
|
||||
try:
|
||||
config.save_config() # uses the stored config_file by default
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"Configuration saved (including disabled plugins)."
|
||||
)
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
f"❌ Failed to save configuration: {e}"
|
||||
)
|
||||
|
||||
elif cmd == "loadconf":
|
||||
config.load_config(config.config_file)
|
||||
await bot.api.send_text_message(room.room_id, "Configuration loaded")
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"Configuration reloaded from file."
|
||||
)
|
||||
|
||||
# If the command is 'show', display the current configuration
|
||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("show"):
|
||||
admin_user = config.admin_user
|
||||
prefix = config.prefix
|
||||
await bot.api.send_text_message(room.room_id, f"Admin user: {admin_user}, Prefix: {prefix}")
|
||||
|
||||
# If the command is 'reset', reset the configuration to default values
|
||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("reset"):
|
||||
config.admin_user = ""
|
||||
elif cmd == "reset":
|
||||
config.prefix = "!"
|
||||
await bot.api.send_text_message(room.room_id, "Configuration reset")
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"Configuration reset to defaults (admin_user unchanged)."
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plugin Metadata
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "Funguy Bot"
|
||||
__description__ = "Admin configuration commands"
|
||||
__version__ = "1.0.2"
|
||||
__author__ = "Funguy Bot (hardened)"
|
||||
__description__ = "Admin-only configuration commands (preserves disabled plugins)"
|
||||
__help__ = """
|
||||
<details>
|
||||
<summary><strong>Admin Config</strong> (!set, !get, !saveconf, …)</summary>
|
||||
<ul>
|
||||
<li><code>!set <option> <value></code> – Set admin_user/prefix</li>
|
||||
<li><code>!get <option></code> – Display config value</li>
|
||||
<li><code>!show</code> – Show current settings</li>
|
||||
<li><code>!saveconf</code> / <code>!loadconf</code> – Save/load config</li>
|
||||
<li><code>!rehash</code> – Reload configuration</li>
|
||||
<li><code>!set prefix <value></code> – Change command prefix (admin only)</li>
|
||||
<li><code>!get <option></code> – Display config value (admin only)</li>
|
||||
<li><code>!show</code> – Show current settings (admin only)</li>
|
||||
<li><code>!saveconf</code> / <code>!loadconf</code> – Save/load config (admin only)</li>
|
||||
<li><code>!reset</code> – Reset to defaults, preserving admin_user (admin only)</li>
|
||||
</ul>
|
||||
<p>Admin only.</p>
|
||||
<p>Changing <code>admin_user</code> via bot commands is blocked for safety.</p>
|
||||
<p>The <code>plugins.disabled</code> section is now preserved when saving.</p>
|
||||
</details>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user