config.py fixed for admin, updated .gitignore
This commit is contained in:
+3
-6
@@ -1,6 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
karma.db
|
|
||||||
proxies.db
|
|
||||||
session.txt
|
session.txt
|
||||||
socks5.txt
|
socks5.txt
|
||||||
venv/
|
venv/
|
||||||
@@ -9,8 +7,7 @@ simplematrixbotlib*/
|
|||||||
chromedriver
|
chromedriver
|
||||||
store
|
store
|
||||||
funguybot.service
|
funguybot.service
|
||||||
stats.db
|
|
||||||
cron.db
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
lastfm.db
|
funguy.conf
|
||||||
venv.bak/
|
*.db
|
||||||
|
plugins/disabled/
|
||||||
|
|||||||
+134
-78
@@ -1,10 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Custom configuration class for the Funguy bot.
|
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
|
# plugins/config.py
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import toml
|
||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@@ -23,13 +26,14 @@ class FunguyConfig(botlib.Config):
|
|||||||
|
|
||||||
# Load configuration from file
|
# Load configuration from file
|
||||||
self.load_toml(config_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}")
|
logging.info(f"Loaded configuration from {config_file}")
|
||||||
|
|
||||||
_admin_user: str = ""
|
_admin_user: str = ""
|
||||||
_prefix: str = ""
|
_prefix: str = ""
|
||||||
_config_file: str= ""
|
_config_file: str= ""
|
||||||
|
|
||||||
# Define getters and setters for custom configuration options
|
|
||||||
@property
|
@property
|
||||||
def admin_user(self):
|
def admin_user(self):
|
||||||
return self._admin_user
|
return self._admin_user
|
||||||
@@ -54,123 +58,175 @@ class FunguyConfig(botlib.Config):
|
|||||||
def config_file(self, value):
|
def config_file(self, value):
|
||||||
self._config_file = value
|
self._config_file = value
|
||||||
|
|
||||||
|
|
||||||
# Method to load configuration from file
|
|
||||||
def load_config(self, config_file):
|
def load_config(self, config_file):
|
||||||
"""
|
"""Load configuration from a TOML file."""
|
||||||
Load configuration options from a TOML file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config_file (str): Path to the configuration file.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
self.load_toml(config_file)
|
self.load_toml(config_file)
|
||||||
|
self._config_file = config_file
|
||||||
logging.info(f"Loaded configuration from {config_file}")
|
logging.info(f"Loaded configuration from {config_file}")
|
||||||
|
|
||||||
|
def save_config(self, config_file=None):
|
||||||
# Method to save configuration to file
|
|
||||||
def save_config(self, config_file):
|
|
||||||
"""
|
"""
|
||||||
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:
|
If config_file is not provided, the instance's stored config_file is used.
|
||||||
config_file (str): Path to the configuration file.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
"""
|
||||||
self.save_toml(config_file)
|
if config_file is None:
|
||||||
logging.info(f"Saved configuration to {config_file}")
|
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):
|
async def handle_command(room, message, bot, prefix, config):
|
||||||
"""
|
"""
|
||||||
Function to handle commands related to bot configuration.
|
Handle commands related to bot configuration.
|
||||||
|
All sub‑commands require the sender to be the configured admin_user.
|
||||||
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
|
|
||||||
"""
|
"""
|
||||||
# Check if the message matches the command pattern and is not from this bot
|
|
||||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
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
|
if not match.is_not_from_this_bot() or not match.prefix():
|
||||||
args = match.args()
|
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:
|
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
|
return
|
||||||
option, value = args
|
option, value = args
|
||||||
# Set the specified configuration option based on the provided value
|
|
||||||
if option == "admin_user":
|
if option == "admin_user":
|
||||||
config.admin_user = value
|
await bot.api.send_text_message(
|
||||||
await bot.api.send_text_message(room.room_id, f"Admin user set to {value}")
|
room.room_id,
|
||||||
|
"❌ Changing 'admin_user' via !set is not allowed for security reasons."
|
||||||
|
)
|
||||||
|
return
|
||||||
elif option == "prefix":
|
elif option == "prefix":
|
||||||
config.prefix = value
|
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:
|
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 cmd == "get":
|
||||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("get"):
|
|
||||||
args = match.args()
|
|
||||||
if len(args) != 1:
|
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
|
return
|
||||||
option = args[0]
|
option = args[0]
|
||||||
if option == "admin_user":
|
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":
|
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:
|
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 cmd == "show":
|
||||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("saveconf"):
|
await bot.api.send_text_message(
|
||||||
config.save_config(config.config_file)
|
room.room_id,
|
||||||
await bot.api.send_text_message(room.room_id, "Configuration saved")
|
f"Admin user: {config.admin_user}\nPrefix: {config.prefix}"
|
||||||
|
)
|
||||||
|
|
||||||
# If the command is 'loadconf', load the saved configuration
|
elif cmd == "saveconf":
|
||||||
elif match.is_not_from_this_bot() and match.prefix() and match.command("loadconf"):
|
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)
|
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 cmd == "reset":
|
||||||
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 = ""
|
|
||||||
config.prefix = "!"
|
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
|
# Plugin Metadata
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
__version__ = "1.0.2"
|
||||||
__version__ = "1.0.0"
|
__author__ = "Funguy Bot (hardened)"
|
||||||
__author__ = "Funguy Bot"
|
__description__ = "Admin-only configuration commands (preserves disabled plugins)"
|
||||||
__description__ = "Admin configuration commands"
|
|
||||||
__help__ = """
|
__help__ = """
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>Admin Config</strong> (!set, !get, !saveconf, …)</summary>
|
<summary><strong>Admin Config</strong> (!set, !get, !saveconf, …)</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>!set <option> <value></code> – Set admin_user/prefix</li>
|
<li><code>!set prefix <value></code> – Change command prefix (admin only)</li>
|
||||||
<li><code>!get <option></code> – Display config value</li>
|
<li><code>!get <option></code> – Display config value (admin only)</li>
|
||||||
<li><code>!show</code> – Show current settings</li>
|
<li><code>!show</code> – Show current settings (admin only)</li>
|
||||||
<li><code>!saveconf</code> / <code>!loadconf</code> – Save/load config</li>
|
<li><code>!saveconf</code> / <code>!loadconf</code> – Save/load config (admin only)</li>
|
||||||
<li><code>!rehash</code> – Reload configuration</li>
|
<li><code>!reset</code> – Reset to defaults, preserving admin_user (admin only)</li>
|
||||||
</ul>
|
</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>
|
</details>
|
||||||
"""
|
"""
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user