ggbro Developer Documentation
Build bots, integrations, and tools for your ggbro community.
Introduction
python-socketio / socket.io-client, or the single-file Python helper at /developers/ggbro.py (also: music_bot.py, welcome_bot.py).Base URL:
https://ggbro.appAPI Base:
https://ggbro.app/api/v1 (/api/v1)Socket.IO Gateway:
https://ggbro.app (or direct https://ws.ggbro.app)Discovery:
GET https://ggbro.app/api/v1 or GET /api/v1/platform/configAPI version: All REST routes live under /api/v1. Future breaking changes will ship as
/api/v2 while v1 stays stable. Channel webhook execute URLs use /api/webhooks/:id/:token (not versioned in the path).Product tour: ggbro Features
The ggbro Bot API allows you to create bots that:
- Respond to slash commands (e.g.,
/play,/ping,/serverinfo) - Send messages and rich embed messages in text channels
- React to events like members joining, messages being sent, etc.
- Send direct messages to users
- Join voice channels and stream audio
- Send text in voice channels only when
chat_enabledis on
Technology Stack
| Component | Technology | Package to Install |
|---|---|---|
| Desktop / Web UI | Electron + React (TypeScript), Tailwind CSS | — |
| Backend | Node.js + Express (TypeScript), SQLite | — |
| Real-time Gateway | Socket.IO v4 | Python: python-socketio[client] — Node.js: socket.io-client |
| REST API | HTTP JSON | Python: requests — Node.js: axios or built-in fetch |
| Voice / media | WebRTC | Browser / Electron APIs |
| Authentication | Bearer token (JWT) | Passed in headers (REST) and handshake auth (Socket.IO) |
Quick Start
Here's the fastest way to get a bot running:
- Go to the Developer Portal or ggbro app → User Settings → Applications
- Create an Application — this automatically creates a bot user and gives you a token
- Copy the Bot Token — you'll need it to authenticate
- Add the bot to a server you own or admin
- Connect via Socket.IO, register slash commands, and start responding!
Minimal Python Bot (5 lines to connect)
bash Install dependenciespip install "python-socketio[client]" requests
Python minimal_bot.pyimport socketio sio = socketio.Client() @sio.event def connect(): print("Connected to ggbro!") sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"}) sio.wait()
Minimal Node.js Bot
bash Install dependenciesnpm install socket.io-client
JavaScript minimal_bot.jsconst { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } }); socket.on("connect", () => console.log("Connected to ggbro!"));
Authentication
All API requests require a Bearer token in the Authorization header:
HTTP Authentication HeaderAuthorization: Bearer YOUR_BOT_TOKEN
For Socket.IO connections, pass the token in the handshake auth object:
Python Socket.IO Authimport socketio sio = socketio.Client(reconnection=True, reconnection_delay=1) sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"})
JavaScript Socket.IO Authconst { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } });
Creating a Bot
Create an application via the Developer Portal UI, or via the REST API:
Create a new application. Automatically creates a bot user account and returns a bot token.
JSON Request Body{ "name": "My Awesome Bot", "description": "A bot that does cool things" }
JSON Response{ "application": { "id": "abc123...", "name": "My Awesome Bot", "bot_user_id": "bot456...", "client_secret": "hex...", "default_permissions": 8895488, "invite_permission_locked": false }, "bot_token": "eyJhbGciOi..." }
The bot user starts offline until it connects. Default invite permissions are least-privilege (8895488); change them via the portal or PATCH .../permissions.
Application Management Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/oauth2/applications | List your applications |
GET | /api/v1/oauth2/applications/:appId | Get application details |
PUT | /api/v1/oauth2/applications/:appId | Update application name and description |
POST | /api/v1/oauth2/applications/:appId/token | Regenerate the bot token |
DELETE | /api/v1/oauth2/applications/:appId | Delete the application and its bot user |
POST | /api/v1/oauth2/applications/:appId/avatar | Upload bot avatar (multipart, field: avatar) |
POST | /api/v1/oauth2/applications/:appId/banner | Upload bot banner (multipart, field: banner) |
PATCH | /api/v1/oauth2/applications/:appId/banner-offset | Set banner offset position |
PATCH | /api/v1/oauth2/applications/:appId/public-invite | Toggle public invite on/off |
PATCH | /api/v1/oauth2/applications/:appId/permissions | Set default invite permissions (default_permissions, invite_permission_locked) |
Bot Command Management
| Method | Path | Description |
|---|---|---|
GET | /api/v1/oauth2/applications/:appId/commands | List all commands registered for this bot |
POST | /api/v1/oauth2/applications/:appId/commands | Create a new command for this bot |
PUT | /api/v1/oauth2/applications/:appId/commands/:cmdId | Update a specific command |
DELETE | /api/v1/oauth2/applications/:appId/commands/:cmdId | Delete a specific command |
GET | /api/v1/oauth2/servers/:serverId/commands | List all bot commands available in a server |
Adding Bot to a Server
Discord-like invite flow: the permissions bitfield in the invite link is what gets granted when a server admin authorizes. New bots start offline and with a least-privilege default (8895488) — not Administrator. Set defaults (including Administrator) in the Developer Portal or via PATCH .../permissions.
Update the app's default invite permissions (owner token).
Body: {"default_permissions": 512, "invite_permission_locked": false}
If ADMINISTRATOR (512) is set, it is stored alone (grants all at runtime). Changing defaults only affects new invite links — existing server bot roles are unchanged.
Response: {"default_permissions", "invite_permission_locked", "permission_labels"}
Add the bot to a server (owner must also be admin/owner of that server). Body optional: {"permissions": <bitfield>}. Creates a managed role named after the bot (without the [BOT] tag) with those permissions. The role cannot be assigned to other members.
Remove the bot from a server. The managed bot role is kept (Discord-like) until a server admin deletes it in Server Settings.
List servers the bot is in, including each server's current bot_permissions / permission_labels (read-only for the bot owner). Bot owners cannot remotely change a server's bot role — only that server's admins can, via role settings.
Disabled (403). Bot owners cannot change permissions on servers. Server admins edit the bot's managed role in Server Settings → Roles.
Public invite card (no auth). Includes default_permissions, invite_permission_locked, permission_labels, and permission_flags.
Authorize the bot onto a server (caller must be server admin/owner; respects public_invite). Body: {"permissions": <bitfield>} — typically the value from the invite URL. If the bot is already in the server, updates the managed role permissions instead. If locked, the bitfield must match the app defaults.
Socket.IO Gateway
Bots connect to the real-time gateway using Socket.IO v4. The gateway provides live events for messages, interactions, typing, presence, and voice.
python-socketio (Python) or socket.io-client (Node.js). Do not use raw WebSocket libraries — Socket.IO has its own protocol on top of WebSocket.
Connecting & Joining Rooms
After connecting, you must join server and channel rooms to receive events:
Python Connect and Join Roomsimport socketio sio = socketio.Client(reconnection=True) @sio.event def connect(): print("Connected!") # Join a server room to receive member events, typing, etc. sio.emit("server:join", "YOUR_SERVER_ID") # Join a channel room to receive messages in that channel sio.emit("channel:join", "YOUR_CHANNEL_ID") sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"}) sio.wait()
JavaScript Connect and Join Roomsconst { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } }); socket.on("connect", () => { console.log("Connected!"); socket.emit("server:join", "YOUR_SERVER_ID"); socket.emit("channel:join", "YOUR_CHANNEL_ID"); });
Room Events
| Emit Event | Payload | Description |
|---|---|---|
server:join | "serverId" (string) | Join a server room to receive member/presence events |
channel:join | "channelId" (string) | Join a channel room to receive message:new events |
Slash Commands
Slash commands let users interact with your bot by typing /commandname. The client shows a Discord-style picker: left rail of bots/apps, commands grouped by bot, plus frequently used. Selecting a command with options opens an option builder — options appear sequentially after the command is selected (not while browsing the list); Tab cycles fields; USER / CHANNEL / ROLE / MENTIONABLE options use searchable entity pickers. Option types mirror Discord’s Application Command Option Types.
/play), matches are ranked
exact name → name prefix → name contains → description.
Tab/Enter selects the top hit — so /play beats a description match like /np (“Now playing”).
Registering Commands
Register commands via REST using your bot token. This replaces all existing global commands for the app (max 50):
Bulk register slash commands. Requires bot token auth. Body: array of command objects.
Owner/dashboard alternatives: GET/POST /api/v1/oauth2/applications/:appId/commands, PUT/DELETE .../commands/:cmdId.
Client list: GET /api/v1/oauth2/servers/:serverId/commands returns every bot command in that server (includes bot_id, bot_name, bot_avatar, app_name).
Command Object
| Field | Type | Description |
|---|---|---|
name | string | 1–32 chars, ^[\w-]{1,32}$, stored lowercased |
description | string | Shown in autocomplete |
options | array | Up to 25 typed option definitions (see below) |
server_id | string? | Optional server scope (owner registration only) |
Option Types (Discord-compatible)
| Type | Code | Aliases | Parsed value |
|---|---|---|---|
| STRING | 3 | string, str, text | string (mentions unwrapped to UUID) |
| INTEGER | 4 | integer, int | number (int) when parseable |
| BOOLEAN | 5 | boolean, bool | true/false from true/1/yes/y/on or false/0/no/n/off |
| USER | 6 | user, member | user UUID (from <@id> or raw id) |
| CHANNEL | 7 | channel | channel UUID (from <#id> or raw id) |
| ROLE | 8 | role | role UUID (from <@&id> or raw id) |
| MENTIONABLE | 9 | mentionable | user or role UUID |
| NUMBER | 10 | number, float, double | float when parseable |
Missing/unknown type defaults to STRING (3). The desktop/web client also infers USER/CHANNEL/ROLE from common option names (user, member, channel, role, …) when a bot registered them as plain STRING.
Option Fields
| Field | Type | Description |
|---|---|---|
name | string | Required. 1–32 chars, ^[\w-]{1,32}$, lowercased |
description | string | Max 100 chars |
type | number | string | Type code or alias above |
required | boolean | Default false |
choices | array? | Up to 25 {name, value} pairs (value string or number) |
autocomplete | boolean? | Stored as true when set (UI hint; live bot autocomplete events not yet sent) |
min_value / max_value | number? | Numeric bounds (INTEGER / NUMBER) |
max_length | number? | STRING max length (clamped 1–6000) |
channel_types | number[]? | CHANNEL filter hint (up to 10 numbers) |
How Clients Send Arguments
- Current desktop/web wire (preferred): positional values in option order —
/play --__bot <@botId> ovetto 7amra. Chat history andargsshow/play ovetto 7amra(routing flag stripped). Multi-word last options stay unquoted when alone. - Named (manual):
/ban --user <@uuid> --reason spamming --days 7. The server still parses these, strips surrounding quotes from values, then normalizesargsto positional form beforeinteraction:create. - Positional (by option order):
/ban <@uuid> spamming 7 - Mentions:
<@userId>,<#channelId>,<@&roleId>— unwrapped to bare UUIDs inoptions. The client also resolves bare member UUIDs /<@id>in slash option fields and the message box into@usernametags when that member is on the server. - Choices: user may pass choice
nameorvalue; delivered value is the choicevalue - Quoted strings work for positional args when a later option is also filled:
/play "lo-fi hip hop" 80 - If no option defs: raw args land in
options.value - Bot routing (client): when multiple bots share a command name, the UI sends
--__bot <@botUserId>. The server strips it beforeinteraction:create— bots never see__botinoptionsorargs. Do not register a user-facing option named__bot.
data.options.<name> (or SDK ctx.get_option('name')) over raw data.args.
Example: for /play, use options.query — never search using a string that still contains --query.
args is the clean positional text (e.g. ovetto 7amra) for simple single-string commands.
Python Register Typed Commandsimport requests BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" commands = [ {"name": "ping", "description": "Check if the bot is alive", "options": []}, { "name": "play", "description": "Play a song", "options": [ { "name": "query", "description": "YouTube URL or search term", "type": "string", # or 3 "required": True, "max_length": 200, }, { "name": "volume", "description": "Playback volume", "type": 4, # INTEGER "required": False, "min_value": 0, "max_value": 200, "choices": [ {"name": "Quiet", "value": 50}, {"name": "Normal", "value": 100}, {"name": "Loud", "value": 150}, ], }, ], }, { "name": "timeout", "description": "Timeout a member", "options": [ {"name": "user", "description": "Member to timeout", "type": "user", "required": True}, {"name": "minutes", "description": "Duration in minutes", "type": "integer", "required": True, "min_value": 1, "max_value": 40320}, {"name": "reason", "description": "Moderation reason", "type": 3, "required": False}, ], }, { "name": "announce", "description": "Post to a channel", "options": [ {"name": "channel", "type": "channel", "description": "Target channel", "required": True}, {"name": "role", "type": "role", "description": "Role to ping", "required": False}, {"name": "message", "type": "string", "description": "Announcement text", "required": True}, {"name": "silent", "type": "boolean", "description": "Suppress mentions", "required": False}, ], }, ] r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=commands, headers={"Authorization": f"Bearer {BOT_TOKEN}"}, ) print("Commands registered!" if r.ok else f"Failed: {r.text}")
JavaScript Register Typed Commandsconst axios = require("axios"); const BOT_TOKEN = "YOUR_BOT_TOKEN"; const API_URL = "https://ggbro.app"; axios.post(`${API_URL}/api/v1/oauth2/commands`, [ { name: "ping", description: "Check bot latency", options: [] }, { name: "ban", description: "Ban a member", options: [ { name: "user", description: "User to ban", type: "user", required: true }, { name: "reason", description: "Ban reason", type: 3, required: false, choices: [ { name: "Spam", value: "spam" }, { name: "Harassment", value: "harassment" }, { name: "Other", value: "other" }, ], }, { name: "silent", description: "Skip public notice", type: "boolean" }, ], }, { name: "move", description: "Move a user to a voice channel", options: [ { name: "user", type: 6, description: "Member", required: true }, { name: "channel", type: 7, description: "Voice channel", required: true, channel_types: [2] }, ], }, ], { headers: { Authorization: `Bearer ${BOT_TOKEN}` }, }).then(() => console.log("Commands registered!"));
Receiving & Responding to Interactions
When a user runs a slash command, your bot receives interaction:create with a typed options object. Respond with interaction:response:
JSON interaction:create (slash_command){ "id": "interaction-uuid", "type": "slash_command", "command": "play", "args": "ovetto 7amra", "options": { "query": "ovetto 7amra" }, "channel_id": "channel-uuid", "server_id": "server-uuid", "message_id": "original-user-message-id", "user": { "id": "user-uuid", "username": "JadaDev" } }
Python Handle Typed Options@sio.on("interaction:create") def on_interaction(data): if data.get("type") != "slash_command": return cmd = (data.get("command") or "").lower() opts = data.get("options") or {} if cmd == "ping": sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": "Pong! 🏓", }) elif cmd == "timeout": target = opts.get("user") # UUID string minutes = opts.get("minutes") # int reason = opts.get("reason") or "No reason" sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": f"Timed out {target} for {minutes}m ({reason})", })
JavaScript Handle Typed Optionssocket.on("interaction:create", (data) => { if (data.type !== "slash_command") return; const cmd = (data.command || "").toLowerCase(); const opts = data.options || {}; if (cmd === "announce") { const channelId = opts.channel; const silent = opts.silent === true; socket.emit("interaction:response", { interaction_id: data.id, channel_id: data.channel_id, content: silent ? "Queued silent announce." : `Will post to <#${channelId}>`, }); } });
data.id → pass as interaction_id in the responsedata.command → slash command namedata.args → clean positional text after the command (e.g. ovetto 7amra; never includes --__bot or raw --option flags after server normalize)data.options → typed map: strings, numbers, and booleans (IDs already unwrapped) — prefer thisdata.channel_id / data.server_id / data.user / data.message_id → context
/developers/ggbro.py v1.2+ exposes
ctx.options, ctx.args, and ctx.get_option('query', ctx.args).
Use that for /play-style commands so multi-word queries never include flag text.
Sending Messages
Bots can send messages to any channel they have access to. Use the bot:send_message Socket.IO event:
bot:send_message, interaction:response, and bot:edit_message all return an acknowledgement payload with {ok, message_id, message}. Store the returned message_id if you plan to edit the message later.
Python Send a Message# Send a simple text message sio.emit("bot:send_message", { "channel_id": "YOUR_CHANNEL_ID", "content": "Hello from my bot! 🤖" })
JavaScript Send a Message// Send a simple text message socket.emit("bot:send_message", { channel_id: "YOUR_CHANNEL_ID", content: "Hello from my bot! 🤖" });
Editing a Bot Message
Bot-authored channel messages can be edited later with bot:edit_message. Edited messages automatically show an edited marker and timestamp in the client UI.
JavaScript Edit a Previously Sent Messagesocket.emit("bot:edit_message", { channel_id: channelId, message_id: existingMessageId, content: "Updated content", embeds: [{ title: "Updated Panel", description: "This embed was edited in place.", color: "#23A55A" }] });
You can also send messages via the REST API:
Send a message (with optional file attachments). Use form data with a content field.
Embeds & Components
Bots can send rich embed messages — similar to Discord embeds. Embeds display a colored sidebar, title, description, fields grid, images, and footer. Use them for /serverinfo, now-playing cards, leaderboards, help menus, and more.
Embed Object
| Field | Type | Description |
|---|---|---|
title | string? | Embed title (rendered as a link if url is set) |
description | string? | Main text body |
url | string? | URL the title links to |
color | string? | Left border colour as hex string, e.g. "#5865F2" |
fields | array? | Array of {name: string, value: string, inline?: boolean} |
thumbnail | string? | Small image URL (top-right corner) |
image | string? | Large image URL (bottom of embed) |
footer | object? | {text: string, icon_url?: string} |
author | object? | {name: string, url?: string, icon_url?: string} |
timestamp | string? | ISO-8601 timestamp shown next to footer |
components | array? | Interactive components rendered under the embed. Supported types: button, string_select, channel_select |
Interactive Components
| Type | Required Fields | Notes |
|---|---|---|
button | {type, label, custom_id} | Use style = primary, secondary, success, danger, or link. Link buttons use url instead of custom_id. |
string_select | {type, custom_id, options[]} | Each option supports {label, value, description?, emoji?, default?}. |
channel_select | {type, custom_id} | Use channel_types to limit results, for example ["text"]. |
Sending Embeds
Pass an embeds array in either bot:send_message or interaction:response. You can include content alongside embeds, or send embeds only (with empty content):
Python Send an Embed via bot:send_messagesio.emit("bot:send_message", { "channel_id": channel_id, "content": "", "embeds": [{ "title": "Hello World", "description": "This is a rich embed message!", "color": "#5865F2", "fields": [ {"name": "Field 1", "value": "Some value", "inline": True}, {"name": "Field 2", "value": "Another value", "inline": True}, ], "footer": {"text": "Powered by ggbro"} }] })
Python Reply to a Slash Command with an Embed@sio.on("interaction:create") def on_interaction(data): if data["command"] == "info": sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": "", "embeds": [{ "title": "Bot Info", "description": "I'm a bot built for ggbro!", "color": "#23a55a", "author": {"name": "My Bot"}, "timestamp": datetime.utcnow().isoformat() }] })
JavaScript Send an Embedsocket.emit("bot:send_message", { channel_id: channelId, content: "", embeds: [{ title: "Now Playing 🎵", description: `Duration: 3:45 | Requested by JadaDev`, color: "#FF0000", thumbnail: "https://i.ytimg.com/vi/.../hqdefault.jpg", author: { name: "Music Bot" }, footer: { text: "Queue: 5 songs remaining" }, components: [ { type: "button", custom_id: "music:pause", label: "Pause", style: "secondary", emoji: { name: "⏸️" } }, { type: "button", custom_id: "music:skip", label: "Skip", style: "primary", emoji: { name: "⏭️" } }, { type: "string_select", custom_id: "music:volume", placeholder: "Set volume", options: [ { label: "50%", value: "50" }, { label: "100%", value: "100", default: true }, { label: "150%", value: "150" } ] }, { type: "channel_select", custom_id: "music:panel-channel", placeholder: "Move panel to a text channel", channel_types: ["text"] } ] }] });
Handling Component Interactions
When a user presses a button or changes a select menu, your bot receives another interaction:create event with type = "message_component".
JavaScript Handle Button and Select Interactionssocket.on("interaction:create", async (data) => { if (data.type !== "message_component") return; if (data.custom_id === "music:skip") { // Update your state, then edit the original control message socket.emit("bot:edit_message", { channel_id: data.channel_id, message_id: data.message_id, embeds: [{ title: "Skipped", description: "Queue advanced.", color: "#5865F2" }] }); } if (data.custom_id === "music:panel-channel") { const selected = data.selected_channels?.[0]; if (!selected) return; socket.emit("bot:send_message", { channel_id: selected.id, embeds: [{ title: "Panel moved", description: "Controls are now posted in this channel." }] }); } });
File Uploads & Attachments
Bots and users can attach files via multipart upload. Prefer Bot REST for text/embeds; use upload for binary attachments.
Multipart form-data. Fields: content (optional text), files / file parts, optional replyTo, optional remoteUrl (Tenor / direct GIF URL).
Python Upload Attachmentimport requests r = requests.post( f"{API_URL}/api/v1/messages/{channel_id}/upload", headers={"Authorization": f"Bearer {BOT_TOKEN}"}, data={"content": "Here is a file"}, files=[("files", ("report.png", open("report.png", "rb"), "image/png"))], ) print(r.json())
JSON Attachment shape on messages{ "id": "attachment-uuid", "filename": "/uploads/attachments/...", "original_name": "report.png", "mime_type": "image/png", "size": 12345 }
- Default max file size: 25 MB. Verified servers or GGBRO Plus users: 500 MB.
- Metadata:
GET /api/v1/messages/attachment/:attachmentId - DM uploads:
POST /api/v1/dm/:targetUserId/upload(and group DMPOST /api/v1/dm/groups/:groupId/upload)
Bot REST API (/api/v1/oauth2/bot/...)
Bot-token-only HTTP surface (requires is_bot). Prefer these over user routes when writing bots. Base path: /api/v1/oauth2/bot.
Messages & DMs
Send channel message. Body: {"content": "...", "embeds": [...], "reply_to": "..."}. Need content and/or embeds. Requires VIEW_CHANNEL + SEND_MESSAGES. For voice channels, chat_enabled must be 1 or returns 403 (Text chat is disabled for this voice channel).
Edit own message. Body: {"content"?, "embeds"?}
Delete own message.
Body: {"message_ids": ["...", ...]} (max 100).
Query: ?before=&after=&limit= (1–100, default 50).
DM a user who shares a server with the bot. Body: {"content"?, "embeds"?}
JSON POST channel message{ "content": "Hello", "embeds": [{ "title": "Status", "description": "Online", "color": "#23A55A" }], "reply_to": "optional-message-id" }
Servers, Members, Roles, Invites
Server info + member_count.
List channels.
List members.
Member + assigned roles.
Kick member (emits member:left).
Body: {"user_id": "...", "reason": "..."}
Unban.
List roles.
Assign role.
Remove role.
Create invite. Body: {"max_uses"?, "expires_in"?} (seconds).
List active invites.
Delete invite.
Body: {"status": "online|idle|dnd|offline", "activity": {"type": "playing"|"listening"|"watching"|"competing"|"custom", "name": "...", "details"?, "state"?, "started_at"? } | null}. Set activity: null to clear. Presence/activity is cleared on gateway disconnect — re-send after reconnect if needed.
Public user profile (includes visible activity when the user is online and Activity is enabled).
POST/DELETE /api/v1/servers/:id/timeouts...), not under /oauth2/bot. Bots with moderator permissions (or their admin bot role) can call those with the bot token.
Events Reference
Join rooms with server:join / channel:join to receive server- and channel-scoped events.
Events Your Bot Receives
| Event | When | Payload (summary) |
|---|---|---|
interaction:create | Slash command or embed component | See Slash Commands / Embeds sections |
message:new | New channel message | Full message + author fields, embeds?, attachments? |
message:update | Message edited | Hydrated message |
message:deleted | Message deleted | {channelId, messageId} |
message:reaction | Reaction add/remove | {channelId, messageId, emoji, userId, action} |
message:pin | Pin toggled | {channelId, messageId, pinned} |
member:joined | Member or bot joins | {serverId, userId, username, avatar} |
member:left | Leave / kick / ban remove | {serverId, userId} |
member:banned | Member banned | {serverId, userId} |
member:timeout | Member timed out | {serverId, userId, expiresAt} |
member:role-updated | Built-in role changed | {serverId, userId, role} |
member:roles-updated | Custom role assign/unassign | {serverId, userId} |
role:created / updated / deleted | Role CRUD | Role object or roleId |
channel:created / updated / deleted | Channel CRUD | Channel payload |
channel:permissions-updated | Channel override changed | Override or reset flag |
category:permissions-updated | Category override changed | Override or reset flag |
user:status | Presence change | {userId, status, customStatus?} |
typing:start / typing:stop | Typing in channel | {userId, username?, channelId} |
dm:new | New DM | DM message object |
dm:reaction | DM reaction | Reaction payload |
dm:typing:start / stop | DM typing | {userId, ...} |
voice:existing | After voice join | Array of participants |
voice:user-joined / voice:user-left | Voice membership | Participant / {socketId} |
voice:state-update | Mute/deafen/video/etc. | State fields |
voice:speaking | Speaking indicator | {socketId, speaking} |
voice:admin-mute-update / voice:admin-deafen-update | Server mute/deafen | Target + flags |
voice:force-leave / force-disconnect / force-move | Moderation / multi-device | Reason / target channel |
bot:audio-data | PCM from another bot | {pcm, sampleRate, channels, botUserId} |
Events Your Bot Can Emit
| Event | Payload | Description |
|---|---|---|
interaction:response | {interaction_id, channel_id, content?, embeds?} | Reply to interaction (ack: {ok, message_id, message}) |
bot:send_message | {channel_id, content?, embeds?} | Send channel message |
bot:edit_message | {message_id, channel_id?, content?, embeds?} | Edit own message |
bot:delete_message | {message_id, channel_id?} | Delete own message |
server:join / server:leave | serverId | Subscribe to server room |
channel:join / channel:leave | channelId | Subscribe to channel room |
voice:join | {channelId} | Join voice |
voice:leave | (none) | Leave voice |
bot:voice-query | {userId, serverId} | Where is this user in voice? |
bot:voice-participants | {channelId} | List voice participants |
bot:audio-data | {channelId, pcm, sampleRate?, channels?} | Relay PCM into voice room |
typing:start / typing:stop | channelId | Show typing (humans/bots) |
status:set | {status} | Set presence |
REST API Overview
All REST endpoints use the base URL https://ggbro.app/api and require Bearer token authentication in the Authorization header (except public invite/stats/emoji file where noted).
Bots typically use the Bot REST paths under /api/v1/oauth2/bot/... plus shared server/channel/message routes when they have membership and permissions.
Servers
List all servers the authenticated account is a member of.
Get server details (name, icon, banner, description, member count).
Upload server icon (multipart field icon). PNG/JPG/WEBP/GIF (animated), max 8MB. Original extension is kept so GIFs animate. Requires manage-server.
Upload server banner (multipart field banner). PNG/JPG/WEBP/GIF (animated), max 10MB. Original extension is kept. Requires manage-server.
Reposition banner. Body: {"banner_offset": 0-100}.
List server members with roles, avatars, statuses.
List server custom GIFs/stickers.
Upload a custom GIF/sticker (multipart).
Delete a server GIF/sticker.
Mute a server for the current user. Body: {"duration": 3600}
Unmute a server for the current user.
List servers muted by the current user.
Audit Log API
Requires admin / ADMINISTRATOR (or MANAGE_SERVER) permission on the bot's role. New bots no longer get Administrator by default — grant it explicitly in the Developer Portal if needed.
Query: ?limit=50 (max 100), ?before=<unix_timestamp> for pagination.
JSON Response{ "entries": [{ "id": "...", "server_id": "...", "actor_id": "moderator-or-user-uuid", "actor_username": "ModName", "actor_avatar": "/uploads/...", "action": "ROLE_ASSIGN", "target_type": "member", "target_id": "member-user-uuid", "target_username": "Victim", "changes": { "role": "Member", "roleId": "role-uuid", "username": "Victim", "userId": "member-user-uuid" }, "created_at": 1710000000 }], "hasMore": false }
target_username is resolved from target_id when target_type is member (including older entries that only stored role fields).
Recorded actions
| Action | Notes |
|---|---|
MEMBER_JOIN | Human or bot join; may include invite metadata |
MEMBER_LEAVE | Voluntary leave |
MEMBER_KICK | changes.moderator + target username |
MEMBER_BAN / MEMBER_UNBAN | Ban includes reason + moderator |
MEMBER_TIMEOUT / MEMBER_TIMEOUT_REMOVE | Duration, reason, moderator |
MEMBER_ROLE_UPDATE | Built-in role change (oldRole/newRole) + member username/userId |
BOT_ADD | Bot invited; actor is inviter, target is bot user |
VOICE_JOIN / VOICE_LEAVE | Channel target |
VOICE_SERVER_MUTE / UNMUTE / DEAFEN / UNDEAFEN | Voice moderation |
VOICE_DISCONNECT / VOICE_MOVE | Disconnect / move member |
CHANNEL_CREATE / UPDATE / DELETE / DUPLICATE / REORDER | Channels |
CATEGORY_CREATE / DELETE | Categories |
ROLE_CREATE / UPDATE / DELETE | Role definition changes |
ROLE_ASSIGN / ROLE_UNASSIGN | Custom role add/remove. target_id = member. changes: { role, roleId, username, userId } |
MESSAGE_EDIT / DELETE / PIN / UNPIN | Messages |
INVITE_CREATE / DELETE / REGENERATE | Invites |
SERVER_CREATE / UPDATE / DELETE / ICON / BANNER / VANITY | Server settings |
SERVER_EMOJI_UPLOAD / DELETE / RENAME | Emojis |
SERVER_SOUNDBOARD_UPLOAD / UPDATE / DELETE | Soundboard |
Members Moderation
Shared server moderation routes (bot token works when the bot is a member with the right permissions). Prefer Bot REST kick/ban when available; timeouts use these routes.
Kick. Requires moderator or KICK_MEMBERS. Emits member:left. Audit: MEMBER_KICK.
Body: {"targetUserId": "...", "reason": "..."}. Emits member:left + member:banned. Audit: MEMBER_BAN.
Unban. Audit: MEMBER_UNBAN.
List bans.
Body: {"targetUserId": "...", "duration": 300, "reason": "..."}. Duration in seconds (min 60, max 28 days). Emits member:timeout. Response includes expiresAt.
Remove timeout. Audit: MEMBER_TIMEOUT_REMOVE.
Python Timeout via shared RESTrequests.post( f"{API_URL}/api/v1/servers/{server_id}/timeouts", headers={"Authorization": f"Bearer {BOT_TOKEN}"}, json={"targetUserId": user_id, "duration": 600, "reason": "spam"}, )
Bot-specific aliases: DELETE /api/v1/oauth2/bot/servers/:serverId/members/:userId (kick), POST .../bans with {"user_id"}, DELETE .../bans/:userId.
Channels
List channels (permission-filtered) + categories.
Get channel by ID.
Create channel. Body: {"name": "general", "type": "text"|"voice", "category_id": "..."}
Duplicate a channel (copies permission overrides).
Update name/topic/slowmode/etc.
Delete channel.
Reorder channels.
Create category.
Rename category.
Delete category (channels uncategorized).
Reorder categories. Body: {"order": ["catId1", "catId2"]}
Channel & Category Permission Overrides
allow and deny are bitmasks using the permissions bitfield. Deny wins over allow for a given bit.
List channel overrides.
Body: {"allow": 4096, "deny": 0}. Emits channel:permissions-updated.
Reset override for role.
List category overrides.
Set category override. Emits category:permissions-updated.
Reset category override.
JSON Hide channel from @everyone, allow a staff role// PUT /api/v1/channels/:channelId/permissions/:everyoneRoleId { "allow": 0, "deny": 2048 } // deny VIEW_CHANNEL // PUT /api/v1/channels/:channelId/permissions/:staffRoleId { "allow": 2048, "deny": 0 }
Messages
Fetch messages (50 per page). Use ?before=messageId for older messages.
Get a single message by ID.
Send a message with optional file attachments (multipart form data). Field: content for text, files for attachments.
Edit a message. Body: {"content": "..."}. Author or manage-messages.
Delete a message. Must be the message author or have manage_messages permission.
Search messages in a channel. Query params: ?q=search+term&limit=25
Get attachment metadata (filename, size, mime type, url).
Mark channel as read (for unread indicators).
Members & Users
Get the current authenticated user/bot. Returns: id, username, email, avatar, banner, bio, status, is_bot, created_at.
Get a user by ID. Returns: username, avatar, status, banner, bio, is_bot, created_at.
Get a user's full profile including mutual friends and mutual servers.
Update the current user's profile. Body: {"username": "...", "bio": "..."} (bio max 500 chars)
Upload a new avatar (multipart field avatar). PNG/JPG/WEBP/GIF (animated), max 2MB. Original extension is kept so GIFs animate.
Upload a new banner (multipart field banner). PNG/JPG/WEBP/GIF (animated), max 5MB. Original extension is kept.
Change password. Body: {"currentPassword": "...", "newPassword": "..."}
Change a member's role in a server. Body: {"role": "admin"}
Friends
Friend routes authenticate as the calling account. Bots can call them with a bot token, but friendship UX is primarily human-oriented. There is no separate bot-friends API.
List friends (accepted).
Search users. Query: ?q=username
Body: {"username": "..."}
Accept pending request.
Remove friend or reject pending.
Friends currently in voice.
Direct Messages
Prefer POST /api/v1/oauth2/bot/dm/:targetUserId for bot DMs (requires a shared server). The routes below are the general DM API.
List DM channels.
Open/create DM channel.
Fetch DMs. Supports ?before= / ?around=
Send DM. Body: {"content": "Hello!"}
DM with attachments (multipart).
Edit DM message.
Delete DM message.
Mark read.
Search DM history. ?q=
Group DMs
Group DMs are under /api/v1/dm/groups. They are user/group features; bots are not specially provisioned into group DMs, but a bot account that is a group member can use the same routes.
List group DMs.
Create group. Body includes member IDs / name.
Update group metadata.
Add members.
Remove member.
Fetch messages.
Send message.
Upload attachments.
Edit message.
Delete message.
Leave group.
Delete group (owner).
Roles
List roles.
Get one role.
Create. Body: {"name": "Moderator", "color": "#5865F2", "permissions": 0, "hoist"?: false}
Update name/color/permissions/hoist.
Delete role.
Reorder. Body: array of {"id", "position"}
Assign. Body: {"targetUserId": "...", "roleId": "..."}. Emits member:roles-updated.
Unassign role.
Roles on a member.
Bot shortcuts: POST/DELETE /api/v1/oauth2/bot/servers/:serverId/members/:userId/roles/:roleId and GET .../roles.
Permissions Bitfield
| Bit | Name | Value |
|---|---|---|
| 0 | MANAGE_CHANNELS | 1 |
| 1 | MANAGE_ROLES | 2 |
| 2 | KICK_MEMBERS | 4 |
| 3 | BAN_MEMBERS | 8 |
| 4 | MANAGE_MESSAGES | 16 |
| 5 | MANAGE_SERVER | 32 |
| 6 | MUTE_MEMBERS | 64 |
| 7 | DEAFEN_MEMBERS | 128 |
| 8 | MOVE_MEMBERS | 256 |
| 9 | ADMINISTRATOR | 512 |
| 10 | ADD_REACTIONS | 1024 |
| 11 | VIEW_CHANNEL | 2048 |
| 12 | SEND_MESSAGES | 4096 |
| 13 | CONNECT | 8192 |
| 14 | MANAGE_REACTIONS | 16384 |
| 15 | SPEAK | 32768 |
| 16 | ATTACH_FILES | 65536 |
| 17 | EMBED_LINKS | 131072 |
| 18 | READ_MESSAGE_HISTORY | 262144 |
| 19 | MENTION_EVERYONE | 524288 |
| 20 | USE_EXTERNAL_EMOJIS | 1048576 |
| 21 | STREAM | 2097152 |
| 22 | USE_SOUNDBOARD | 4194304 |
| 23 | USE_APPLICATION_COMMANDS | 8388608 |
Invite URL: /invite?bot=<botUserId>&permissions=<bitfield>. Authorizing grants a managed role named after the bot with exactly those permissions (Administrator may be requested from the Developer Portal). Defaults in the portal only update the invite link — not existing servers. Bot owners cannot remotely edit a server's bot role; server admins do that in Server Settings. Kick/ban/leave keeps the role until an admin deletes it. New bots default to least-privilege 8895488 (not Administrator). Combine flags with bitwise OR, e.g. KICK_MEMBERS | BAN_MEMBERS | MUTE_MEMBERS = 4|8|64 = 76. Channel overrides use the same bits; deny wins. Uploads/GIFs need ATTACH_FILES; slash commands need USE_APPLICATION_COMMANDS; camera/screen need STREAM; soundboard needs USE_SOUNDBOARD. Bot moderation REST routes enforce the bot's role bits (missing Kick → 403 on kick, etc.).
Emojis
Custom emojis allow servers to upload and use custom image emojis in messages and reactions. Emojis use the format <:name:id> in message content.
List all custom emojis in a server. Requires server membership.
Response: { "emojis": [{ "id", "server_id", "name", "file_path", "original_name", "mime_type", "size", "uploaded_by", "created_at", "uploaded_by_username" }], "canManage": boolean }
List server custom emojis (public, no membership required). Useful for cross-server emoji display.
Upload a custom emoji. Multipart form data with field emoji (image, max 256 KB). Optional name field (max 32 chars, alphanumeric/underscore/hyphen). Requires Manage Server permission. Limit: 50 emojis per server.
Response: { "emoji": { "id", "server_id", "name", "file_path", ... } }
Delete a custom emoji from a server. Requires Manage Server permission.
Response: { "success": true }
List all custom emojis from every server the authenticated user belongs to. Includes server_name on each emoji object.
Get a custom emoji image by ID. Returns a redirect (302) to the image file. Can be used as an <img> src directly.
Using Custom Emojis
To include a custom emoji in a message, use the format: <:emoji_name:emoji_id>
For reactions, pass the same format as the emoji field: "<:emoji_name:emoji_id>"
Standard Unicode emojis can also be used directly (e.g. "👍").
Soundboard
List server soundboard sounds (each includes an icon emoji).
Upload a sound (multipart). Fields: sound (required audio ≤8MB), optional name (max 40), optional icon (Unicode emoji for the voice picker tile; default 🎶). Requires manage-server. Audit: SERVER_SOUNDBOARD_UPLOAD.
Edit a sound. JSON {name?, icon?} or multipart with optional sound (replace audio) plus name/icon. Audit: SERVER_SOUNDBOARD_UPDATE.
Delete a sound. Audit: SERVER_SOUNDBOARD_DELETE.
Catalog of playable sounds across eligible servers (GGBRO Plus/soundboard feature). Each sound has id, name, icon, builtin, file_url.
Play a sound in the caller’s active voice channel.
Reactions & Pins
Add or toggle a reaction on a message. Body: {"emoji": "👍"} for Unicode, or {"emoji": "<:name:id>"} for custom emojis. If you already reacted with this emoji, it will be removed (toggle). Adding a brand-new emoji type requires ADD_REACTIONS permission; adding to an existing emoji type is always allowed.
Remove your reaction from a message.
Get all reactions on a message with user lists.
Get all pinned messages in a channel.
Pin a message to a channel.
Unpin a message from a channel.
Get pinned messages in a DM conversation.
Pin a DM message.
Unpin a DM message.
Invites & Bot Invites
Create invite. Body: {"max_uses"?, "expires_in"?}
List invites.
Delete invite.
Invite use history.
Regenerate primary invite code.
Join via invite code.
Public bot invite card: name, avatar, description, public_invite, default_permissions, invite_permission_locked, permission_labels, permission_flags.
Authorize bot onto a server. Body: {"permissions": <bitfield>}. Respects public_invite (otherwise owner-only). Creates/updates managed bot role.
Owner adds own bot. Optional body: {"permissions": <bitfield>}.
Body: {"default_permissions": number, "invite_permission_locked"?: boolean} — updates invite defaults only.
Body: {"public_invite": true}
Always 403 — bot owners cannot change server bot roles remotely.
Bot token invite CRUD: /api/v1/oauth2/bot/servers/:serverId/invites (POST/GET/DELETE).
Copying IDs in the App
GGBRO IDs are UUIDs. No separate “Developer Mode” toggle is required — copy actions are always available:
- Channel ID — right-click a channel in the sidebar → Copy Channel ID (also Copy Channel Link).
- Server ID — right-click a server icon in the server rail → Copy Server ID.
- User ID — open a user profile popup (click avatar) and use the copy button, or right-click a member in the member list → Copy User ID.
Use these IDs in REST paths, slash option values, and Socket.IO room joins.
Errors & Rate Limits
Most endpoints return JSON errors:
JSON Error{ "error": "Human readable message" }
| Status | Meaning |
|---|---|
200 / 201 | Success / created |
400 | Invalid body or state |
401 | Missing/invalid token |
403 | Authenticated but forbidden (permissions / not a member) |
404 | Resource missing |
409 | Conflict (e.g. duplicate command name) |
There is no global bot API rate-limit middleware today. Auth flows (e.g. password reset) use internal rate buckets. Still: avoid tight polling loops; prefer Socket.IO events. Upload size limits (25 MB / 500 MB) and command caps (50 commands, 25 options) apply as hard limits.
Webhooks
Channel webhooks let integrations post messages into a text channel without a bot user session. A channel may have multiple webhooks. Creating/managing requires the Manage Channels permission. Execute URLs are unauthenticated secrets — treat them like passwords.
Important: POST /api/webhooks/:id/:token must not send an Authorization header. The URL token is the credential. Sending a Bearer JWT will not authorize execute and is unnecessary.
Also available in the Developer Portal → Webhooks, the Message Builder (Discohook-style embeds), and in Edit Channel → Integrations inside the app.
List webhooks for a channel. Auth: user Bearer with Manage Channels. Response includes full url and token for managers (for Copy URL).
Create a webhook. Body: {"name":"My Hook","avatar?":"https://…"}. Returns full token and url.
Same as list (alias).
Same as create (alias).
Rename or set avatar. Body: {"name?":"…","avatar?":"…"}. Auth required.
Regenerate token. Returns new full token and url. Auth required. Do not confuse with execute (/:id/:token).
Delete a webhook. Auth required.
Fetch webhook metadata with the execute token (name, channel, url). No auth header.
Execute webhook (no Authorization header). Body: {"content?":"…","embeds?":[],"username?":"override","avatar_url?":"…"}. Requires content or embeds. Discord-style embed shapes (image.url, integer color) are accepted and normalized. Query ?wait=true returns the created message JSON; otherwise 204. Wrong id/token → 401 {"error":"Invalid webhook"}.
bash Execute webhookcurl -X POST "https://ggbro.app/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN?wait=true" \ -H "Content-Type: application/json" \ -d '{"content":"Hello from a webhook!","username":"CI Bot"}'
Messages appear with a WEBHOOK badge and the webhook display name/avatar. Rate limit: ~30 executes per minute per webhook.
Stats
Public platform statistics: {"users", "bots", "servers", "messages"}
Full Example: Ping Pong Bot (Python)
A complete, copy-paste-ready bot. Responds to /ping with "Pong!", /echo with the user's message, and /hello with a rich embed.
bash Install Dependenciespip install "python-socketio[client]" requests
Python ping_bot.py — Complete Working Example""" ggbro Ping Pong Bot (Python) A complete working example for the ggbro platform. ggbro uses Socket.IO v4 for real-time communication. Install: pip install "python-socketio[client]" requests """ import socketio import requests # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" # Get from Developer Portal API_URL = "https://ggbro.app" # --- Socket.IO Client --- sio = socketio.Client(reconnection=True, reconnection_delay=1) # --- Helper: Reply to a slash command --- def reply(ctx, content, embeds=None): """Respond to an interaction (slash command).""" payload = { "interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content } if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) # --- Helper: Send a message to a channel --- def send_message(channel_id, content, embeds=None): """Send a message to any channel.""" payload = {"channel_id": channel_id, "content": content} if embeds: payload["embeds"] = embeds sio.emit("bot:send_message", payload) # --- Events --- @sio.event def connect(): print("[+] Connected to ggbro!") @sio.event def disconnect(): print("[!] Disconnected from ggbro") # --- Slash Command Handler --- @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() args = data.get("args", "") user = data["user"]["username"] print(f"[cmd] /{cmd} from {user}") if cmd == "ping": reply(data, "Pong! 🏓") elif cmd == "echo": message = data.get("options", {}).get("message") or args if message: reply(data, f"📢 {message}") else: reply(data, "❌ Please provide a message!") elif cmd == "hello": # Reply with an embed reply(data, "", embeds=[{ "title": f"Hello, {user}!", "description": "Welcome! I'm a bot running on ggbro.", "color": "#23a55a" }]) # --- Register Commands --- def register_commands(): commands = [ {"name": "ping", "description": "Check if the bot is alive", "options": []}, {"name": "echo", "description": "Repeat your message", "options": [ {"name": "message", "description": "Text to echo", "required": True} ]}, {"name": "hello", "description": "Say hello with an embed", "options": []}, ] r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=commands, headers={"Authorization": f"Bearer {BOT_TOKEN}"} ) if r.ok: print("[+] Commands registered: /ping, /echo, /hello") else: print(f"[!] Failed to register commands: {r.status_code} {r.text}") # --- Main --- if __name__ == "__main__": print("Starting ggbro Ping Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()
Full Example: Ping Pong Bot (JavaScript / Node.js)
bash Install Dependenciesnpm install socket.io-client axios
JavaScript ping_bot.js — Complete Working Example/** * ggbro Ping Pong Bot (Node.js) * A complete working example for the ggbro platform. * ggbro uses Socket.IO v4 for real-time communication. * * Install: npm install socket.io-client axios */ const { io } = require("socket.io-client"); const axios = require("axios"); // --- Configuration --- const BOT_TOKEN = "YOUR_BOT_TOKEN"; // Get from Developer Portal const API_URL = "https://ggbro.app"; // --- Socket.IO Client --- const socket = io(API_URL, { auth: { token: BOT_TOKEN }, reconnection: true, reconnectionDelay: 1000 }); // --- Helper: Reply to a slash command --- function reply(ctx, content, embeds) { const payload = { interaction_id: ctx.id, channel_id: ctx.channel_id, content }; if (embeds) payload.embeds = embeds; socket.emit("interaction:response", payload); } // --- Helper: Send a message to a channel --- function sendMessage(channelId, content, embeds) { const payload = { channel_id: channelId, content }; if (embeds) payload.embeds = embeds; socket.emit("bot:send_message", payload); } // --- Events --- socket.on("connect", () => console.log("[+] Connected to ggbro!")); socket.on("disconnect", () => console.log("[!] Disconnected")); // --- Slash Command Handler --- socket.on("interaction:create", (data) => { const cmd = (data.command || "").toLowerCase(); const args = data.args || ""; const user = data.user.username; console.log(`[cmd] /${cmd} from ${user}`); if (cmd === "ping") { reply(data, "Pong! 🏓"); } else if (cmd === "echo") { const message = (data.options && data.options.message) || args; reply(data, message ? `📢 ${message}` : "❌ Please provide a message!"); } else if (cmd === "hello") { reply(data, "", [{ title: `Hello, ${user}!`, description: "Welcome! I'm a bot running on ggbro.", color: "#23a55a" }]); } }); // --- Register Commands --- async function registerCommands() { try { await axios.post(`${API_URL}/api/v1/oauth2/commands`, [ { name: "ping", description: "Check if the bot is alive", options: [] }, { name: "echo", description: "Repeat your message", options: [ { name: "message", description: "Text to echo", required: true } ]}, { name: "hello", description: "Say hello with an embed", options: [] }, ], { headers: { Authorization: `Bearer ${BOT_TOKEN}` } }); console.log("[+] Commands registered: /ping, /echo, /hello"); } catch (err) { console.error("[!] Failed to register:", err.response?.data || err.message); } } // --- Start --- registerCommands();
Full Example: /serverinfo with Embeds (Python)
A complete bot that responds to /serverinfo with a rich embed containing server name, owner, member count, and creation date:
Python serverinfo_bot.py — Complete Working Example""" ggbro Server Info Bot Responds to /serverinfo with a rich embed showing server details. Uses Socket.IO for real-time + REST API for data fetching. Install: pip install "python-socketio[client]" requests """ import socketio import requests from datetime import datetime # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" HEADERS = {"Authorization": f"Bearer {BOT_TOKEN}"} sio = socketio.Client(reconnection=True) def reply(ctx, content="", embeds=None): payload = { "interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content, } if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) @sio.event def connect(): print("[+] Connected to ggbro!") @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() if cmd == "serverinfo": server_id = data["server_id"] user = data["user"]["username"] # Fetch server details from REST API r = requests.get(f"{API_URL}/api/v1/servers/{server_id}", headers=HEADERS) if not r.ok: reply(data, "❌ Failed to fetch server info") return server = r.json() # Fetch members mr = requests.get(f"{API_URL}/api/v1/servers/{server_id}/members", headers=HEADERS) members = mr.json().get("members", []) if mr.ok else [] # Find the owner owner = next((m for m in members if m.get("role") == "owner"), None) owner_name = owner["username"] if owner else "Unknown" # Build the embed icon_url = f"{API_URL}/uploads/icons/{server['icon']}" if server.get("icon") else None reply(data, "", embeds=[{ "title": server.get("name", "Unknown Server"), "description": server.get("description") or "No description set.", "color": "#5865F2", "thumbnail": icon_url, "fields": [ {"name": "👑 Owner", "value": owner_name, "inline": True}, {"name": "👥 Members", "value": str(len(members)), "inline": True}, {"name": "🆔 Server ID", "value": f"`{server_id}`"}, ], "footer": {"text": f"Requested by {user}"}, "timestamp": datetime.utcnow().isoformat(), }]) # --- Register Commands --- def register_commands(): r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=[{"name": "serverinfo", "description": "Show server details", "options": []}], headers=HEADERS ) print("[+] /serverinfo registered" if r.ok else f"[!] Failed: {r.text}") if __name__ == "__main__": print("Starting Server Info Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()
Full Example: Welcome Bot (Python)
Greets new members with a rich embed when they join a server:
Python welcome_bot.py — Complete Working Example""" ggbro Welcome Bot Greets new members with a rich embed when they join. Install: pip install "python-socketio[client]" requests """ import socketio import requests from datetime import datetime # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" HEADERS = {"Authorization": f"Bearer {BOT_TOKEN}"} # Set your welcome channel ID per server WELCOME_CHANNELS = { "your-server-id": "your-welcome-channel-id", } sio = socketio.Client(reconnection=True) def send_message(channel_id, content="", embeds=None): payload = {"channel_id": channel_id, "content": content} if embeds: payload["embeds"] = embeds sio.emit("bot:send_message", payload) def reply(ctx, content="", embeds=None): payload = {"interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content} if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) @sio.event def connect(): print("[+] Welcome Bot connected!") # Join all configured server rooms for server_id in WELCOME_CHANNELS: sio.emit("server:join", server_id) # --- Welcome new members with an embed --- @sio.on("member:joined") def on_member_join(data): server_id = data.get("serverId") username = data.get("username", "Someone") avatar = data.get("avatar") channel_id = WELCOME_CHANNELS.get(server_id) if not channel_id: return avatar_url = f"{API_URL}/uploads/avatars/{avatar}" if avatar else None send_message(channel_id, "", embeds=[{ "title": f"Welcome, {username}! 👋", "description": "We're glad to have you here. Check out the channels and say hi!", "color": "#23a55a", "thumbnail": avatar_url, "timestamp": datetime.utcnow().isoformat(), }]) # --- Slash command to set welcome channel --- @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() if cmd == "setwelcome": channel_id = (data.get("options", {}).get("channel_id") or data.get("args", "")).strip() if not channel_id: reply(data, "❌ Please provide a channel ID!") return WELCOME_CHANNELS[data["server_id"]] = channel_id reply(data, "", embeds=[{ "title": "✅ Welcome Channel Set", "description": f"New members will be greeted in this channel.", "color": "#23a55a" }]) # --- Register Commands --- def register_commands(): r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=[ {"name": "setwelcome", "description": "Set the welcome channel", "options": [ {"name": "channel_id", "description": "Channel ID for welcome messages", "required": True} ]} ], headers=HEADERS ) print("[+] /setwelcome registered" if r.ok else f"[!] Failed: {r.text}") if __name__ == "__main__": print("Starting Welcome Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()