# WidgetBot > Discord chat integration for Dash apps with floating Crate button, inline Widget embed, slash commands, and AI-powered responses. --- .. toc:: .. llms_copy::WidgetBot # WidgetBot `dash-widgetbot` integrates Discord chat into your Dash applications using a pure Python hook-based architecture — no React build required. - **DiscordCrate** — Floating chat button (the one in the bottom-right corner of this page) - **DiscordWidget** — Inline iframe embed for placing chat anywhere in your layout - **Slash Commands** — AI-powered `/ai`, `/ask`, `/gen`, `/status` commands - **Webhook API** — Send messages to Discord from Python ## Installation [Visit GitHub Repo](https://github.com/pip-install-python/dash-widgetbot) ```bash pip install dash-widgetbot pip install dash-widgetbot[bot,ai] # with Discord bot + Gemini AI ``` --- ## Overview .. exec::docs.dash_widgetbot.introduction :code: false ```python # File: docs/dash_widgetbot/introduction.py import os import dash_mantine_components as dmc from dash_iconify import DashIconify from dash_widgetbot import discord_widget_container component = dmc.Stack([ dmc.Alert( children=dmc.Stack([ dmc.Text( "The floating Discord button in the bottom-right corner of this page is a " "DiscordCrate — a global chat widget that appears on every page. " "Try clicking it to open the community chat!", size="sm", ), dmc.Text( "Below is a DiscordWidget — an inline embed you can place anywhere in your layout.", size="sm", fw=500, ), ], gap="xs"), title="Two Components", color="indigo", icon=DashIconify(icon="ic:baseline-discord", width=20), ), dmc.SimpleGrid( cols=2, children=[ dmc.Paper( dmc.Stack([ dmc.Group([ DashIconify(icon="tabler:message-circle", width=24, color="#5865f2"), dmc.Text("DiscordCrate", fw=700, size="lg"), ], gap="xs"), dmc.Text("Floating chat button", size="sm", c="dimmed"), dmc.List([ dmc.ListItem("Global — appears on every page", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("Toggle, notify, navigate commands", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("Slash command bridge (/ai, /ask)", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("Event listeners (sentMessage, signIn)", icon=DashIconify(icon="tabler:check", width=16, color="teal")), ], size="sm", spacing="xs"), ], gap="sm"), withBorder=True, p="lg", ), dmc.Paper( dmc.Stack([ dmc.Group([ DashIconify(icon="tabler:layout-bottombar", width=24, color="#5865f2"), dmc.Text("DiscordWidget", fw=700, size="lg"), ], gap="xs"), dmc.Text("Inline iframe embed", size="sm", c="dimmed"), dmc.List([ dmc.ListItem("Placed anywhere in your layout", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("No floating button", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("Read-only events (message, signIn)", icon=DashIconify(icon="tabler:check", width=16, color="teal")), dmc.ListItem("No command dispatch", icon=DashIconify(icon="tabler:x", width=16, color="gray")), ], size="sm", spacing="xs"), ], gap="sm"), withBorder=True, p="lg", ), ], ), dmc.Paper( discord_widget_container( server=os.getenv("WIDGETBOT_SERVER", ""), channel=os.getenv("WIDGETBOT_CHANNEL", ""), width="100%", height="350px", ), withBorder=True, p="xs", radius="md", ), ], gap="md") ``` --- ## Inline Widget Embed .. exec::docs.dash_widgetbot.widget_example :code: false ```python # File: docs/dash_widgetbot/widget_example.py import os import dash_mantine_components as dmc from dash_widgetbot import discord_widget_container component = dmc.Stack([ dmc.Text("Inline Discord Widget", fw=600, size="lg"), dmc.Text( "discord_widget_container() creates a cross-origin iframe pointing to the " "WidgetBot shard. Unlike the Crate, it renders directly in your layout with " "no floating button. Configure server, channel, dimensions, and shard URL.", size="sm", c="dimmed", ), dmc.Paper( discord_widget_container( server=os.getenv("WIDGETBOT_SERVER", ""), channel=os.getenv("WIDGETBOT_CHANNEL", ""), width="100%", height="400px", ), withBorder=True, p="xs", radius="md", ), dmc.Code( """from dash_widgetbot import discord_widget_container widget = discord_widget_container( server=os.getenv("WIDGETBOT_SERVER"), channel=os.getenv("WIDGETBOT_CHANNEL"), width="100%", height="400px", shard="https://e-business.widgetbot.co", # optional custom shard )""", block=True, ), ], gap="md") ``` --- ## Sending Messages (Webhook) .. exec::docs.dash_widgetbot.webhook_example :code: false ```python # File: docs/dash_widgetbot/webhook_example.py import os import time import dash_mantine_components as dmc from dash import html, callback, Input, Output, State, clientside_callback, no_update from dash_iconify import DashIconify from dash_widgetbot.webhook import send_webhook_message component = dmc.Stack([ dmc.Text("Send Messages via Webhook", fw=600, size="lg"), dmc.Text( "Use send_webhook_message() to post messages to Discord from your Dash app. " "Messages are sent via the Discord webhook API and appear in the channel.", size="sm", c="dimmed", ), dmc.Paper( dmc.Stack([ dmc.Textarea( id="wb-webhook-content", label="Message", placeholder="Type a message to send to Discord...", value="Hello from pip-docs+ documentation!", minRows=2, autosize=True, ), dmc.Group([ dmc.TextInput( id="wb-webhook-username", placeholder="Bot display name (optional)", label="Username", style={"flex": 1}, ), dmc.TextInput( id="wb-webhook-avatar", placeholder="https://cdn.discordapp.com/embed/avatars/0.png", label="Avatar URL (optional)", style={"flex": 1}, ), ]), dmc.Group([ dmc.Button( "Send to Discord", id="wb-webhook-send-btn", leftSection=DashIconify(icon="tabler:send", width=18), color="indigo", loading=False, ), html.Div(id="wb-webhook-result"), ]), dmc.Text( "Open the floating Discord chat (bottom-right) to see your message appear.", size="xs", c="dimmed", fs="italic", ), ], gap="sm"), withBorder=True, p="lg", ), dmc.Code( """from dash_widgetbot.webhook import send_webhook_message result = send_webhook_message( "Hello from Dash!", webhook_url=os.getenv("DISCORD_WEBHOOK_URL"), # auto from .env username="My Bot", # optional display name avatar_url="https://...", # optional avatar thread_id="...", # optional thread ) # result: {"success": True, "message_id": "123...", "status_code": 200}""", block=True, ), ], gap="md") @callback( Output("wb-webhook-result", "children"), Input("wb-webhook-send-btn", "n_clicks"), State("wb-webhook-content", "value"), State("wb-webhook-username", "value"), State("wb-webhook-avatar", "value"), running=[(Output("wb-webhook-send-btn", "loading"), True, False)], prevent_initial_call=True, ) def send_message(_n, content, username, avatar_url): if not content: return dmc.Badge("Enter a message first", color="yellow", variant="light", size="sm") result = send_webhook_message( content, username=username or None, avatar_url=avatar_url or None, ) if result["success"]: return dmc.Badge( f"Sent! Open the Discord chat to see it.", color="green", variant="light", size="sm", ) return dmc.Badge( f"Error: {result.get('error', 'Unknown')[:60]}", color="red", variant="light", size="sm", ) ``` --- ## Command Bridge Patterns .. exec::docs.dash_widgetbot.commands_example :code: false ```python # File: docs/dash_widgetbot/commands_example.py import dash_mantine_components as dmc from dash_iconify import DashIconify component = dmc.Stack([ dmc.Text("Command Bridge Patterns", fw=600, size="lg"), dmc.Text( "The Crate is controlled via a store-based bridge. Python callbacks write " "command dicts to a dcc.Store, and clientside JS dispatches them to the " "Crate API. All helpers return command dicts with a _ts timestamp to " "prevent Dash deduplication.", size="sm", c="dimmed", ), dmc.Alert( "Try clicking the floating Discord button (bottom-right) and typing " "/status to see the command bridge in action!", title="Live Demo", color="teal", icon=DashIconify(icon="tabler:terminal-2", width=20), ), dmc.SimpleGrid( cols=2, children=[ dmc.Paper( dmc.Stack([ dmc.Text("Toggle Open/Close", fw=600, size="sm"), dmc.Code( """from dash_widgetbot import STORE_IDS, crate_toggle @callback( Output(STORE_IDS["command"], "data", allow_duplicate=True), Input("my-button", "n_clicks"), prevent_initial_call=True, ) def toggle(_n): return crate_toggle() # toggle # return crate_toggle(True) # force open # return crate_toggle(False) # force close""", block=True, ), ], gap="xs"), withBorder=True, p="md", ), dmc.Paper( dmc.Stack([ dmc.Text("Show Notification", fw=600, size="sm"), dmc.Code( """from dash_widgetbot import crate_notify return crate_notify( "Hello from Dash!", timeout=5000, # auto-dismiss in 5s avatar="https://...", # custom avatar )""", block=True, ), ], gap="xs"), withBorder=True, p="md", ), dmc.Paper( dmc.Stack([ dmc.Text("Navigate Channel", fw=600, size="sm"), dmc.Code( """from dash_widgetbot import crate_navigate # Switch to a different channel return crate_navigate("CHANNEL_ID") # Switch server + channel return crate_navigate( "CHANNEL_ID", guild="SERVER_ID" )""", block=True, ), ], gap="xs"), withBorder=True, p="md", ), dmc.Paper( dmc.Stack([ dmc.Text("Hide / Show", fw=600, size="sm"), dmc.Code( """from dash_widgetbot import crate_hide, crate_show # Hide the Crate button entirely return crate_hide() # Show it again return crate_show()""", block=True, ), ], gap="xs"), withBorder=True, p="md", ), ], ), dmc.Paper( dmc.Stack([ dmc.Text("All Bridge Helpers", fw=600, size="sm"), dmc.Table( [ dmc.TableThead(dmc.TableTr([ dmc.TableTh("Helper"), dmc.TableTh("Description"), ])), dmc.TableTbody([ dmc.TableTr([dmc.TableTd(dmc.Code("crate_toggle(is_open)")), dmc.TableTd("Toggle or set open/closed")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_notify(content, timeout)")), dmc.TableTd("Show notification bubble")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_navigate(channel, guild)")), dmc.TableTd("Switch to a channel")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_send_message(msg)")), dmc.TableTd("Send message on behalf of user")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_hide() / crate_show()")), dmc.TableTd("Hide/show the button")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_login() / crate_logout()")), dmc.TableTd("Auth controls")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_update_options(**opts)")), dmc.TableTd("Update config at runtime")]), dmc.TableTr([dmc.TableTd(dmc.Code("crate_set_color(var, val)")), dmc.TableTd("Set CSS variable")]), ]), ], ), ], gap="sm"), withBorder=True, p="lg", ), ], gap="md") ``` --- ## Slash Commands & Event Bridge .. exec::docs.dash_widgetbot.events_example :code: false ```python # File: docs/dash_widgetbot/events_example.py import dash_mantine_components as dmc from dash_iconify import DashIconify component = dmc.Stack([ dmc.Text("Slash Commands & Event Bridge", fw=600, size="lg"), dmc.Text( "WidgetBot doesn't support native Discord slash commands. dash-widgetbot " "implements a message-parsing bridge that intercepts /command text, builds " "a fake interaction, and dispatches to registered handlers.", size="sm", c="dimmed", ), dmc.Alert( children=dmc.Stack([ dmc.Text("Try these in the floating Discord chat (bottom-right):", size="sm", fw=500), dmc.List([ dmc.ListItem([dmc.Code("/status"), " — Show app info"]), dmc.ListItem([dmc.Code("/ai "), " — Generate AI content with Gemini"]), dmc.ListItem([dmc.Code("/ask "), " — Ask the AI a question"]), dmc.ListItem([dmc.Code("/ai "), " + attach an image — Multimodal AI"]), ], size="sm"), ], gap="xs"), title="Live Slash Commands", color="indigo", icon=DashIconify(icon="tabler:terminal-2", width=20), ), dmc.Paper( dmc.Stack([ dmc.Text("How the Bridge Works", fw=600, size="sm"), dmc.Code( """# 1. User types "/ai explain this image" in the Crate # 2. WidgetBot fires sentMessage via postMessage # 3. widgetbot_fix.js intercepts and forwards to Dash store: window.dash_clientside.set_props('_widgetbot-crate-event', { data: {type: 'sentMessage', content: '/ai explain...', ...} }) # 4. Python callback parses the command: @callback( Output("_crate-slash-result", "data"), Output("_widgetbot-crate-command", "data", allow_duplicate=True), Input("_widgetbot-crate-event", "data"), prevent_initial_call=True, ) def _handle_crate_slash(event_data): content = event_data.get("content", "") if not content.startswith("/"): return no_update, no_update # Parse "/ai prompt" → cmd_name="ai", rest="prompt" parts = content[1:].split(None, 1) cmd_name, rest = parts[0], parts[1] if len(parts) > 1 else "" # Build fake interaction and dispatch to handler... # 5. Handler generates AI response via Gemini # 6. Response posted to Discord channel via bot token # 7. Message appears in the Crate widget""", block=True, ), ], gap="sm"), withBorder=True, p="lg", ), dmc.Paper( dmc.Stack([ dmc.Text("Store IDs (Event System)", fw=600, size="sm"), dmc.Text( "add_discord_crate() returns 6 store IDs for the command/event bridge:", size="sm", c="dimmed", ), dmc.Table( [ dmc.TableThead(dmc.TableTr([ dmc.TableTh("Key"), dmc.TableTh("Store ID"), dmc.TableTh("Direction"), dmc.TableTh("Purpose"), ])), dmc.TableTbody([ dmc.TableTr([dmc.TableTd("config"), dmc.TableTd(dmc.Code("_widgetbot-crate-config")), dmc.TableTd("Read"), dmc.TableTd("Initial config (read-only)")]), dmc.TableTr([dmc.TableTd("command"), dmc.TableTd(dmc.Code("_widgetbot-crate-command")), dmc.TableTd("Write"), dmc.TableTd("Send commands to Crate")]), dmc.TableTr([dmc.TableTd("event"), dmc.TableTd(dmc.Code("_widgetbot-crate-event")), dmc.TableTd("Read"), dmc.TableTd("Generic events (sentMessage, etc.)")]), dmc.TableTr([dmc.TableTd("message"), dmc.TableTd(dmc.Code("_widgetbot-crate-message")), dmc.TableTd("Read"), dmc.TableTd("Incoming Discord messages")]), dmc.TableTr([dmc.TableTd("user"), dmc.TableTd(dmc.Code("_widgetbot-crate-user")), dmc.TableTd("Read"), dmc.TableTd("Sign-in / sign-out state")]), dmc.TableTr([dmc.TableTd("status"), dmc.TableTd(dmc.Code("_widgetbot-crate-status")), dmc.TableTd("Read"), dmc.TableTd("Ready + open state")]), ]), ], ), ], gap="sm"), withBorder=True, p="lg", ), ], gap="md") ``` --- ## Setup & Configuration .. exec::docs.dash_widgetbot.setup_guide :code: false ```python # File: docs/dash_widgetbot/setup_guide.py import dash_mantine_components as dmc from dash_iconify import DashIconify component = dmc.Stack([ dmc.Text("Setup & Configuration", fw=600, size="lg"), dmc.Alert( children="All hook registrations must happen BEFORE dash.Dash() is created. " "Hooks are processed at app creation time, not at request time.", title="Critical: Initialization Order", color="red", icon=DashIconify(icon="tabler:alert-triangle", width=20), ), dmc.Paper( dmc.Stack([ dmc.Text("Initialization Order", fw=600, size="sm"), dmc.Code( """from dotenv import load_dotenv load_dotenv() # 1. Register Crate hooks BEFORE Dash() from dash_widgetbot import add_discord_crate store_ids = add_discord_crate( server=os.getenv("WIDGETBOT_SERVER"), channel=os.getenv("WIDGETBOT_CHANNEL"), color=os.getenv("WIDGETBOT_COLOR", "#5865f2"), shard=os.getenv("WIDGETBOT_SHARD", ""), ) # 2. Register interactions endpoint BEFORE Dash() from dash_widgetbot import add_discord_interactions if os.getenv("DISCORD_PUBLIC_KEY"): add_discord_interactions() # 3. Create Dash app AFTER hooks import dash app = dash.Dash(__name__) # 4. Register command handlers AFTER app from dash_widgetbot import register_command, sync_discord_endpoint register_command("ask", my_handler, ephemeral=True) sync_discord_endpoint()""", block=True, ), ], gap="sm"), withBorder=True, p="lg", ), dmc.Paper( dmc.Stack([ dmc.Text("Environment Variables", fw=600, size="sm"), dmc.Table( [ dmc.TableThead(dmc.TableTr([ dmc.TableTh("Variable"), dmc.TableTh("Required"), dmc.TableTh("Description"), ])), dmc.TableTbody([ dmc.TableTr([dmc.TableTd(dmc.Code("WIDGETBOT_SERVER")), dmc.TableTd("Yes"), dmc.TableTd("Discord guild snowflake ID")]), dmc.TableTr([dmc.TableTd(dmc.Code("WIDGETBOT_CHANNEL")), dmc.TableTd("Yes"), dmc.TableTd("Default channel snowflake ID")]), dmc.TableTr([dmc.TableTd(dmc.Code("WIDGETBOT_COLOR")), dmc.TableTd("No"), dmc.TableTd("Button hex color (default #5865f2)")]), dmc.TableTr([dmc.TableTd(dmc.Code("WIDGETBOT_SHARD")), dmc.TableTd("No"), dmc.TableTd("Custom WidgetBot shard URL")]), dmc.TableTr([dmc.TableTd(dmc.Code("DISCORD_PUBLIC_KEY")), dmc.TableTd("Bot"), dmc.TableTd("Ed25519 public key for slash commands")]), dmc.TableTr([dmc.TableTd(dmc.Code("DISCORD_APPLICATION_ID")), dmc.TableTd("Bot"), dmc.TableTd("Discord application ID")]), dmc.TableTr([dmc.TableTd(dmc.Code("DISCORD_BOT_TOKEN")), dmc.TableTd("Bot"), dmc.TableTd("Bot token for API calls")]), dmc.TableTr([dmc.TableTd(dmc.Code("DISCORD_WEBHOOK_URL")), dmc.TableTd("Bot"), dmc.TableTd("Webhook URL for sending messages")]), dmc.TableTr([dmc.TableTd(dmc.Code("GEMINI_API_KEY")), dmc.TableTd("AI"), dmc.TableTd("Google Gemini API key for /ai, /ask")]), dmc.TableTr([dmc.TableTd(dmc.Code("GEMINI_MODEL")), dmc.TableTd("No"), dmc.TableTd("Gemini model name (default gemini-2.0-flash)")]), ]), ], ), ], gap="sm"), withBorder=True, p="lg", ), dmc.Paper( dmc.Stack([ dmc.Text("Package Extras", fw=600, size="sm"), dmc.Code( """pip install dash-widgetbot # Core (Crate + Widget) pip install dash-widgetbot[bot] # + Discord bot (PyNaCl, requests) pip install dash-widgetbot[ai] # + Gemini AI (google-genai) pip install dash-widgetbot[realtime] # + Socket.IO transport pip install dash-widgetbot[all] # Everything""", block=True, ), ], gap="sm"), withBorder=True, p="lg", ), ], gap="md") ``` --- ## Crate Properties — `add_discord_crate()` | Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | `server` | string | Required | Discord server (guild) snowflake ID | | `channel` | string | `""` | Default channel snowflake ID | | `color` | string | `"#5865f2"` | Button hex color | | `location` | list | `None` | `[vertical, horizontal]` position | | `notifications` | bool | `True` | Show notification bubbles | | `indicator` | bool | `True` | Show unread dot indicator | | `timeout` | int | `10000` | ms before notification auto-dismiss | | `defer` | bool | `False` | Lazy-load Crate | | `pages` | list | `None` | Route paths for visibility scoping | | `prefix` | string | `""` | Namespace for multi-instance | | `shard` | string | `""` | Custom shard URL | --- ## Widget Properties — `discord_widget_container()` | Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | `server` | string | Required | Discord server (guild) snowflake ID | | `channel` | string | `""` | Channel snowflake ID | | `width` | string | `"100%"` | CSS width | | `height` | string | `"600px"` | CSS height | | `shard` | string | `""` | Custom WidgetBot shard URL | --- ## Contributing Contributions to dash-widgetbot are welcome! Visit the [GitHub repo](https://github.com/pip-install-python/dash-widgetbot/issues) for issues and feature requests. ## License MIT License. --- *Source: /pip/dash_widgetbot* *Generated with dash-improve-my-llms*