Table of Contents
VoxAI Plugin Examples
This guide provides practical, copy-and-paste examples to help you get started with the VoxAI Plugin API. All code shown here should be placed inside the main register(app) function within your plugin's .py file.
For a complete list of all available functions, please see the API Reference.
—
Example 1: Basic Chat Command (!hello)
This is the “Hello World” of plugins. It registers a new chat command !hello that, when used, makes VoxAI send a chat message back to the user who typed it.
This example demonstrates:
app.register_command()app.send_twitch_chat()
# This is the function that will be called when someone types !hello
def say_hello(args, user):
# args = any text that came after the command (we ignore it here)
# user = the username of the person who sent the command
app.send_twitch_chat(f"@{user}, hello there! Welcome to the stream!")
# This tells VoxAI to listen for "!hello" and run our "say_hello" function
app.register_command("hello", say_hello)
—
Example 2: Streamer-Only Command (!shoutout)
This example demonstrates how to create a command that only the streamer can use. It uses the app.channel attribute to verify the user's identity, which is a core “Best Practice”. It also uses the args parameter to read text after the command.
This example demonstrates:
- Checking user permissions against
app.channel - Using the
argsparameter from a command app.register_command()
def handle_shoutout(args, user):
# BEST PRACTICE: Check if the user is the streamer (channel owner)
# We always compare in .lower() case to be safe.
if user.lower() != app.channel.lower():
return # Silently ignore if the user is not the streamer
# 'args' will be the username, e.g., if the command was "!so some_streamer",
# args will be "some_streamer"
target_user = args.strip()
if not target_user:
app.send_twitch_chat(f"@{user}, you forgot to specify a user to shout out!")
return
# Clean the username just in case they added an @
target_user = target_user.replace("@", "")
app.send_twitch_chat(f"=== BIG SHOUTOUT === Go check out the amazing {target_user} over at https://twitch.tv/{target_user.lower()} ! They are fantastic!")
# Register the command. VoxAI handles the rest.
app.register_command("so", handle_shoutout)
app.register_command("shoutout", handle_shoutout) # You can register multiple aliases
—
Example 3: Handling a Twitch Event (Raid)
Plugins can listen for any Twitch event, not just chat commands. This example listens for the raid event and uses TTS to speak a custom welcome message.
This example demonstrates:
app.register_event_handler()app.speak_text()(which is the easy-to-use TTS function)app.add_chat()(to add a log message to the UI)
# This function's parameters MUST match the Event Reference guide
def on_raid(raider_name, viewer_count):
# This function is automatically called by VoxAI when a raid occurs
welcome_message = f"Welcome in to all {viewer_count} raiders from {raider_name}'s channel! Get comfortable, we're glad you're here!"
# Make VoxAI speak the message aloud using the configured TTS service
app.speak_text(welcome_message)
# Also send a log message to the VoxAI chat window for the streamer to see
app.add_chat(f"[MyPlugin] Fired custom thank you for {raider_name}'s raid.")
# Register the handler to listen for the 'raid' event
app.register_event_handler("raid", on_raid)
—
Example 4: AI Command with Threading (!ask)
This is a more advanced example showing the correct way to use blocking or long-running tasks like app.ask_ai(). You must run these tasks in a separate thread to prevent VoxAI from freezing.
This example requires an import: Add import threading to the very top of your plugin .py file.
This example demonstrates:
- Using
import threading(CRITICAL BEST PRACTICE) app.ask_ai()- Handling a function in a new thread
# AT THE VERY TOP OF YOUR PLUGIN .py FILE, add this line:
import threading
# Then, inside your register(app) function, add the following logic:
def ask_ai_question(args, user):
# args = the question the user asked (e.g., "what is the capital of France?")
if not args:
app.send_twitch_chat(f"@{user}, please ask a question after the command!")
return
# We can build a more detailed prompt for the AI
prompt = f"A Twitch viewer named {user} has a question for you: {args}"
# CRITICAL: AI calls and other network requests can take several seconds.
# We MUST run them in a separate thread so they don't block the main VoxAI application.
def do_ai_work():
try:
app.add_chat(f"[MyPlugin] Sending prompt to AI for {user}...")
# This is the blocking call. It runs safely in the thread.
response = app.ask_ai(prompt)
# Send the response back to chat and speak it
app.send_twitch_chat(f"@{user}, {response}")
app.speak_text(response)
except Exception as e:
print(f"[MyPluginError] Failed to ask AI: {e}")
app.send_twitch_chat(f"@{user}, I had a little brain-fart and couldn't think of an answer. Sorry!")
# This is the final step: start the new thread.
# setting daemon=True ensures the thread will close when VoxAI closes.
threading.Thread(target=do_ai_work, daemon=True).start()
app.register_command("ask", ask_ai_question)
—
Example 5: Self-Contained Plugin with a Sound File
This demonstrates the “Best Practice” for creating a self-contained plugin. The plugin creates its own data folder to store an audio file, then registers a command to play it.
This example assumes you have created a folder named my_sound_plugin_data inside your plugins folder, and inside that folder, you have placed a file named alert.mp3.
File Structure:
plugins/
|
+- my_sound_plugin.py
|
+- my_sound_plugin_data/
|
+- alert.mp3
This example demonstrates:
app.play_sound_immediately()- Using
os.pathto create a self-contained plugin (BEST PRACTICE)
# AT THE VERY TOP OF YOUR PLUGIN .py FILE, add this line:
import os
# --- This setup code goes at the top of your register() function ---
# Best Practice: Store your assets in a dedicated subfolder.
# This gets the directory where your .py file is currently located.
plugin_dir = os.path.dirname(__file__)
# This creates a path to your data folder (e.g., "plugins/my_sound_plugin_data")
data_folder = os.path.join(plugin_dir, "my_sound_plugin_data")
# This creates the folder if it doesn't already exist.
os.makedirs(data_folder, exist_ok=True)
# Define the full path to your sound file
my_alert_sound = os.path.join(data_folder, "alert.mp3")
# --- Now, we define and register our command ---
def play_alert(args, user):
# Always check if the file exists before trying to play it!
if os.path.exists(my_alert_sound):
# We use play_sound_immediately so it doesn't wait for TTS to finish
app.play_sound_immediately(my_alert_sound)
app.send_twitch_chat("WEE-WOO WEE-WOO! Alert has been triggered!")
else:
# This message helps the streamer debug if they installed it wrong.
app.send_twitch_chat(f"@{user}, the streamer is missing the alert.mp3 file!")
app.add_chat(f"[MyPlugin] ERROR: Could not find sound file at: {my_alert_sound}")
app.register_command("alert", play_alert)
—
Example 6: Creating a Plugin Menu Window
This example shows how to add your own menu item to the main “Plugins” menu in the VoxAI app. This is perfect for opening a settings or “about” window for your plugin.
This example requires imports for Tkinter. Add these to the top of your plugin file:
import tkinter as tk import customtkinter as ctk
This example demonstrates:
app.add_plugin_menu_command()- Using
app.masteras the parent for a newtk.Toplevelwindow - Using
win.grab_set()to make the window modal (stay on top)
# --- Place this logic inside your register() function ---
def open_my_about_window():
# Create a new window. Always use app.master as the parent!
win = tk.Toplevel(app.master)
win.title("About My Plugin")
win.geometry("350x200")
# This tells the window to "grab" focus and stay on top of the main app.
win.transient(app.master)
win.grab_set()
# We can use CustomTkinter widgets to match the style of VoxAI
main_frame = ctk.CTkFrame(win, fg_color="#2b2b2b")
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
ctk.CTkLabel(main_frame, text="My Awesome Plugin!", font=("Segoe UI", 20, "bold")).pack(pady=(10, 5))
ctk.CTkLabel(main_frame, text="Version 1.0 - Made by Modder McModface").pack(pady=5)
ctk.CTkButton(main_frame, text="Awesome!", command=win.destroy).pack(pady=20, side="bottom")
# This one line adds the button to the "Plugins" dropdown menu in the main VoxAI app.
app.add_plugin_menu_command("About My Awesome Plugin", open_my_about_window)
—
Example 7: Handling Power-ups (Gigantify)
VoxAI supports Twitch Power-ups like Gigantify, Message Effects, and On-Screen Celebrations. This example listens for these effects and tracks how many bits were spent on them.
This example demonstrates:
app.register_event_handler()for the new “powerup” event.- Extracting cost and type data.
def on_powerup(user, powerup_type, cost):
# powerup_type will be "gigantify_an_emote", "message_effect", or "celebration"
# cost is the integer amount of bits used (e.g. 50)
clean_type = powerup_type.replace("_", " ").title() # e.g. "Gigantify An Emote"
app.add_chat(f"[MyPlugin] {user} used {clean_type} for {cost} bits!")
if cost > 0:
# Example logic: Give the user 100 points for every bit spent
new_points = cost * 100
app.send_twitch_chat(f"Wow @{user}! Thanks for the Power-up! You earned {new_points} imaginary points!")
# Register the handler
app.register_event_handler("powerup", on_powerup)
—
Example 8: Saving & Loading Settings (Persistence)
Your plugin might need to remember things between sessions (like a custom API key, a toggle state, or a “High Score”). Since VoxAI's main settings file is reserved for the core app, Best Practice is to save a local JSON file inside your plugin's data folder.
This example creates a command `!setkey [value]` that saves a value to a file, and remembers it even after VoxAI restarts.
import json
import os
# 1. Setup paths
plugin_dir = os.path.dirname(__file__)
data_folder = os.path.join(plugin_dir, "my_settings_data")
settings_file = os.path.join(data_folder, "config.json")
os.makedirs(data_folder, exist_ok=True)
# 2. Global variable to hold our settings in memory
my_settings = {"api_key": "default"}
# 3. Helper functions to load/save
def load_settings():
global my_settings
if os.path.exists(settings_file):
try:
with open(settings_file, "r") as f:
my_settings = json.load(f)
except:
print("[MyPlugin] Error loading settings, using defaults.")
def save_settings():
with open(settings_file, "w") as f:
json.dump(my_settings, f, indent=4)
# Load immediately when plugin starts
load_settings()
# 4. Command to change the setting
def set_api_key(args, user):
if not args:
app.send_twitch_chat(f"@{user}, please provide a key! usage: !setkey 12345")
return
# Update global variable
my_settings["api_key"] = args.strip()
# Save to disk
save_settings()
app.send_twitch_chat(f"@{user}, API Key updated and saved!")
# 5. Command to read the setting
def check_key(args, user):
current_key = my_settings.get("api_key", "None")
app.send_twitch_chat(f"The current saved key is: {current_key}")
app.register_command("setkey", set_api_key)
app.register_command("checkkey", check_key)
—
Example 9: Passive Chat Listener (Hooks)
Sometimes you want the bot to react to normal chat messages without a `!` command. You can do this using Chat Hooks.
Warning: Code in hooks runs for every single chat message. Keep it fast! If you need to do heavy work (like AI or API calls), spin up a thread.
def my_chat_hook(user, message):
# Ignore messages from the bot itself to prevent infinite loops
if user.lower() == app.channel.lower():
return
# Check for a keyword
if "pizza" in message.lower():
# React naturally without a command
app.send_twitch_chat(f"Did someone say pizza? I love pizza, @{user}!")
# Advanced: React to "Good Bot"
if "good bot" in message.lower():
app.speak_text(f"Thank you {user}, I try my best.")
# Register the hook
# Note: We append to the list, we don't use a register function.
app.plugin_chat_hooks.append(my_chat_hook)
—
Example 10: Periodic Timers (Loops)
If you want your plugin to do something automatically every X minutes (like a “Hydrate” reminder), you need a threaded loop.
Important: You must check `getattr(app, “_shutdown”, False)` in your loop. This ensures the thread stops running when you close VoxAI.
import threading
import time
def hydration_timer():
interval_seconds = 1800 # 30 minutes
# Loop forever until VoxAI shuts down
while not getattr(app, "_shutdown", False):
# Sleep in small chunks so we can exit quickly if the app closes
for _ in range(interval_seconds):
if getattr(app, "_shutdown", False): return
time.sleep(1)
# Time is up, do the thing!
if app.is_channel_live(): # Only spam chat if we are actually live
app.send_twitch_chat("🥤 Time to hydrate! Take a sip of water!")
# Start the timer in the background
threading.Thread(target=hydration_timer, daemon=True).start()
—
Example 11: Universal Bit Tracking (Goal Bar Logic)
VoxAI 1.1.8 introduced the “bits” event. This event fires for EVERY bit usage: standard cheers, power-ups (Gigantify), Extension usage, and Combos.
Use this event if you are building a Goal Bar, Leaderboard, or Total Counter. Do not use the legacy “cheer” or “powerup” events for accounting, or you might miss bits.
def on_any_bits(user, amount):
# amount will be an integer
try:
bit_val = int(amount)
except:
bit_val = 0
if bit_val > 0:
# Example: Add to a global counter
# global_bit_count += bit_val
print(f"[MyPlugin] Tracking: {user} spent {bit_val} bits (Source: Universal).")
# If this was a massive drop, maybe trigger a special alert?
if bit_val >= 5000:
app.send_twitch_chat(f"😱 HOLY MOLY! {user} just dropped {bit_val} bits!")
# Register for the universal "bits" event
app.register_event_handler("bits", on_any_bits)
—
Example 12: Audio Visualizer (Real-Time Volume)
VoxAI 1.1.7+ exposes the raw audio volume level while the bot is speaking. You can use this to animate avatars, create visualizers, or trigger lighting effects.
Note: This event fires very rapidly (approx. 50 times per second). Keep the logic inside very simple!
def on_audio_level(level):
# level is a float, usually between 0.0 (Silent) and 1.0 (Loud)
current_vol = float(level)
# Example: Simple Threshold (Mouth Open/Closed)
threshold = 0.05
if current_vol > threshold:
# Set your avatar image to Open
pass
else:
# Set your avatar image to Closed
pass
# Example: Print loudness (Only for testing, spams console!)
# print(f"Volume: {current_vol:.3f}")
app.register_event_handler("audio_level", on_audio_level)
