Scripting Best Practices
Write reliable, secure, and maintainable scripts with these proven patterns and recommendations.
Overview
Following best practices ensures your scripts are reliable during streams, easy to maintain, and don't introduce security vulnerabilities. This guide covers the most important patterns to follow.
Quick Checklist
- Always handle errors gracefully
- Validate all user input
- Use appropriate logging
- Cache expensive operations
- Never trust user-provided data
- Test before going live
Error Handling
Proper error handling prevents script failures from disrupting your stream. Always wrap your code in try-catch blocks and validate inputs.
def Execute():
try:
# Always validate required arguments
user = args.get("user")
if not user:
CPH.LogWarn("No user provided")
return False
# Parse numbers safely
amount_str = args.get("amount", "0")
try:
amount = int(amount_str)
except ValueError:
CPH.SendMessage("Please provide a valid number!")
return False
# Main logic
process_user(user, amount)
return True
except Exception as e:
CPH.LogError(f"Script failed: {e}")
return False
def process_user(user, amount):
# Separated logic for clarity
points = CPH.GetUserVar(user, "points", 0)
CPH.SetUserVar(user, "points", points + amount)Do
- Validate required arguments exist
- Parse numbers with error handling
- Log errors for debugging
- Return
Falseon failure - Provide user-friendly error messages
Avoid
- Assuming arguments always exist
- Unhandled exceptions
- Silent failures
- Exposing technical errors to chat
- Continuing after critical errors
Performance
Scripts run during your stream, so performance matters. Avoid blocking operations and cache expensive data.
# BAD: Fetching data in every call
def Execute():
all_users = fetch_all_users() # Expensive!
for user in all_users:
if user["name"] == args["user"]:
return process(user)
return False
# GOOD: Cache expensive operations
_user_cache = {}
_cache_time = 0
def Execute():
global _user_cache, _cache_time
import time
current_time = time.time()
# Refresh cache every 5 minutes
if current_time - _cache_time > 300:
_user_cache = fetch_all_users_as_dict()
_cache_time = current_time
user = args.get("user")
if user in _user_cache:
return process(_user_cache[user])
return FalsePerformance Tips
Cache Data
Cache API responses and expensive calculations. Refresh periodically.
Avoid Loops
Use built-in functions instead of iterating when possible.
String Building
Use join/concat methods instead of repeated concatenation.
Early Returns
Return early from functions when conditions aren't met.
Security
Never trust user input. Chat messages can contain malicious content designed to exploit your scripts.
# BAD: Using user input directly in commands
def Execute():
filename = args["message"]
content = CPH.ReadFile(filename) # Path traversal risk!
CPH.SendMessage(content)
# GOOD: Validate and sanitize input
import os
ALLOWED_DIR = "/path/to/allowed/files"
def Execute():
filename = args.get("message", "")
# Remove path traversal attempts
safe_name = os.path.basename(filename)
# Validate file extension
if not safe_name.endswith(('.txt', '.json')):
CPH.SendMessage("Invalid file type!")
return False
# Build safe path
safe_path = os.path.join(ALLOWED_DIR, safe_name)
# Verify path is still within allowed directory
if not os.path.abspath(safe_path).startswith(ALLOWED_DIR):
CPH.LogWarn("Path traversal attempt blocked")
return False
if os.path.exists(safe_path):
content = CPH.ReadFile(safe_path)
CPH.SendMessage(content[:500]) # Limit output
return TrueSecurity Essentials
- Never execute user input as code - No eval(), loadstring(), etc.
- Sanitize file paths - Prevent path traversal attacks
- Validate URLs - Only allow known domains for HTTP requests
- Limit output - Don't dump large amounts of data to chat
- Check permissions - Verify user roles before sensitive operations
Code Organization
Well-organized code is easier to maintain, debug, and extend.
"""
Points Management Script
Handles adding, removing, and checking user points.
"""
# Constants at the top
POINTS_PER_MINUTE = 10
MAX_POINTS = 1000000
MIN_BET = 100
def Execute():
"""Main entry point - route to appropriate handler."""
command = args.get("command", "check")
handlers = {
"check": handle_check,
"add": handle_add,
"remove": handle_remove,
"gamble": handle_gamble
}
handler = handlers.get(command, handle_unknown)
return handler()
def handle_check():
"""Show user's current points."""
user = get_user()
points = get_points(user)
CPH.SendMessage(f"{user} has {points:,} points")
return True
def handle_add():
"""Add points to a user (mod only)."""
if not is_mod():
CPH.SendMessage("Only mods can add points!")
return False
# ... rest of logic
return True
# Helper functions
def get_user():
return args.get("user", "anonymous")
def get_points(user):
return CPH.GetUserVar(user, "points", 0)
def is_mod():
return args.get("isModerator", False)Organization Principles
Constants at Top
Define configuration values at the top of your script for easy modification.
Single Responsibility
Each function should do one thing well. Break complex logic into smaller functions.
Descriptive Names
Use clear, descriptive names for functions and variables. Avoid abbreviations.
Document Intent
Add comments explaining why, not what. The code shows what happens.
Testing
Test your scripts before going live. Use debug modes and mock data to verify behavior.
# Add test mode to your scripts
DEBUG_MODE = False # Set True during development
def Execute():
if DEBUG_MODE:
# Log all incoming arguments
CPH.LogInfo("=== DEBUG START ===")
for key, value in args.items():
CPH.LogInfo(f" {key}: {value} ({type(value).__name__})")
CPH.LogInfo("=== DEBUG END ===")
# Main logic
result = process_command()
if DEBUG_MODE:
CPH.LogInfo(f"Result: {result}")
return result
def process_command():
# Your logic here
return True
# Test with mock data
def test_locally():
"""Run this function to test without FaustBot."""
global args
args = {
"user": "testuser",
"message": "!points check",
"isModerator": True
}
# Mock CPH for local testing
class MockCPH:
@staticmethod
def LogInfo(msg): print(f"[INFO] {msg}")
@staticmethod
def SendMessage(msg): print(f"[CHAT] {msg}")
@staticmethod
def GetUserVar(u, k, d=None): return d
@staticmethod
def SetUserVar(u, k, v): print(f"[VAR] {u}.{k} = {v}")
global CPH
CPH = MockCPH()
Execute()Testing Checklist
- Test with missing/empty arguments
- Test with invalid data types
- Test edge cases (zero, negative, very large values)
- Test with special characters in strings
- Test permission checks with different user roles
- Use FaustBot's Test Action feature with mock events
Common Mistakes
Avoid these frequently encountered issues in FaustBot scripts.
1. Not Returning a Boolean
The Execute() function must return True or False.
Forgetting to return stops the action.
2. Infinite Loops
Scripts that run too long will be terminated. Avoid infinite loops and limit iterations in while loops.
3. Blocking Operations
Long HTTP requests or file operations block the action queue. Use timeouts and consider async patterns where available.
4. Global State Bugs
Be careful with global variables. They persist between script runs and can cause unexpected behavior.
5. Rate Limit Violations
Don't send too many messages or API requests. Respect platform rate limits to avoid bans.
6. Hardcoded Values
Use variables and constants instead of magic numbers. Makes scripts easier to configure and maintain.