# Introduction > Build slash commands, auto replies, games and server tools for your Discord server. No hosting, no setup. Custom Commands lets you add your own commands to your Discord server. You make them on the dashboard, and the bot runs them in your server. A command can be as small as a one-line reply, or as big as an economy with coins, a shop and a leaderboard. ## What you can build - **Replies and info commands.** `/rules`, `/socials`, `/serverinfo`. - **Fun commands.** `/8ball`, `/coinflip`, trivia, games with buttons. - **Server tools.** Polls, giveaways, reminders, ticket panels, role menus. - **Systems that remember.** Levels and XP, coins, warnings, birthdays. - **Automatic actions.** Welcome messages, auto roles, replies to keywords, scheduled posts. ## Two ways to build **No code.** Type a reply, pick an embed, add buttons. Most commands need nothing more. Use variables like `{user.mention}` to make the reply personal. **With code.** When you need logic (random picks, saved data, buttons that do things), add a few lines of Python to the command. These docs list every function you can use, with examples. > **Tip:** Not sure how to write it? Ask **Slushy**, the AI agent on the dashboard. Describe the command in plain words and Slushy builds it for you. ## How these docs are organised | Section | Read it when | |---|---| | [Getting started](https://ccbot.app/docs/getting-started/first-command) | You are new. Make your first command in two minutes. | | [Guides](https://ccbot.app/docs/guides/welcome-message) | You want to build a full feature: levels, economy, tickets, giveaways. | | [Concepts](https://ccbot.app/docs/concepts/buttons-and-menus) | You want to understand buttons, forms, saved data and limits. | | [API reference](https://ccbot.app/docs/reference/response) | You write code and need the exact name and arguments of a function. | | [Examples](https://ccbot.app/docs/examples/8ball) | You want a small command to copy and change. | ## Using an AI assistant? Give it [ccbot.app/llms-full.txt](https://ccbot.app/llms-full.txt). That file holds all of these docs as plain text, so the assistant can write commands that work. Every page also has a **Copy as Markdown** button at the top. ## Need help? Join the [support server](https://ccbot.app/support). Ask in the help channel and share what you tried. Source: https://ccbot.app/docs --- # Your first command > Make a /hello command that greets whoever runs it. Takes two minutes. You will make `/hello`. When someone runs it, the bot replies: > Hello @Alex, welcome to My Server! ## Before you start 1. [Add the bot](https://ccbot.app/invite) to your server. 2. Open the [dashboard](https://ccbot.app/dashboard) and pick your server. You need the **Manage Server** permission in that server. ## 1. Create the command On the dashboard, open **Create β†’ Slash Command**. Fill in **Basic details**: | Field | Value | |---|---| | Command Name | `hello` | | Description | `Say hello` | The description shows under the command name in Discord's command menu. Keep it short. > **Note:** Names use lowercase letters, numbers, `-` and `_`. No spaces. Discord rule. ## 2. Write the reply Scroll to **Response**. In **Content / Reply Message**, type: ```text Hello {user.mention}, welcome to {server.name}! ``` Text inside `{ }` is a **variable**. The bot fills it in when the command runs: | Variable | Becomes | |---|---| | `{user.mention}` | A ping of the person who ran the command | | `{user.display_name}` | Their name, without a ping | | `{server.name}` | Your server's name | | `{server.member_count}` | How many members the server has | | `{channel.mention}` | The channel the command ran in | ## 3. Save Press **Save**. The command is now on the dashboard. Discord needs to learn about new commands. The dashboard does this for you. If you see **Sync required** at the top, press **Sync now**. ## 4. Try it In your server, type `/hello` and press Enter. > **Command not showing?:** Discord can take up to a minute. Press `Ctrl + R` to reload Discord. Still missing? Check the bot has the **Use Application Commands** permission in that channel. ## Make it better ### Only the user sees the reply Under **Behavior**, turn on **Only the user sees the reply**. Good for help commands, so the chat stays clean. ### Add an option Under **Arguments**, add a **Member** argument named `friend`. Then change the reply: ```text {user.mention} says hello to {friend.mention}! ``` Every argument becomes a variable with the same name. ### Use an embed Make an embed under **Embeds**, then pick it in **Reply Embed**. Variables work in embeds too. ### Add some code Want a random pick? Open **Pre-hook** and add: ```python greeting = random(["Hello", "Hi", "Hey", "Welcome"]) ``` Then use it in the reply: ```text {greeting} {user.mention}! ``` The pre-hook runs before the reply is sent. Anything you make there can go in the reply with `{ }`. Source: https://ccbot.app/docs/getting-started/first-command --- # How a command runs > The order things happen in when someone runs your command, and how to stop early or show an error. A command has three parts. They run in this order every time: 1. **Pre-hook.** Your code that runs first. 2. **Response.** The reply the bot sends. 3. **Post-hook.** Your code that runs after the reply is sent. You do not need all three. Most commands only have a Response. ## The full order ```text 1. Pre-hook runs 2. { } variables in the Response are filled in 3. The reply is sent 4. "response" becomes available 5. If you used set_view, the bot waits for the buttons to finish 6. Post-hook runs ``` ## Pre-hook Use the pre-hook to get data ready for the reply. Anything you make here can go in the reply with `{ }`. ```python roll = randomNumber(1, 6) ``` Response: ```text {user.mention} rolled a {roll}! ``` You can also build the whole reply from code. See [Response functions](https://ccbot.app/docs/reference/response). ```python set_content(f"{user.mention} rolled a {randomNumber(1, 6)}!") ``` ## Post-hook The post-hook runs after the reply is in the channel. Use it for work that should happen after, like a log message or reactions on the reply. In the post-hook you can read `response`. It is the message the bot just sent. ```python add_reactions(response, "πŸ‘", "πŸ‘Ž") ``` > **Note:** `response` does not exist in the pre-hook. The reply is not sent yet. Pre-hook and post-hook share everything. A variable you make in the pre-hook is still there in the post-hook. They also share one set of [limits](https://ccbot.app/docs/concepts/limits). ## Stop with an error Call `set_error("text")` to stop the command. The user sees your text as the error. Nothing below that line runs. ```python if user.is_bot: set_error("Bots cannot use this command.") set_content("Welcome!") ``` ## Stop without a reply Call `end()` to send no reply and skip the post-hook. > **end() does not stop the pre-hook:** Every line after `end()` in the pre-hook still runs. The bot only checks `end()` after the pre-hook is done. To stop right away, use `set_error(...)`. ```python if channel.name != "bot-commands": end() ``` ## When something breaks If your code has a mistake, the bot catches it. The error goes to the **Error Logs** page on the dashboard, under **Logs**. The user sees that something went wrong. In the post-hook you can check what happened in the pre-hook: | Name | True when | |---|---| | `pre_hook_failed` | The pre-hook raised an error. | | `pre_hook_cooldown` | The command was on cooldown. | | `pre_hook_error` | Holds the error from the pre-hook. | You do not need `try` / `except` around everything. Use it only when you have a real fallback. ## Events are different Events have only one code box, and no Response. The code must send its own messages with `send_message`. See [Events](https://ccbot.app/docs/getting-started/events). Source: https://ccbot.app/docs/getting-started/how-commands-run --- # Variables in replies > Put names, numbers and server info in a reply with curly brackets. A **variable** is a name in curly brackets, like `{user.mention}`. When the command runs, the bot puts the real value in its place. ```text Hello {user.mention}! There are {server.member_count} members here. ``` Becomes: ```text Hello @Alex! There are 1,204 members here. ``` Variables work in the **Content / Reply Message** box and in embeds used as the reply. ## The person who ran the command | Variable | What it shows | |---|---| | `{user.mention}` | A ping, like @Alex | | `{user.display_name}` | Their name in this server | | `{user.username}` | Their Discord username | | `{user.id}` | Their user ID | | `{user.avatar_url}` | A link to their avatar | | `{user.top_role}` | Their highest role | | `{user.unix_joined_at}` | When they joined, as a number for timestamps | ## The server | Variable | What it shows | |---|---| | `{server.name}` | The server name | | `{server.member_count}` | How many members | | `{server.icon_url}` | A link to the server icon | | `{server.owner.mention}` | A ping of the owner | | `{server.premium_subscription_count}` | Number of boosts | ## The channel | Variable | What it shows | |---|---| | `{channel.mention}` | A link to the channel, like #general | | `{channel.name}` | The channel name | | `{channel.id}` | The channel ID | For every field, see [Objects](https://ccbot.app/docs/reference/objects). ## Arguments Every argument you add to a command becomes a variable with the same name. Add a **Member** argument named `target`. Then: ```text {user.mention} gave a cookie to {target.mention}! ``` A **Text** argument named `reason` is plain text: ```text Reason: {reason} ``` > **Watch out:** An argument hides anything with the same name. An argument named `user` replaces the person who ran the command. Pick a different name, like `member` or `target`. ## Values from your code Anything you make in the **Pre-hook** can go in the reply. ```python coins = int(member_var("coins", 0)) ``` ```text You have {coins} coins. ``` You can also do small sums inside the brackets. With an **Integer** argument named `age`: ```text Next year you will be {age + 1}. ``` If something inside the brackets fails, it shows as `None` instead of breaking the command. ## Saved values Some values should stay after the command ends, like coins or a counter. Save them with server or member variables: ```python update_member_var("coins", 100) ``` See [Saving data](https://ccbot.app/docs/concepts/saving-data). ## In events Events have no reply box, so curly-bracket variables do not work there. Build the text in code with an f-string instead: ```python send_message("welcome", f"Welcome {user.mention} to {server.name}!") ``` Source: https://ccbot.app/docs/getting-started/variables --- # Text commands > Commands that run when someone types a prefix and a name in chat, like ..rules. A **text command** runs when someone types a message that starts with your **prefix** and the command name. ```text ..rules ``` The default prefix is `..` (two dots). Change it on the **Settings** page under **Command prefix**. It can be 1 to 10 characters. ## Slash or text? | | Slash command | Text command | |---|---|---| | How to run | Type `/` and pick it from the menu | Type the prefix and the name | | Shows in Discord's command menu | Yes | No | | Arguments | Discord shows a box for each one | Typed after the name, split by spaces | | Reply only the user can see | Yes | No | | Buttons, embeds, code | Yes | Yes | Pick **slash** for most commands. People can find them in the menu, and Discord checks the arguments for you. Pick **text** for quick shortcuts, or when you want commands that do not show in the menu. ## Make one 1. Open **Create β†’ Text Command**. 2. Set **Command Name**, for example `rules`. 3. Write the reply in **Response**. 4. Press **Save**. Text commands do not need a sync. They work as soon as you save. ## Arguments Arguments are typed after the name, split by spaces: ```text ..give @Alex 50 ``` With two arguments, a **Member** named `target` and an **Integer** named `amount`, the bot reads `@Alex` as `target` and `50` as `amount`. Rules for typing arguments: - **Spaces split arguments.** Wrap text in double quotes to keep spaces: `..say "hello there"`. - **The last argument takes the rest.** If there are more words than arguments, the extra words go into the last one. Good for a reason or a message. - **Members, roles and channels** are typed as mentions: `@Alex`, `@Mods`, `#general`. - **Boolean** arguments accept `yes`, `true` or `1` as true. Each argument is a variable with its name, the same as in slash commands: ```text {user.mention} gave {amount} coins to {target.mention}. ``` ## In code Text commands have a `message` variable. It is the message that ran the command. There is no `interaction`. ```python add_reactions(message, "βœ…") ``` Everything else works the same as a slash command: pre-hook, Response, post-hook, buttons and embeds. See [How a command runs](https://ccbot.app/docs/getting-started/how-commands-run). > **Note:** The command name must match exactly. `..Rules` does not run `rules`. Source: https://ccbot.app/docs/getting-started/text-commands --- # Events > Run code on its own when something happens in your server, like a member joining or a button click. A command runs when someone types it. An **event** runs on its own when something happens in your server. A member joins, a reaction is added, a button is clicked. ## Make an event 1. Open **Create β†’ Event**. 2. Give it a **Name**. Only you see this. 3. Pick a **Type**. This is what makes it run. 4. Write the code in the **Code** box. 5. Press **Save**. An event has one code box. It has no Response box and no post-hook. To say something, the code sends its own message with `send_message`. > **How many events can be on:** Free servers can have **2** events turned on at once. Starter has 5. Basic and Advanced have no limit. ## Event types | Type | Runs when | Extra field | |---|---|---| | Member Join | A member joins the server | | | Member Leave | A member leaves or is kicked | | | Member Update | A member's nickname, roles or timeout changes | | | Button Click / Select Menu Change | Someone clicks a button or picks from a menu | Custom ids | | Modal Submit | Someone sends a form | Custom ids | | Autocomplete | Someone types in a command option with suggestions | | | Thread (Create/Delete) | A thread is made, changed or deleted | | | Reaction (Add/Remove) | Someone adds or removes a reaction | Reactions | | MESSAGE (Pro Only) | Someone sends a message. Premium servers only | | | Clock | A timer you set with `schedule_clock` or a job goes off | Custom ids | | Voice Join | A member joins a voice channel | | | Voice Leave | A member leaves a voice channel | | | Voice Change | A member moves to another voice channel | | | Vote (top.gg) | Someone votes for the server on top.gg | | **Custom ids** says which buttons, forms or timers this event listens to. Separate them with commas. For Clock, `remind_*` matches every id that starts with `remind_`. **Reactions** says which emojis this event listens to. Use the emoji, a custom emoji name, or its ID. Each type gets different variables, like `user` or `channel`. See [Event variables](https://ccbot.app/docs/reference/event-variables) for the full list. ## Example: auto role on join Type: **Member Join** ```python add_roles(user, "Member") send_message("welcome", f"Welcome {user.mention}! You are member #{server.member_count}.") ``` `user` is the member who joined. `"Member"` and `"welcome"` are a role name and a channel name. IDs and mentions work too. > **Tip:** For a simple welcome message, you do not need code. Use the **Welcome & Leave** page. See [Welcome message](https://ccbot.app/docs/guides/welcome-message). ## Example: keyword auto reply Type: **MESSAGE (Pro Only)** ```python if user is None or user.is_bot: return text = message.content.lower() if "how do i join" in text: send_message(channel, "Read the steps in #how-to-join.", reply=message_id) ``` > **Always skip bots first:** A message event also runs for the bot's own messages. Without the first two lines, the bot can reply to its own reply, again and again. ## Example: log reactions Type: **Reaction (Add/Remove)**. Reactions: `⭐` ```python if action == "add": send_message("star-log", f"{user.mention} starred a message in {channel.mention}") ``` `action` is `"add"` or `"remove"`. ## Curly brackets do not work here Events have no reply box, so `{user.mention}` style variables do nothing. Build text with an f-string: `f"Hi {user.mention}"`. The `f` before the quote matters. ## Commands-only functions These only work in commands, not in events: `set_content`, `set_embed`, `set_response_message`, `set_dm_response`, `set_components`, `set_view`, `add_attachment`, `end`. Use `send_message` instead. Source: https://ccbot.app/docs/getting-started/events --- # Using Slushy > Slushy is the AI agent on the dashboard. Describe what you want in plain words and it builds it for you. **Slushy** builds commands, events and embeds for you. You say what you want in plain words. Slushy writes it, and you approve it before anything changes. You can find Slushy in two places: - **AI Agent** in the dashboard menu. A full chat that can work on your whole server. - **AI Builder** on a command or event form. A chat that fills in that one form. ## What Slushy can do - **Make things.** Slash commands, text commands, events and saved embeds. - **Change things.** "Add a 10 second cooldown to /hello." - **Answer questions about your server.** It can read your commands, events, settings and saved data. "Which of my commands are turned off?" - **Copy a design.** Paste a screenshot of an embed you like, and ask for one like it. ## How to ask Say what it should do, and where. ```text Make a /ban command that posts the reason in #mod-log ``` Good requests name three things: 1. **The name.** `/ban`, `/daily`, "a welcome event". 2. **What it does.** "Gives 100 coins once a day." 3. **Where it posts.** "In #mod-log", "only the user sees it". Point at real things in your server while you type: | Type | To pick | |---|---| | `/` | A command | | `#` | A channel | | `@` | A role, event or embed | Picking them from the list is better than typing a name. Slushy then knows exactly which one you mean. ## Approve or reject When Slushy wants to change something, it shows the change first. You can: - **Approve** it. The change is saved. - **Reject with a note.** Say what should change instead, and Slushy tries again. For bigger jobs, Slushy may first show a **Plan**. Press **Approve plan** to let it start. ### Approval mode Pick how careful Slushy should be: | Mode | What happens | |---|---| | **Ask first** | Every change waits for your approval. | | **Auto** | Changes run right away. Deletes still ask. | Start with **Ask first** until you know how Slushy works. ## Fix an error When a command breaks, the error can show a **Fix with Slushy** button. It opens Slushy with the error ready to send. ## Daily limit Slushy has a daily allowance. The bar under the chat shows how much you used today and when it resets. When it runs out, Slushy can use AI credit instead. See **AI & Credit** on the dashboard. > **Tip:** Slushy writes the same code these docs explain. Open a command it made and read the **Pre-hook**. It is a quick way to learn. Source: https://ccbot.app/docs/getting-started/slushy --- # Welcome messages > Greet new members in a channel or by DM, give them roles, and say goodbye when they leave. No code. You will set up a welcome message like this: > Welcome to My Server, @Alex! You are our 1284th member. You can also send a DM, attach a picture banner, give roles on join, and post a goodbye message. ## What you need - The bot in your server. - A channel for welcome messages, for example `#welcome`. - The bot can see and send messages in that channel. ## 1. Open Welcome & Leave On the dashboard, open **Welcome & Leave**. The page has four tabs: | Tab | What it does | |---|---| | Welcome | The message in a channel, and the DM. | | Leave | The goodbye message. | | Roles | Roles given on join, and roles kept for members who come back. | | Banner | A picture card with the member's avatar and name. | ## 2. Write the channel message In the **Welcome** tab, find **Channel message**. 1. Turn on the switch. 2. In **Channel**, pick your welcome channel. 3. In **Message**, type your text: ```text Welcome to {server}, {user}! You are our {member_count_ordinal} member. ``` 4. In **Embed**, pick a saved embed if you want one. Leave it on **No embed** for text only. Changes save on their own. ## Placeholders Text inside `{ }` is a placeholder. The bot fills it in for each new member. | Placeholder | Becomes | |---|---| | `{user}` | A ping of the new member | | `{user.mention}` | Same as `{user}` | | `{user.name}` | Their display name, no ping | | `{user.id}` | Their user ID | | `{server}` | Your server's name | | `{member_count}` | Member count, for example `1284` | | `{member_count_ordinal}` | Member count as a place, for example `1284th` | | `{date}` | The date they joined | | `{time}` | The time they joined | > **Note:** Welcome messages use only these placeholders. Command variables like `{server.member_count}` do not work here. ## 3. Send a DM too Under **Direct message**, turn on the switch and write a message. The bot sends it to every new member. Some members block DMs from servers. The bot skips them. Nothing breaks. ## 4. Add a banner A banner is a picture with the member's avatar, name and your text on it. 1. Open the **Banner** tab. 2. Pick a design under **Designs**, or change the **Background**. 3. Go back to **Welcome**. 4. Turn on **Attach the banner** in the channel message or the DM. The banner switch is grey until you design a banner. ## 5. Test it Press **Send me a test** under the message. The bot sends the message with your own name in it. To see the real thing, ask a friend to join, or use a second account. ## Give roles on join Open the **Roles** tab. In **Roles on join**, pick up to 25 roles. Every new member gets them. > **Roles not given?:** The bot needs the **Manage Roles** permission. The bot's own role must also be **above** the roles it gives. Drag it higher in **Server Settings β†’ Roles**. **Retain roles on leave** gives members their old roles back when they rejoin. This needs a premium plan. ## Say goodbye Open the **Leave** tab. Turn on **Leave message**, pick a channel and write the text: ```text {user.name} has left {server}. Goodbye! ``` Use `{user.name}`, not `{user}`. A ping to someone who left shows as a broken mention. ## Ideas - Point new members to your rules: `Read <#123456789012345678> first!` Put your real channel ID in. - Want logic, like a different message for bots? Make a **Member Join** event with code. See [Events](https://ccbot.app/docs/getting-started/events). - Track joins with a counter. See [Saving data](https://ccbot.app/docs/concepts/saving-data). Source: https://ccbot.app/docs/guides/welcome-message --- # Role menus > Let members pick their own roles with reactions or buttons. Colours, pings, games, verification. No code. A role menu is a message where members pick roles themselves. They react to an emoji, or click a button, and get the role. Good for: - Colour roles - Ping roles, like "Event pings" or "News" - Game or region roles - A "Verify" button that unlocks the server ## What you need - The bot in your server, with the **Manage Roles** permission. - The roles you want to give, already made in **Server Settings β†’ Roles**. - A channel for the menu, for example `#roles`. > **Most common problem:** The bot can only give roles that are **below** its own role. Open **Server Settings β†’ Roles** and drag the bot's role above every role in your menu. ## Reactions or buttons? | | Reaction roles | Button roles | |---|---|---| | Members | React with an emoji | Click a button | | Looks | Emojis under the message | Buttons with labels and colours | | Modes | Give, take away | Toggle, add only, remove only, unique, verify | | Best for | Quick menus | Clear labels, "pick only one" menus, verification | Both are on the same page. You pick the type when you create the menu. ## 1. Start a new menu On the dashboard, open **Reaction Roles**, then press the create button. Pick **Reaction roles** or **Button roles**. Fill in: | Field | What to put | |---|---| | Name | A name only you see, for example `Colour roles` | | Channel | Where the menu goes | ## 2. Write the message Under **The message**, pick one: - **Write a message.** The bot posts a new message. Type the text in **Message text**. - **Use an existing message.** Pick a message that is already in the channel. Reaction roles only. Example text: ```text Pick your colour! React below. ❀️ Red πŸ’™ Blue πŸ’š Green ``` ## 3a. Add reactions For **Reaction roles**, press **Add your first reaction**. For each row: 1. Pick an emoji. 2. In **Gives**, pick the roles the member gets. 3. In **Takes away**, pick roles to remove (optional). 4. Turn on **Take the roles back when the reaction is removed** if un-reacting should remove the role. ## 3b. Add buttons For **Button roles**, press **Add button** under **Buttons**. For each button: 1. Type a **Label**, for example `Red team`. 2. Pick a **Colour** and an emoji. 3. Pick the **Behaviour**. 4. Pick **Roles to add** and **Roles to remove**. | Behaviour | What a click does | |---|---| | Toggle | Click to get the roles. Click again to lose them. | | Add only | Click to get the roles. Clicking again does nothing. | | Remove only | Click to lose the roles. | | Unique | Members keep only one role from this message. | | Verify | Gives the roles once. They are never taken away. | ## 4. Save Press the create button at the bottom. The bot posts the message and adds the reactions or buttons. Later edits change the same message in Discord. Members keep the roles they have. ## 5. Test it Go to the channel and react or click. Check your roles in your profile. Nothing happened? Check the warning at the top of this page. Then check the bot can see the channel. ## Ideas - **Verification gate.** Make a `Verified` role. Hide your channels from `@everyone` and show them to `Verified`. Add one **Verify** button. - **One colour only.** Use **Unique**, so picking Blue removes Red. - **Need logic?** For example "only level 5+ can pick this". Build buttons with code instead. See [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus). Source: https://ccbot.app/docs/guides/role-menu --- # Auto moderation > Remove scam links, spam and raids on their own. Free on every plan, and off until you turn it on. Auto moderation checks each new message in your server. When a message breaks a check you turned on, the bot removes it and can warn, time out, kick or ban the sender. It is **free** on every plan. Everything is **off** until you turn it on, so nothing changes in your server by surprise. No code is needed. Open **Auto Moderation** on the dashboard. The page has three tabs: | Tab | What it does | |---|---| | **Checks** | The bot's own checks: scam links, blocked sites, spam, raids and strikes. | | **Discord AutoMod** | Rules that Discord itself runs, before a message is posted. | | **Log** | What the bot did in the last 30 days. | ## Turn it on 1. Open **Auto Moderation β†’ Checks**. 2. Turn on the main switch at the top. The title changes from *Protection is paused* to show how many checks are on. 3. Turn on the checks you want. The page saves on its own. Look for **Saved** under the switch. > **A good start:** Turn on **Scam links**. It is marked **Recommended**. It catches fake Nitro, fake Steam and token-stealing sites, from public lists the bot keeps up to date. ## The checks Each check has its own switch. Click a check to open its settings. ### Links | Check | What it catches | |---|---| | **Scam links** | Links to known scam and phishing sites. | | **Blocked sites** | Whole groups of sites: **Adult sites**, **Gambling** and **IP grabbers** (links that record the address of whoever clicks). Add your own under **Also block these sites**. | | **Invites and links** | **Server invites**, **All other links**, or both. | ### Spam | Check | Settings | |---|---| | **Message flood** | Too many messages: **Messages** within **Within** seconds. | | **Repeated messages** | The same message **Times in a row** within a time. | | **Same message in many channels** | One message posted in a number of **Channels** within a time. | | **Mass mentions** | More than **Most mentions allowed** in one message. | | **All caps** | Messages with too many **Capital letters**. Short messages are skipped (**Skip messages shorter than**). | | **Emoji spam** | More than **Most emojis allowed** in one message. | ### Joins **Raids** catches a wave of new members: a number of **Joins** within a few seconds. Pick what happens: - **Only alert the mod log** - **Kick the new members** - **Ban the new members** ## What happens on a hit Each message check has its own action: | Action | Result | |---|---| | Delete the message | The message is removed. | | Delete and warn | Removed, and the bot posts a short warning in the channel. | | Delete and time out | Removed, and the member is timed out. Set the length in **Time out for**. | | Delete and kick | Removed, and the member is kicked. | | Delete and ban | Removed, and the member is banned. | The warning never shows the link that was removed, so nobody can click it. ## Strikes Each hit counts as a **strike** for 30 days. Under **Strikes**, you can go harder on members who keep breaking the rules. Click **Add step** to add a rung to the ladder. For example: | At | Punishment | |---|---| | 3 strikes | Time out for 10 minutes | | 5 strikes | Kick | | 8 strikes | Ban | The bot always takes the harsher of the two: the check's own action, or the step the member has reached. ## Exceptions Nobody is skipped by default, not even admins. You choose who and what to leave alone. **For the whole server**, use **Log and exceptions** at the bottom of the page: - **Leave these roles alone**: for example your moderator role. - **Skip these channels**: for example a links channel. - **Always allow these sites**: sites that a link check gets wrong. Scam links, Blocked sites and Invites and links never remove them. **For one check only**, click the settings button (the slider icon) on that check's row. It opens **Exceptions** with the same three lists. A number on the button shows how many exceptions the check has. ## Mod log Pick a **Mod log channel** under **Log and exceptions**. The bot posts an entry there for each action, with the member, the check and what it did. The colour of the entry shows how harsh the action was. You can also read the last 30 days on the **Log** tab. ## Discord AutoMod The **Discord AutoMod** tab manages Discord's own AutoMod rules. Discord runs these **before** a message is posted, so the message never appears. Click **New rule**, then pick a **Trigger**: | Trigger | Blocks | |---|---| | Keyword / regex | Your own words or patterns, one per line. | | Preset lists | Discord's lists, like **Profanity** and **Slurs**. | | Mention spam | Messages with more than **Max mentions**. | | Spam (Discord's model) | Messages Discord thinks are spam. | Then pick the **Actions**: **Block the message**, **Send an alert** to a channel, and **Timeout the member**. > **Note:** Discord only lets a bot manage AutoMod rules with the **Manage Server** permission. If the tab says *The bot needs one more permission*, click **Invite the bot again**. ## Permissions the bot needs | To do this | The bot needs | |---|---| | Remove messages | Manage Messages | | Time out members | Timeout Members | | Kick | Kick Members | | Ban | Ban Members | Discord does not let a bot act on the server owner, or on members with a role higher than the bot's role. Move the bot's role up under **Server Settings β†’ Roles** if an action fails. ## Learn more - [Moderation commands](https://ccbot.app/docs/guides/moderation-commands) adds `/kick`, `/ban` and `/timeout` for your moderators. - [Permissions and cooldowns](https://ccbot.app/docs/concepts/permissions-and-cooldowns) explains who can run what. Source: https://ccbot.app/docs/guides/auto-moderation --- # Social alerts > Post in your server when a YouTube video, a Twitch or Kick stream, a Bluesky post, a podcast episode or a blog post goes live. Social alerts follow creators and feeds for you. When something new appears, the bot posts it in the channel you pick. No code is needed. Open **Social Alerts** on the dashboard. > **Premium:** Social alerts are part of Premium. **Starter** gets 3 alerts, **Basic** 5 and **Advanced** 10. Without a plan the page shows **Upgrade to unlock**. ## What you can follow | Platform | Follow | Posts when | |---|---|---| | YouTube | A channel name, @handle or link | New videos, Shorts and live streams | | Twitch | A streamer name or twitch.tv link | A stream goes live | | Kick | A kick.com link or name | A stream goes live | | Bluesky | A name or @name.bsky.social | The account posts | | Podcast | A show name, Apple Podcasts link or feed URL | A new episode comes out | | RSS feed | A feed URL | Any blog, news site or Steam news posts | ## Add an alert 1. Open **Social Alerts**. Under **Pick where to follow**, click a platform. 2. Under **Follow**, type the name or link. Pick the right match from the list. 3. Under **Post to**, pick the **Channel**. 4. Pick who to **Ping**: **Nobody**, **@everyone**, or a role. 5. Change the **Message** if you like. See below. 6. Click **Send test** to see a sample post in your channel. 7. Click **Add alert**. The alert now shows under **Your alerts**. ## Change the message Each alert has three parts. The preview next to the settings shows the result as you type. **Message** is the text above the card. Click a name under **Insert** to add a placeholder. The bot fills it in for each post: | Placeholder | Becomes | |---|---| | `{name}` | The channel, streamer, account, show or feed name | | `{title}` | The video, stream, post or episode title | | `{url}` | The link | | `{type}` | YouTube only: video, Short or live | | `{game}` | Twitch and Kick only: the stream category | | `{mention}` | The ping you picked | For example, the default YouTube text is: ```text {mention} **{name}** posted a new {type}! ``` **Card** is the embed under the text. Set its **Title**, **Description**, **Colour**, **Picture** and **Footer**. Turn on **Show who posted** and **Show the time** if you want them. **Button** adds a link button under the message, like **Watch** or **Read**. Change its text, or turn it off. ## Only some posts Open **Only some posts** to filter by words in the title: - **Only when the title has one of**: for example `update, patch, devlog`. - **Skip when the title has any of**: for example `sponsored, ad`. YouTube alerts can also **Skip Shorts**. ## Pause or remove an alert Each alert under **Your alerts** has a switch. Turn it off to pause the alert, and back on to resume it. To delete an alert, use its remove button. The bot stops posting for it. Posts already sent stay in the channel. > **Tip:** The bot needs **View Channel**, **Send Messages** and **Embed Links** in the channel you post to. To ping a role, the role must be mentionable, or the bot needs **Mention @everyone, @here, and All Roles**. ## Learn more - [Welcome message](https://ccbot.app/docs/guides/welcome-message) is another feature that works with no code. - [Auto moderation](https://ccbot.app/docs/guides/auto-moderation) keeps scam links and spam out of your server. Source: https://ccbot.app/docs/guides/social-alerts --- # Moderation commands > Make /kick, /ban, /timeout and /purge for your moderators, with a permission check and a log message. You will make four commands for your moderators: | Command | What it does | |---|---| | `/kick` | Removes a member from the server. They can join again. | | `/ban` | Removes a member and stops them from joining again. | | `/timeout` | Stops a member from talking for a while. | | `/purge` | Deletes many recent messages at once. | Each one checks that the member who runs it is allowed to, and posts a note in a log channel. ## Before you start Make a text channel named `mod-log`. The commands post there. Check that the bot's role is **above** the roles of the members it acts on. Move it up under **Server Settings β†’ Roles**. Discord never lets a bot act on the server owner. ## Two layers of protection Use both: 1. **Hide the command.** In the command form, switch to **Advanced**. Open **Permissions** and pick a permission under **Discord permissions**, for example **Kick Members**. Discord then hides the command from members without it. 2. **Check in code.** `require_permission("kick_members")` stops the command if the member lacks the permission. Server admins can change the hiding in Discord's own settings, so the code check is the one you can trust. If the member does not have the permission, `require_permission` stops the command and shows an error. You can write your own text with `error_message="..."`. ## /kick Create a slash command named `kick` with two arguments: | Type | Name | Required | |---|---|---| | Member | `target` | Yes | | Text | `reason` | No | **Pre-hook:** ```python require_permission("kick_members") if target.id == user.id: set_error("You can't kick yourself.") why = reason or "No reason given" kick_member(target, reason=why) send_message("mod-log", f"πŸ‘’ {user.mention} kicked {target.mention}. Reason: {why}") set_content(f"Kicked {target.display_name}.") ``` `set_error` stops the command and shows the text to the member. Nothing after it runs. ## /ban Same arguments as `/kick`. **Pre-hook:** ```python require_permission("ban_members") if target.id == user.id: set_error("You can't ban yourself.") why = reason or "No reason given" ban_member(target, reason=why) send_message("mod-log", f"πŸ”¨ {user.mention} banned {target.mention}. Reason: {why}") set_content(f"Banned {target.display_name}.") ``` ## /timeout | Type | Name | Required | Description | |---|---|---|---| | Member | `target` | Yes | Who to time out | | Text | `length` | Yes | For example 10 minutes, 1 hour, 2 days | **Pre-hook:** ```python require_permission("moderate_members") timeout_member(target, length) send_message("mod-log", f"πŸ”‡ {user.mention} timed out {target.mention} for {length}.") set_content(f"Timed out {target.display_name} for {length}.") ``` A timeout can be up to **28 days**. A longer time, or a time the bot cannot read, stops the command with an error. To lift a timeout early, make `/untimeout` with `remove_timeout(target)`. ## /purge | Type | Name | Required | Description | |---|---|---|---| | Integer | `amount` | Yes | How many messages to delete | **Pre-hook:** ```python require_permission("manage_messages") if amount < 1 or amount > 100: set_error("Pick a number from 1 to 100.") purge_messages(channel.id, amount) send_message("mod-log", f"🧹 {user.mention} deleted {amount} messages in {channel.mention}.") set_content(f"Deleted {amount} messages.") ``` Under **Behavior**, turn on **Only the user sees the reply**. Otherwise the reply lands in the channel you just cleaned. > **Watch out:** Discord only bulk-deletes messages newer than **14 days**. Older messages are skipped. The bot needs **Manage Messages** and **Read Message History** in that channel. ## Permissions the bot needs | Command | The bot needs | |---|---| | `/kick` | Kick Members | | `/ban` | Ban Members | | `/timeout` | Timeout Members | | `/purge` | Manage Messages, Read Message History | If the bot lacks one, the member sees an error like *"Missing permission to kick member"*. ## Limits Each command sends one log message. A free server can send **3** extra messages per run, so you have room to spare. See [Limits](https://ccbot.app/docs/concepts/limits). ## Learn more - [Checks](https://ccbot.app/docs/reference/checks) lists `require_permission`, `allowed_roles`, `cooldown` and the other gates. - [Members and roles](https://ccbot.app/docs/reference/members-and-roles) lists `kick_member`, `ban_member`, `timeout_member` and more. - [Auto moderation](https://ccbot.app/docs/guides/auto-moderation) handles spam and scam links without a moderator. Source: https://ccbot.app/docs/guides/moderation-commands --- # Levels and XP > Give members XP for chatting, announce level-ups, and add /rank and /leaderboard commands. You will build a level system: - Members get 15 to 25 **XP** (experience points) for chatting. At most once a minute, so spam does not help. - The bot posts a message when someone reaches a new level. - `/rank` shows a member's level and XP. - `/leaderboard` shows the top 10. > **Premium needed:** XP for chatting needs a **Message** event. Message events run only on servers with a premium plan. `/rank` and `/leaderboard` work on every plan. ## What you need - A premium plan on the server. - About 10 minutes. The XP is saved in a **Mongo database**. That is a place to keep data that lasts, with one row (called a *document*) per member. You do not need to set it up. It is made the first time the code saves something. Learn more in [Saving data](https://ccbot.app/docs/concepts/saving-data). ## How levels work A member's level comes from their XP: | Level | XP needed | |---|---| | 1 | 100 | | 2 | 400 | | 3 | 900 | | 5 | 2,500 | | 10 | 10,000 | The rule is: level = square root of (XP Γ· 100). Each level takes a bit longer than the last. ## 1. Give XP for messages On the dashboard, open **Create β†’ Event**. | Field | Value | |---|---| | Name | `XP for messages` | | Type | `MESSAGE (Pro Only)` | Paste this in **Code**: ```python def level_of(xp): return int((xp / 100) ** 0.5) # Bots and webhooks do not earn XP. if user and not user.is_bot: levels = SlashMongo("levels") uid = str(user.id) now = int(unix_timestamp) doc = await levels.find_one({"_id": uid}) or {"xp": 0, "last": 0} # One reward per minute. if now - doc.get("last", 0) >= 60: old_xp = doc.get("xp", 0) new_xp = old_xp + randomNumber(15, 25) await levels.update_one( {"_id": uid}, {"$set": {"xp": new_xp, "last": now}}, upsert=True, ) if level_of(new_xp) > level_of(old_xp): send_message( channel, f"{user.mention} reached **level {level_of(new_xp)}**!", ) ``` Press save. What the code does: 1. Skips bots. 2. Loads the member's row. A new member starts at 0 XP. 3. Stops if they got XP less than 60 seconds ago. 4. Adds 15 to 25 XP and saves. `upsert=True` makes the row if it is not there yet. 5. Posts a message when the level goes up. > **Note:** Database functions like `find_one` and `update_one` need `await` in front. Most other functions do not. See [Storage](https://ccbot.app/docs/reference/storage). ## 2. Make /rank Open **Create β†’ Slash Command**. | Field | Value | |---|---| | Command Name | `rank` | | Description | `Show your level` | Under **Arguments**, add a **Member** argument: | Field | Value | |---|---| | Name | `member` | | Description | `Whose rank to show` | | Required | Off | Paste this in **Pre-hook**: ```python def level_of(xp): return int((xp / 100) ** 0.5) target = member or user doc = await SlashMongo("levels").find_one({"_id": str(target.id)}) xp = doc["xp"] if doc else 0 level = level_of(xp) next_xp = (level + 1) ** 2 * 100 set_content( f"**{target.display_name}** is level **{level}**\n" f"XP: {intcomma(xp)} / {intcomma(next_xp)}" ) ``` Leave the **Response** box empty. `set_content` fills the reply. When nobody is picked, `member` is empty, so `member or user` falls back to the person who ran the command. ## 3. Make /leaderboard Make another slash command named `leaderboard`. Paste this in **Pre-hook**: ```python def level_of(xp): return int((xp / 100) ** 0.5) top = await SlashMongo("levels").find({}, sort=[("xp", -1)], limit=10) lines = [] for place, doc in enumerate(top, start=1): lines.append( f"**{place}.** <@{doc['_id']}> β€” level {level_of(doc['xp'])} " f"({intcomma(doc['xp'])} XP)" ) set_embed(Message( title=f"Top members in {server.name}", description="\n".join(lines) or "Nobody has XP yet. Start chatting!", color="#5865F2", )) ``` `sort=[("xp", -1)]` puts the highest XP first. `limit=10` keeps only 10 rows, so the command stays fast on big servers. Mentions inside an embed do not ping anyone, so the leaderboard is quiet. ## 4. Test it 1. Send a message in your server. 2. Run `/rank`. You have 15 to 25 XP. 3. Send another message right away. Run `/rank` again. Nothing changed: that is the one-minute wait. 4. Run `/leaderboard`. Not working? Open **Logs β†’ Error Logs** on the dashboard. Errors from your code show there. ## Ideas - **Level roles.** In the event, after the level-up message, give a role: `if level_of(new_xp) == 5: add_roles(user, "Level 5")`. - **No XP in some channels.** Add `and channel.name != "bot-spam"` to the first `if`. - **Image rank card.** Design one in **Image Templates** and send it with `render_template`. See [Web and files](https://ccbot.app/docs/reference/web-and-files). - **Reset everyone.** Make a staff-only command that runs `await SlashMongo("levels").delete_many({})`. Source: https://ccbot.app/docs/guides/levels --- # Economy and coins > Build a coin system with /daily, /balance, /give and a shop that sells roles. You will build a small economy: | Command | What it does | |---|---| | `/daily` | Gives 100 coins, once every 24 hours | | `/balance` | Shows how many coins someone has | | `/give` | Sends coins to another member | | `/shop` | Lists what you can buy | | `/buy` | Buys a role with coins | Works on every plan. ## What you need - About 15 minutes. - For the shop: the roles you want to sell, and the bot's role **above** them in **Server Settings β†’ Roles**. Coins are saved in a **Mongo database** called `economy`, with one row per member. It is made the first time someone gets coins. See [Saving data](https://ccbot.app/docs/concepts/saving-data). > **Note:** Every command below goes in the **Pre-hook** box of a slash command. Leave the **Response** box empty. `set_content` writes the reply. ## 1. /daily Open **Create β†’ Slash Command**. Name it `daily`, description `Claim your daily coins`. Pre-hook: ```python cooldown("24 hours", error_message="You already claimed today. Come back %relative%.") bank = SlashMongo("economy") uid = str(user.id) await bank.update_one({"_id": uid}, {"$inc": {"coins": 100}}, upsert=True) doc = await bank.find_one({"_id": uid}) set_content(f"You got **100** coins. You now have **{intcomma(doc['coins'])}**.") ``` - `cooldown` stops the command for 24 hours per member. The wait is saved, so a bot restart does not reset it. - `%relative%` becomes "in 5 hours", and Discord counts it down live. - `$inc` adds to the number. `upsert=True` makes the row for a new member. ## 2. /balance Name it `balance`. Add a **Member** argument named `member`, with **Required** off. Pre-hook: ```python target = member or user doc = await SlashMongo("economy").find_one({"_id": str(target.id)}) coins = doc["coins"] if doc else 0 set_content(f"**{target.display_name}** has **{intcomma(coins)}** coins.") ``` ## 3. /give Name it `give`. Add two arguments: | Type | Name | Required | |---|---|---| | Member | `member` | On | | Integer | `amount` | On | Set the **Minimum** of `amount` to `1`. Pre-hook: ```python if member.id == user.id or member.is_bot: set_error("Pick another member.") bank = SlashMongo("economy") # Take the coins only if the giver has enough. One step, so two # /give at the same time cannot spend the same coins twice. taken = await bank.update_one( {"_id": str(user.id), "coins": {"$gte": amount}}, {"$inc": {"coins": -amount}}, ) if taken.modified_count == 0: set_error("You do not have enough coins.") await bank.update_one( {"_id": str(member.id)}, {"$inc": {"coins": amount}}, upsert=True, ) set_content(f"{user.mention} gave **{intcomma(amount)}** coins to {member.mention}.") ``` `set_error` stops the command and shows the text to the user. Nothing after it runs. ## 4. /shop Name it `shop`. Pre-hook: ```python SHOP = { "vip": {"price": 5000, "role": "VIP"}, "red": {"price": 1000, "role": "Red"}, } lines = [] for key, item in SHOP.items(): lines.append(f"`{key}` β€” **{item['role']}** role, {intcomma(item['price'])} coins") set_embed(Message( title="Shop", description="\n".join(lines) + "\n\nBuy with `/buy`.", color="#F1C40F", )) ``` Change `SHOP` to your own items. `role` is the exact role name in your server. ## 5. /buy Name it `buy`. Add a **Text** argument named `item`, **Required** on. Under **Choices**, keep **Fixed list** and add one value per item: `vip`, `red`. Members then pick from a list instead of typing. Pre-hook: ```python SHOP = { "vip": {"price": 5000, "role": "VIP"}, "red": {"price": 1000, "role": "Red"}, } choice = SHOP.get(item) if choice is None: set_error("That item is not in the shop.") if has_role(user, choice["role"]): set_error("You already have that role.") bank = SlashMongo("economy") uid = str(user.id) paid = await bank.update_one( {"_id": uid, "coins": {"$gte": choice["price"]}}, {"$inc": {"coins": -choice["price"]}}, ) if paid.modified_count == 0: set_error(f"You need {intcomma(choice['price'])} coins.") try: add_roles(user, choice["role"]) except Exception: # Give the coins back if the role could not be given. await bank.update_one({"_id": uid}, {"$inc": {"coins": choice["price"]}}) set_error("Could not give the role. Your coins were returned.") set_content(f"You bought the **{choice['role']}** role!") ``` > **Tip:** Keep the `SHOP` list the same in `/shop` and `/buy`. When you add an item, change both, and add its value to the `item` choices. ## 6. Test it 1. Run `/daily`. You get 100 coins. 2. Run `/daily` again. It says when you can claim next. 3. Run `/give` with a friend and `50`. Check both with `/balance`. 4. Lower a price to `50` and try `/buy`. Errors from your code show in **Logs β†’ Error Logs** on the dashboard. ## Ideas - **Coins for chatting.** Use the Message event from [Levels and XP](https://ccbot.app/docs/guides/levels) and `$inc` coins instead of XP. Needs premium. - **Rich list.** Copy `/leaderboard` from the levels guide and sort by `coins`. - **Gambling.** A `/coinflip` that bets coins. Start from the [coin flip example](https://ccbot.app/docs/examples/coinflip). - **Staff tools.** A `/addcoins` command for admins. Set its Discord permissions to **Manage Server** so only staff see it. Source: https://ccbot.app/docs/guides/economy --- # Ticket system > Post a panel with an "Open a ticket" button. Each click opens a private thread for the member and your staff. You will build a support ticket system: 1. A panel message with an **Open a ticket** button. 2. A click opens a **private thread**. Only the member and your staff can see it. 3. Inside, a **Close ticket** button deletes the thread when the problem is solved. Works on every plan. ## What you need - A channel for the panel, for example `#support`. - The bot needs these permissions in that channel: **Create Private Threads**, **Send Messages in Threads** and **Manage Threads**. - Staff need **Manage Threads** to see every ticket. ## How the parts fit | Part | Kind | Job | |---|---|---| | `/ticket-panel` | Slash command | Posts the panel once | | `Tickets` | Event: Button Click | Runs when someone clicks a ticket button | The panel button stays on the message for weeks. Each click runs the event. The event knows which button was clicked by its **custom ID**, a short name you give each button. ## 1. The panel command Open **Create β†’ Slash Command**. | Field | Value | |---|---| | Command Name | `ticket-panel` | | Description | `Post the ticket panel` | Open the permissions part of the form. In **Discord permissions**, pick **Manage Server**. Only staff see the command. Under **Behavior**, turn on **Only the user sees the reply**. Pre-hook: ```python panel = ComponentBuilder() panel.add_button("Open a ticket", style="primary", emoji="🎫", custom_id="ticket_open") send_message( channel, message=Message( title="Need help?", description="Press the button below. A private ticket opens for you and our staff.", color="#5865F2", ), components=panel, ) set_content("Panel posted.") ``` ## 2. The button event Open **Create β†’ Event**. | Field | Value | |---|---| | Name | `Tickets` | | Type | `Button Click / Select Menu Change` | | Custom ids | `ticket_open, ticket_close` | Code: ```python # Answer Discord first. It waits only 3 seconds. defer_response(ephemeral=True) if custom_id == "ticket_open": thread = create_thread( channel, name=f"ticket-{user.username}", type="private", initial_message=f"{user.mention} thanks for opening a ticket. Tell us how we can help.", ) close = ComponentBuilder() close.add_button("Close ticket", style="danger", custom_id="ticket_close") send_message(thread, "Staff: press this when the ticket is solved.", components=close) respond_interaction(interaction, f"Your ticket is open: {thread.mention}") elif custom_id == "ticket_close": if not has_permission(user, "manage_threads"): respond_interaction(interaction, "Only staff can close tickets.") else: delete_channel(channel) ``` What it does: - `defer_response(ephemeral=True)` tells Discord "working on it". The answer only shows to the clicker. - `create_thread(..., type="private")` makes the private thread. Mentioning the member in `initial_message` adds them to it. - The **Close ticket** button has its own custom ID, so the same event handles it. - `delete_channel(channel)` deletes the thread. Here `channel` is the thread the button is in. > **Watch out:** Each custom ID must be in the event's **Custom ids** box. A button whose ID is not listed does nothing, and Discord shows "This interaction failed". ## 3. Test it 1. Run `/ticket-panel` in `#support`. 2. Press **Open a ticket**. A link to your new thread appears. 3. Open the thread and press **Close ticket**. The thread is deleted. 4. Try **Close ticket** from an account without **Manage Threads**. It says only staff can close. Errors from your code show in **Logs β†’ Error Logs** on the dashboard. ## Ideas - **Ask why first.** Open a form when the button is clicked, then make the thread with the answer. See [Forms](https://ccbot.app/docs/concepts/forms). - **Log closed tickets.** Before `delete_channel`, post `send_message("ticket-log", f"{channel.name} closed by {user.mention}")`. - **One ticket per member.** Save the open thread ID with `update_member_var("ticket", thread.id)` and check it before making a new one. See [Storage](https://ccbot.app/docs/reference/storage). - **Stop spam clicks.** In a button event, `wait = cooldown("1 minute", key=user.id)` gives the time left, or `False` when the member may click. Stop with a message when `wait` is set. Source: https://ccbot.app/docs/guides/tickets --- # Giveaways > Start a giveaway with a Join button, then pick a random winner with one command. You will build a giveaway system: | Part | Job | |---|---| | `/giveaway` | Posts the giveaway with a **Join** button | | `Giveaway join` event | Saves each member who clicks **Join** | | `/gend` | Picks a random winner and announces it | Works on every plan. One giveaway runs at a time. ## What you need - About 10 minutes. - A channel for giveaways. Entries are saved in a **Mongo database** called `giveaway`, one row per member. A member who clicks twice still has one entry. See [Saving data](https://ccbot.app/docs/concepts/saving-data). ## 1. /giveaway Open **Create β†’ Slash Command**. | Field | Value | |---|---| | Command Name | `giveaway` | | Description | `Start a giveaway` | Add a **Text** argument named `prize`, **Required** on. Open the permissions part of the form. In **Discord permissions**, pick **Manage Server**. Under **Behavior**, turn on **Only the user sees the reply**. Pre-hook: ```python # Start fresh: remove entries from the last giveaway. entries = SlashMongo("giveaway") await entries.delete_many({}) update_server_var("giveaway_prize", prize) join = ComponentBuilder() join.add_button("Join", style="success", emoji="πŸŽ‰", custom_id="giveaway_join") send_message( channel, message=Message( title=f"πŸŽ‰ Giveaway: {prize}", description="Press **Join** to enter. Good luck!", color="#E91E63", ), components=join, ) set_content("Giveaway started. Run /gend to pick the winner.") ``` `update_server_var` saves the prize name, so `/gend` can show it later. ## 2. The Join button Open **Create β†’ Event**. | Field | Value | |---|---| | Name | `Giveaway join` | | Type | `Button Click / Select Menu Change` | | Custom ids | `giveaway_join` | Code: ```python defer_response(ephemeral=True) entries = SlashMongo("giveaway") await entries.update_one( {"_id": str(user.id)}, {"$setOnInsert": {"joined": int(unix_timestamp)}}, upsert=True, ) total = await entries.count() respond_interaction(interaction, f"You are in! {total} entries so far.") ``` - The member's ID is the row's `_id`, so a second click finds the same row. No double entries. - `$setOnInsert` only writes when the row is new. - `count()` counts every entry. ## 3. /gend Make another slash command named `gend`, description `Pick the giveaway winner`. Give it **Manage Server** too, and turn on **Only the user sees the reply**. Pre-hook: ```python entries = SlashMongo("giveaway") picked = await entries.aggregate([{"$sample": {"size": 1}}]) if not picked: set_error("Nobody has joined yet.") winner = picked[0]["_id"] prize = server_var("giveaway_prize", "the prize") send_message(channel, f"πŸŽ‰ Congratulations <@{winner}>! You won **{prize}**!") await entries.delete_many({}) set_content("Winner picked.") ``` `$sample` picks one random row. The database does the picking, so it stays fast with thousands of entries. ## 4. Test it 1. Run `/giveaway` with prize `Nitro`. 2. Press **Join**. It says "You are in! 1 entries so far." 3. Press **Join** again. Still 1 entry. 4. Run `/gend`. You win. Errors from your code show in **Logs β†’ Error Logs** on the dashboard. ## Ideas - **Reroll.** Remove the `delete_many` line from `/gend`. Entries then stay until the next `/giveaway`, so running `/gend` again picks a new winner. - **Only members with a role can join.** In the event, check `has_role(user, "Member")` before saving. When it is `False`, answer with `respond_interaction` and skip the save. - **End on its own.** `schedule` can run a built-in action later, but it cannot run your own code. A **Clock** event with `schedule_clock` can. See [Scheduling](https://ccbot.app/docs/reference/scheduling). - **Show the count on the message.** Edit the giveaway message with `edit_message` after each join. See [Messages](https://ccbot.app/docs/reference/messages). Source: https://ccbot.app/docs/guides/giveaway --- # Poll > Make a /poll command that posts a real Discord poll, or a quick vote with buttons. You will make `/poll`. Members run it with a question and some answers. The bot posts a poll that everyone can vote on. There are two ways to do it: | Way | Best for | |---|---| | **Discord poll** | Most polls. Discord counts the votes and shows the result bar. Lasts from 1 hour to 32 days. | | **Button vote** | A quick yes/no vote that ends after a few minutes. | ## Discord poll ### 1. Create the command Open **Create β†’ Slash Command** and switch to **Advanced** at the top of the form. | Field | Value | |---|---| | Command Name | `poll` | | Description | `Start a poll` | Under **Behavior**, turn on **Only the user sees the reply**. The poll itself is public. Only the "Poll posted" note is private. ### 2. Add the arguments Under **Arguments**, add three: | Type | Name | Required | Description | |---|---|---|---| | Text | `question` | Yes | What to ask | | Text | `answers` | Yes | Answers, split by commas | | Integer | `hours` | No | How long it stays open | ### 3. Add the code Open **Pre-hook** and paste: ```python choices = [a.strip() for a in answers.split(",") if a.strip()] if len(choices) < 2: set_error("Give at least two answers, split by commas.") await create_poll(question, choices, duration=hours or 24) set_content("Poll posted!") ``` Leave the **Content / Reply Message** box empty. `set_content` writes the reply. ### 4. Try it Save, then run: ```text /poll question: Pizza or burgers? answers: Pizza, Burgers, Both ``` > **Discord's poll limits:** - Up to **10** answers. Extra answers are dropped. > - Each answer can be up to **55** characters. The question can be up to **300**. > - `duration` is in **hours**, from 1 to 768 (32 days). The default is 24. ### Let members pick more than one Add `multiselect=True`: ```python await create_poll(question, choices, duration=hours or 24, multiselect=True) ``` ### Post it in another channel Add `channel=`. It takes a channel, its ID or its name: ```python await create_poll(question, choices, channel="announcements") ``` ## Button vote This version puts **Yes** and **No** buttons on the reply. Each button shows its count. A member can change their vote. Each member counts once. Make a new slash command named `vote` with one **Text** argument named `question`. Leave **Only the user sees the reply** off, so everyone can see the buttons. ### Pre-hook ```python class Vote(View): def __init__(self): self.votes = {} async def cast(self, interaction, choice): await defer(interaction, action="update") self.votes[str(interaction.user.id)] = choice yes = len([v for v in self.votes.values() if v == "yes"]) no = len(self.votes) - yes self.update_component("yes", label=f"Yes ({yes})") self.update_component("no", label=f"No ({no})") # Edit through the newest click, so the edit never expires. self.interaction = interaction await self.update_view() @button(label="Yes (0)", style="success", custom_id="yes") async def yes(self, interaction, component): await self.cast(interaction, "yes") @button(label="No (0)", style="danger", custom_id="no") async def no(self, interaction, component): await self.cast(interaction, "no") view = Vote() view.interaction = interaction set_view(view) set_content(f"**{question}**\nClick to vote.") ``` ### Post-hook The post-hook runs when the vote ends. Voting ends after **3 minutes with no new votes**. Each new vote starts the 3 minutes again. ```python yes = len([v for v in view.votes.values() if v == "yes"]) no = len(view.votes) - yes await respond_interaction( view.interaction, content=f"**{question}**\nVoting closed. Yes: {yes} Β· No: {no}", ) ``` `respond_interaction` changes the text and removes the buttons. > **Tip:** Need a vote that stays open for hours or days? Use the Discord poll above. Buttons on a command reply are for short votes. ## Learn more - [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus) explains how a View works. - [Server events and polls](https://ccbot.app/docs/reference/server-events-and-polls) lists every argument of `create_poll`. - [Views](https://ccbot.app/docs/reference/views) lists `update_component`, `update_view` and the other View tools. Source: https://ccbot.app/docs/guides/poll --- # Button game > Build rock, paper, scissors with buttons. Only the player can click, and the game ends on its own if they walk away. You will make `/rps`. The bot shows three buttons. The player picks one, the bot picks one, and the reply shows who won. > You picked **rock**, I picked **scissors**. You win! This guide uses a **View**. A View is a set of buttons that lives inside one run of a command. The buttons, the code that runs on a click, and the result all stay in the same command. You do not need an event for it. ## 1. Create the command Open **Create β†’ Slash Command** and switch to **Advanced** at the top of the form. | Field | Value | |---|---| | Command Name | `rps` | | Description | `Play rock, paper, scissors` | Leave the **Content / Reply Message** box empty. The code writes the reply. ## 2. Add the buttons Open **Pre-hook** and paste: ```python class Game(View): def __init__(self, user_id): self.user_id = user_id # only this member can click self.pick = None async def choose(self, interaction, pick): await defer(interaction, action="update") self.pick = pick self.stop() # ends the game; the post-hook runs next @button(label="Rock", emoji="πŸͺ¨", style="secondary", custom_id="rock") async def rock(self, interaction, component): await self.choose(interaction, "rock") @button(label="Paper", emoji="πŸ“„", style="secondary", custom_id="paper") async def paper(self, interaction, component): await self.choose(interaction, "paper") @button(label="Scissors", emoji="βœ‚οΈ", style="secondary", custom_id="scissors") async def scissors(self, interaction, component): await self.choose(interaction, "scissors") game = Game(user_id=user.id) game.interaction = interaction set_view(game) set_content(f"{user.mention}, pick one!") ``` What each part does: | Part | Job | |---|---| | `class Game(View)` | Makes a new set of buttons. | | `self.user_id = user_id` | Only the member who ran `/rps` can click. Other clicks are ignored. | | `@button(...)` | Adds one button. The function under it runs on a click. | | `defer(interaction, action="update")` | Tells Discord "click received" within its 3-second limit. | | `self.stop()` | Ends the View. No code after it in that function runs. | | `set_view(game)` | Puts the buttons on the reply and waits for clicks. | > **Watch out:** Do not call `super().__init__()` in your View, and do not call `game.wait()` yourself. `set_view` does the waiting for you. ## 3. Show the result Open **Post-hook** and paste: ```python if game.pick is None: result = "Too slow! The game ended." else: bot_pick = random(["rock", "paper", "scissors"]) beats = {"rock": "scissors", "paper": "rock", "scissors": "paper"} if game.pick == bot_pick: outcome = "It's a tie!" elif beats[game.pick] == bot_pick: outcome = "You win!" else: outcome = "I win!" result = f"You picked **{game.pick}**, I picked **{bot_pick}**. {outcome}" await respond_interaction(interaction, content=result) ``` The post-hook runs after the View ends. That happens after a click, or after **3 minutes with no click**. If the time runs out, `game.pick` is still `None`. `respond_interaction` changes the reply text and removes the buttons. ## 4. Try it Save, then run `/rps` in your server. Click a button. Ask a friend to click your buttons. Nothing happens for them, because of `user_id`. ## Make it better ### Keep score Save wins with a member variable. Add this to the post-hook, just before the `respond_interaction` line: ```python if game.pick is not None and outcome == "You win!": wins = int(member_var("rps_wins", 0)) + 1 update_member_var("rps_wins", wins) result = f"{result} You have {wins} wins." ``` Member variables are saved as text, so `int(...)` turns the value back into a number. See [Saving data](https://ccbot.app/docs/concepts/saving-data). ## Learn more - [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus) explains Views and when to use an event instead. - [Views](https://ccbot.app/docs/reference/views) lists every View tool, like `update_component` and `disable_all_components`. - [Interactions](https://ccbot.app/docs/reference/interactions) covers `defer` and `respond_interaction`. Source: https://ccbot.app/docs/guides/button-game --- # Reminders > Make a /remind command that DMs a member later, or pings them in the channel. You will make `/remind`. A member runs it with a time and a note. Later, the bot sends them the note. ```text /remind when: 2 hours note: Take the pizza out ``` The bot uses `schedule`. It saves an action now and runs it later. The bot keeps the action safe through restarts. ## 1. Create the command Open **Create β†’ Slash Command** and switch to **Advanced** at the top of the form. | Field | Value | |---|---| | Command Name | `remind` | | Description | `Remind me about something` | Under **Behavior**, turn on **Only the user sees the reply**. ## 2. Add the arguments | Type | Name | Required | Description | |---|---|---|---| | Text | `when` | Yes | For example 10 minutes, 2 hours, 3 days | | Text | `note` | Yes | What to remind you about | ## 3. Add the code Open **Pre-hook** and paste: ```python await schedule("send_dm", f"in {when}", data=f"⏰ Reminder: {note}") set_content(f"Got it! I will DM you {timestamp(f'in {when}', 'R')}.") ``` | Part | Job | |---|---| | `"send_dm"` | The action to run later: a DM. | | `f"in {when}"` | When to run it, for example `in 2 hours`. | | `data=...` | The text of the DM. | | `timestamp(..., 'R')` | Shows "in 2 hours" in the reply. It counts down on its own in Discord. | The DM goes to the member who ran the command. You do not need to say who. > **Note:** Write the time as a length: `10 minutes`, `2 hours`, `3 days`. If the bot cannot read the time, the member sees *"Schedule time must be in the future"* and nothing is saved. ## 4. Try it Save, then run: ```text /remind when: 1 minute note: It works! ``` Wait one minute and check your DMs. ## Ping in the channel instead Some members turn DMs off. Then a DM reminder fails without a word. To ping them in the channel instead, use `send_message`: ```python await schedule( "send_message", f"in {when}", data=f"⏰ {user.mention}, reminder: {note}", ) set_content(f"Got it! I will ping you here {timestamp(f'in {when}', 'R')}.") ``` The message goes to the channel where the command ran. ## Limits `schedule` works on every plan. Each server can have a number of actions **waiting** at once: | Plan | Waiting actions | |---|---| | Free | 20 | | Starter | 100 | | Basic | 250 | | Advanced | 1,000 | An action stops counting once it has run. When the server is full, `/remind` shows an error until older reminders go off. > **Tip:** `schedule` returns an ID. Save it if you want a `/cancel-reminder` command later. `await cancel_schedule(id)` deletes a waiting action, and `await list_schedules()` lists all of them for the server. ## Learn more - [Scheduling](https://ccbot.app/docs/reference/scheduling) lists every action `schedule` can run, like `add_role` and `delete_message`. - [Time](https://ccbot.app/docs/reference/time) covers `timestamp` and the other time tools. - The [/remind example](https://ccbot.app/docs/examples/remind) is a ready-to-copy version. Source: https://ccbot.app/docs/guides/reminders --- # Buttons and menus > Two ways to add buttons and select menus, and how to pick the right one. There are two ways to make buttons and menus work. Picking the right one saves a lot of code. | | View | Component + event | |---|---|---| | Lives in | The command itself | A separate event | | Works for | A few minutes after the command | Forever, even after the bot restarts | | Can see the command's variables | Yes | No | | Good for | Confirm prompts, games, pickers, quizzes | Role panels, ticket buttons, "click any time" panels | **Rule of thumb:** if the click happens soon after the command, use a **View**. If the button must keep working for days, use a **component and an event**. ## Views A **View** is a group of buttons and menus inside the command. You write what each button does right there, next to the rest of the command. Pre-hook: ```python class Confirm(View): def __init__(self, user_id): self.user_id = user_id # only this person can click self.choice = None @button(label="Yes", style="success", custom_id="yes") async def yes(self, interaction, component): await defer(interaction, action="update") self.choice = "yes" self.disable_all_components() await self.update_view() self.stop() @button(label="No", style="danger", custom_id="no") async def no(self, interaction, component): await defer(interaction, action="update") self.choice = "no" self.disable_all_components() await self.update_view() self.stop() view = Confirm(user.id) view.interaction = interaction set_view(view) set_content("Are you sure?") ``` Post-hook: ```python if view.choice == "yes": send_message(channel, "Confirmed.") elif view.choice is None: send_message(channel, "Timed out.") ``` The post-hook runs after the View stops, or after it times out. Things to know: - Start every button with `await defer(interaction, action="update")`. It tells Discord the click was received. - `user_id` limits who can click. Leave it out and anyone can click. - `self.stop()` ends the View. - `await self.update_view()` redraws the buttons, for example after you disable them. - The View times out after 3 minutes with no clicks. - Never call `super().__init__()`. - With `set_view`, the bot waits for the View for you. Do not call `view.wait()`. Full list: [Views](https://ccbot.app/docs/reference/views). ### Reading what was picked In a menu callback, read the choice from `interaction.values`. It is always a list. ```python @text_menu( options=[TextOption(label="Red", value="red"), TextOption(label="Blue", value="blue")], custom_id="color", ) async def color(self, interaction, component): await defer(interaction, action="update") self.picked = interaction.values[0] self.stop() ``` > **Watch out:** `component` is the menu you wrote, not what the user picked. `component.values` does not hold the choice. Use `interaction.values`. ## Components and events For a button that must work forever, split it in two: 1. **A command posts the button** with a `custom_id`. 2. **An event listens** for that `custom_id` and does the work. Command pre-hook: ```python c = ComponentBuilder() c.add_button("Open ticket", style="primary", custom_id="ticket_open") set_components(c) set_content("Need help? Press the button.") ``` Event, type **Button Click / Select Menu Change**, Custom ids: `ticket_open`: ```python defer_response(ephemeral=True) respond_interaction(interaction, "Your ticket is being made…") ``` The event does not know anything from the command. It only gets `custom_id`, `user`, `channel`, `message` and `interaction`. Full list: [Components](https://ccbot.app/docs/reference/components). ## The 3-second rule Discord waits 3 seconds for an answer to a click. Without one, the user sees "This interaction failed". - In a View button, start with `await defer(interaction, action="update")`. - In an event, start with `defer_response(...)`. Then do the slow work. See [Interactions](https://ccbot.app/docs/reference/interactions). ## Discord limits - At most 5 rows. - At most 5 buttons in a row. - A select menu takes a whole row by itself. Source: https://ccbot.app/docs/concepts/buttons-and-menus --- # Forms > Pop up a form with text boxes, then do something with what the user typed. A **form** (Discord calls it a *modal*) is a pop-up window with text boxes. Use it when you need longer answers, like an application, a report or feedback. A form has two parts: 1. **Open the form.** From a command or a button click. 2. **Handle the answers.** In a **Modal Submit** event. ## 1. Build and open the form In a slash command: 1. Under **Behavior**, turn off **Show "thinking…"**. A form must be the first answer to the command. 2. In the **Pre-hook**, build the form and open it: ```python form = ModalBuilder(title="Staff application", custom_id="apply") form.add_text_input("age", "How old are you?", max_length=3) form.add_text_input("why", "Why do you want to join?", style="long") await send_modal(form) end() ``` `end()` stops the normal reply. The form is the reply. ### Text box options ```python form.add_text_input( name, label, style="short", placeholder=UNDEFINED, value=UNDEFINED, required=True, min_length=0, max_length=4000, ) ``` `UNDEFINED` means "not set". Leave those out unless you need them. | Option | What it does | |---|---| | `name` | The key you read the answer with later. | | `label` | The question shown above the box. | | `style` | `"short"` for one line, `"long"` for a big box. | | `placeholder` | Grey hint text inside the box. | | `value` | Text already filled in. | | `required` | Set `False` to allow an empty answer. | | `min_length`, `max_length` | Shortest and longest answer allowed. | Discord allows at most 5 text boxes in one form. ## 2. Handle the answers Make an event: - **Type:** Modal Submit - **Custom ids:** `apply` (the same `custom_id` as the form) ```python defer_response(ephemeral=True) age = values.get("age", "") why = values.get("why", "") send_message("applications", f"New application from {user.mention}\nAge: {age}\nWhy: {why}") respond_interaction(interaction, "Thanks! Your application was sent.") ``` `values` holds every answer, by the `name` you gave each text box. > **Answer fast:** Discord waits 3 seconds. Call `defer_response(...)` first, then do the work, then `respond_interaction(...)`. ## Open a form from a button In a **Button Click** event, open the form as the first answer. Do not defer before it. ```python form = ModalBuilder(title="Report a user", custom_id="report") form.add_text_input("details", "What happened?", style="long") await send_modal(form) ``` Inside a [View](https://ccbot.app/docs/concepts/buttons-and-menus), pass the click as the second argument: ```python @button(label="Apply", custom_id="apply_btn") async def apply(self, interaction, component): await send_modal(form, interaction) ``` Without the second argument, the form tries to answer the command again, and Discord refuses it. Full list of form functions: [Forms reference](https://ccbot.app/docs/reference/modals). Source: https://ccbot.app/docs/concepts/forms --- # Saving data > Keep coins, levels, warnings and settings after a command ends. Variables in your code are gone when the command ends. To keep something, save it. There are three ways: | Way | Good for | Kept after restart | |---|---|---| | Server and member variables | One value: a counter, a setting, a user's coins | Yes | | Mongo database | Lists and records: inventories, warnings, leaderboards | Yes | | Temp variables | Short-lived values | No | **Start with member variables** for simple things. **Move to Mongo** when you need lists, many fields per user, or sorting. ## Server variables One value for the whole server. ```python count = int(server_var("counter", 0)) update_server_var("counter", count + 1) set_content(f"This command was used {count + 1} times.") ``` - `server_var(name, default)` reads a value. Gives `default` when it was never saved. - `update_server_var(name, value)` saves a value. You can see and edit saved values on the dashboard under **Variables**. ## Member variables One value per member. Without `member=`, it is the person who ran the command. ```python coins = int(member_var("coins", 0)) update_member_var("coins", coins + 100) set_content(f"You now have {coins + 100} coins.") ``` For someone else, pass `member=`: ```python their_coins = int(member_var("coins", 0, member=target)) ``` > **Values come back as text:** Saved values are stored as text. `member_var("coins")` gives `"100"`, not `100`. Wrap it in `int(...)` before doing sums. ## Mongo databases A Mongo database stores records. A record is a set of named fields, like a row: ```python {"user_id": 123, "coins": 500, "items": ["sword", "shield"]} ``` Open one with `SlashMongo(database, collection)`. Pick any names. Each server gets its own private copy. ```python db = SlashMongo("economy", "wallets") wallet = await db.find_one({"user_id": user.id}) coins = wallet["coins"] if wallet else 0 await db.update_one( {"user_id": user.id}, {"$set": {"coins": coins + 100}}, upsert=True, ) ``` `upsert=True` makes the record when it does not exist yet. ### Common actions | Code | What it does | |---|---| | `await db.insert_one(record)` | Add one record. | | `await db.find_one(filter)` | Get the first match, or `None`. | | `await db.find(filter)` | Get all matches, as a list. | | `await db.update_one(filter, changes)` | Change the first match. | | `await db.delete_one(filter)` | Delete the first match. | | `await db.count(filter)` | Count matches. | Write `await` in front of every Mongo call. ### Top 10 leaderboard ```python db = SlashMongo("economy", "wallets") top = await db.aggregate([ {"$sort": {"coins": -1}}, {"$limit": 10}, ]) lines = [f"<@{w['user_id']}>: {w['coins']}" for w in top] set_content(listify(lines, prefix="number")) ``` You can browse your data on the dashboard under **Mongo Databases**. ## Databases **Databases** (under **Variables**) are tables you set up on the dashboard first, with fixed columns. Open one by its ID: ```python db = await load_database("my_table_id") rows = db.find(where={"user_id": str(user.id)}) await db.insert({"user_id": str(user.id), "note": "hello"}) ``` Use these when you want to manage the rows by hand on the dashboard. For data that code writes, Mongo is easier. ## Temp variables Kept in memory only. They vanish when the bot restarts. ```python set_temp_var("last_winner", user.id) winner = temp_var("last_winner") ``` Use them for short-lived values where losing them does not matter. ## Locks Two people can run a command at the same time. When both change the same record, one change can be lost. Wrap the change in a lock: ```python acquire_lock(f"wallet-{user.id}", error="Slow down, try again.") # ... read, change, save ... release_lock(f"wallet-{user.id}") ``` > **Always release the lock:** A lock is not released when the command ends. If your code stops with an error between the two lines, the lock stays on and the command keeps saying "Slow down". Keep the code between them short and safe, or release the lock before any `set_error`. Full list: [Storage reference](https://ccbot.app/docs/reference/storage). Source: https://ccbot.app/docs/concepts/saving-data --- # Permissions and cooldowns > Choose who can use a command, where it works, and how often. There are two ways to limit a command: - **On the form.** No code. Best for most commands. - **In code.** For rules the form cannot express, like "only on weekends" or "once per day per server". ## On the form Switch the command form to **Advanced**, then open **Permissions**. ### Discord permissions Pick the Discord permissions a member needs, like **Ban Members**. Discord hides the command from members without them. This works for top-level slash commands only. It is not available in sub commands. ### Roles and channels | Field | What it does | |---|---| | Allowed roles | Only members with one of these roles can use it. | | Blocked roles | Members with any of these roles cannot use it. | | Allowed channels | It only works in these channels. | | Blocked channels | It does not work in these channels. | Empty means no limit: **Everyone** and **Every channel**. ## In code Put these at the top of the **Pre-hook**. Each one stops the command with an error when the check fails. You do not need an `if` around them. ### require_permission ```python require_permission("ban_members") ``` The member needs every permission listed. Administrators always pass. Permission names are lowercase with underscores, like `manage_messages`, `kick_members`, `manage_server`. ### Roles ```python allowed_roles("Staff", "Moderator") banned_roles("Muted") ``` `allowed_roles` passes when the member has **any** of the roles. Use names, IDs or mentions. ### Channels ```python allowed_channels("bot-commands") banned_channels("general", "announcements") ``` Every check takes `error_message=` for your own text: ```python allowed_roles("VIP", error_message="This command is for VIP members.") ``` ## Cooldowns A cooldown stops people from running a command too often. ```python cooldown("30 seconds") ``` The time is plain English: `"10 seconds"`, `"5 minutes"`, `"1 hour"`, `"1 day"`. ### Per user, per server, or shared By default each user has their own cooldown. Change who shares it with `key=`: | Code | Who shares the cooldown | |---|---| | `cooldown("1 hour")` | Each user on their own | | `cooldown("1 hour", key=server.id)` | The whole server | | `cooldown("1 hour", key=channel.id)` | Everyone in one channel | ### Allow a few uses first `count=` lets someone run it several times before the cooldown starts: ```python cooldown("1 minute", count=3) ``` ### A nice message `error_message=` can show when the cooldown ends: ```python cooldown("1 day", error_message="You already claimed today. Come back %relative%.") ``` | Placeholder | Shows | |---|---| | `%time%` | How long to wait, like `5 minutes` | | `%relative%` | A live countdown, like **in 5 minutes** | | `%at%` | The time it ends, in each reader's own timezone | | `%timestamp%` | The raw time number, for your own Discord timestamp | > **Tip:** Put `cooldown(...)` after your other checks. Then someone who fails a role check does not use up their cooldown. ## Checks in your own logic To decide something without stopping the command, use `has_role` or `has_permission`. They give `True` or `False`: ```python if has_role(user, "VIP"): reward = 200 else: reward = 100 ``` Full list: [Checks reference](https://ccbot.app/docs/reference/checks). Source: https://ccbot.app/docs/concepts/permissions-and-cooldowns --- # Limits and premium > How much one command run can do, what premium changes, and the two premium-only functions. Every run of a command or event has limits. They keep one script from spamming a server or slowing the bot for everyone. A **run** is one use of a command or one time an event fires. The pre-hook and post-hook of one run share the same limits. ## Actions per run | Action | Free | Premium | |---|---|---| | `send_message` and `forward_message` (together) | 3 | 5 | | `send_dm` | 2 | 2 | | `add_roles` | 5 | 15 | | `remove_roles` | 5 | 15 | | `fetch`, different web requests | 5 | 20 | | `load_file`, different links | 5 | 20 | | Page lists (`PaginatorV2`) | 3 | 3 | The command's own reply is **not** counted. Only extra messages are. Fetching the same web address twice in one run counts once. The second time comes from memory. Image templates (`render_template`) are limited per minute, not per run: 20 a minute per server on free, 120 on premium. On free, going over stops the script. On premium, the call waits its turn. ## Sizes | Thing | Free | Premium | |---|---|---| | Download size for `fetch` and `load_file` | 5 MB | 100 MB | | Mongo results per search | 10,000 | 500,000 | | Images per rendered template | 5 | 15 | ## Code limits These are the same on every plan: | Thing | Limit | |---|---| | Loop steps in one run (all loops together) | 100,000 | | Memory | 256 MB | | `range()` size | 1,000,000 | | `sleep()` | 1 day | | Pages in a page list | 200 | | Suggestions in autocomplete | 25 | There is **no time limit**. A command can wait for button clicks for minutes. The limits count work, not time. > **Note:** All loops in one run share the 100,000 steps. A loop inside a loop uses them up fast. Loop over members once, not members times roles. ## Premium-only features Only these refuse to run on a free server: | Feature | What happens on free | |---|---| | `ask_ai(...)` | Stops with "ask_ai is a premium feature". | | Repeating jobs (`start_job`) | The job pauses itself when it fires. | | **Message** events | They do not run. | Everything else works on free. Premium only raises the numbers above. `schedule` works on free. It caps how many actions can wait at once: 20 on free, 100 on Starter, 250 on Basic, 1,000 on Advanced. They free up as they run. `cancel_schedule`, `list_schedules` and `render_template` also work on free. ## Dashboard limits Some limits depend on your plan, but your code never sees them: - How many commands you can have. - How many events can be on at once. Free has 2. See the [Premium](https://ccbot.app/premium) page for each plan. ## Write for free If a command fits the free limits, it works on every server. A command that sends 5 messages breaks on free servers. Source: https://ccbot.app/docs/concepts/limits --- # Common mistakes > The mistakes that make a command do nothing or fail, and how to fix each one. Your command does nothing, or shows an error? Check this list first. ## Blocked words anywhere in the code Some words are blocked for safety. If one appears **anywhere** in your code, the whole script is turned off and nothing runs. No error shows. This includes text inside quotes and comments. ```text getattr __name__ __class__ __builtins__ __bases__ __subclasses__ __build_class__ __getattribute__ __closure__ cell_contents __base__ __mro__ ``` ```python # Breaks the whole command, even though it is only a message: set_content("Use getattr for this") ``` `getattr` and `__name__` are the ones people write by accident. ## import does nothing There is nothing to import. Every function in these docs is already there. An `import` line is removed. ```python import random # removed, does nothing pick = random(["a", "b", "c"]) # use the built-in random(...) instead ``` ## print does not exist `print(...)` fails. To see a value, put it in the reply: ```python set_content(f"Debug: {my_value}") ``` ## Sending with .send() Discord's own send methods are blocked. Use the helpers. They count toward [limits](https://ccbot.app/docs/concepts/limits) the right way. | Do not write | Write | |---|---| | `channel.send("hi")` | `send_message(channel, "hi")` | | `member.send("hi")` | `send_dm(member, "hi")` | | `message.respond("hi")` | `send_message(channel, "hi", reply=message.id)` | | `member.add_role(role)` | `add_roles(member, role)` | | `member.remove_role(role)` | `remove_roles(member, role)` | ## Using response in the pre-hook `response` is the bot's reply. It only exists in the **Post-hook**, after the reply is sent. ## set_content in the post-hook The reply is already sent when the post-hook runs. `set_content` there does nothing. Use `send_message` or `edit_message` instead. ## end() did not stop the code `end()` only cancels the reply. The lines after it in the pre-hook still run. Use `set_error("text")` to stop right away. See [How a command runs](https://ccbot.app/docs/getting-started/how-commands-run). ## Calling view.wait() after set_view `set_view(view)` already waits for the buttons. Calling `view.wait()` again waits twice. Remove it. ## Forgetting await Many functions get `await` added for you, like `send_message`, `add_roles` and `update_member_var`. Others need it written by hand: ```text fetch ask_ai render_template load_script sleep schedule send_modal defer wait_for create_poll create_event lock_channel unlock_channel set_slowmode list_members view.update_view paginator.start every Mongo call ``` Without `await`, these do nothing. Writing `await` is always safe, so when in doubt, write it. ## Clicks say "This interaction failed" Discord waits 3 seconds for an answer to a click. Answer first, then do the work: - In a View button: `await defer(interaction, action="update")` - In a Button Click event: `defer_response()` See [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus). ## Reading the menu choice from component In a menu callback, `component.values` is not the user's choice. Use `interaction.values[0]`. ## An argument hides a variable An argument named `user`, `channel` or `server` replaces the built-in one. Name it `target` or `member` instead. ## Saved numbers are text `member_var` and `server_var` give back text. `"100" + 1` fails. Use `int(member_var("coins", 0))`. ## Made-up names Only the functions in these docs exist. A guessed name like `edit_role` or `get_user` fails. Search the docs with **Ctrl K** to find the right one. ## Message event replies to itself A **MESSAGE** event also runs for the bot's own messages. Start with: ```python if user is None or user.is_bot: return ``` ## Still stuck? - Open **Logs β†’ Error Logs** on the dashboard. It shows the real error. - Ask Slushy: press **Fix with Slushy** on the error. See [Using Slushy](https://ccbot.app/docs/getting-started/slushy). Source: https://ccbot.app/docs/concepts/common-mistakes --- # Response > Change the command's own reply from code. Set the text, the embed, where it goes, and what buttons it has. A slash or text command sends one reply after the pre-hook runs. You can type that reply in the **Response** box on the dashboard. The functions on this page let you change it from code instead. These functions work in the **pre-hook** of slash commands and text commands. They do not exist in events. To learn the order in which a command runs, read [How commands run](https://ccbot.app/docs/getting-started/how-commands-run). You do not need to write `await` in front of any function on this page. > **The reply is free:** The command's own reply does not count toward the message limit. Use these functions for the answer. Keep `send_message` for extra messages. See [Limits](https://ccbot.app/docs/concepts/limits). ## set_content Sets the text of the reply. It replaces the text from the **Response** box. ```python set_content(content) ``` ```python roll = randomNumber(1, 6) set_content(f"{user.mention} rolled a **{roll}**!") ``` > **Note:** `set_content` is for slash commands only. In a text command, write the text in the **Response** box and use `{ }` variables in it. ## set_embed Sets the embed of the reply. ```python set_embed(embed) ``` | Argument | What it does | |---|---| | `embed` | The ID of an embed you saved on the dashboard, an embed object, or a `Message(...)`. | ```python # A saved embed. Its { } variables are filled in when the reply is sent. set_embed("welcome-card") # An embed built in code set_embed(Message(title="Your balance", description=f"{coins} coins", color="#F1C40F")) ``` In a text command, `set_embed` takes a saved embed ID only. ## set_response_channel Sends the reply to another channel instead of the channel where the command ran. ```python set_response_channel(channel) ``` `channel` is a channel object, a channel ID (a number), or the exact channel name. It must be a text channel or a thread. ```python set_response_channel("announcements") ``` The person who ran the command still gets a short answer in the first channel. It says *"Command executed βœ…"*. Change that text with `set_response_message`. ## set_dm_response Sends the reply as a direct message. ```python set_dm_response(member=None) ``` With no argument, the reply goes to the person who ran the command. Pass a member to send it to someone else. ```python set_dm_response() set_response_message("Check your DMs!") ``` When the DM fails, for example because the member has DMs turned off, the bot posts *"Failed to DM @member"* in the channel. ## set_response_message Sets the short answer shown in the channel when the reply goes somewhere else. It only matters together with `set_response_channel` or `set_dm_response`. ```python set_response_message(message) ``` `message` is text, or a `Message(...)` with text and an embed. ```python set_response_channel("staff-reports") set_response_message("Thanks! Your report was sent to the staff.") ``` ## set_components Adds buttons and menus to the reply. ```python set_components(builder) ``` `builder` is a `ComponentBuilder`. Clicks go to a handler that you make on the dashboard, or to an `on_click` function. See [Components](https://ccbot.app/docs/reference/components). ```python c = ComponentBuilder() c.add_link_button("Open website", "https://example.com") set_components(c) ``` ## set_view Adds a View to the reply. A View is a set of buttons and menus whose clicks are handled inside the same command. ```python set_view(view) ``` The bot sends the reply, then waits for clicks. The post-hook runs when the View has finished. Do not call `view.wait()` yourself after `set_view`. ```python view = Confirm() view.interaction = interaction set_view(view) set_content("Are you sure?") ``` See [Views](https://ccbot.app/docs/reference/views) for a full example. ## add_attachment Adds a file to the reply. Call it more than once to add more files. ```python add_attachment(file, filename=None) ``` | Argument | What it does | |---|---| | `file` | A file from `load_file(...)` or `render_template(...)`. | | `filename` | A new name for the file, like `"card.png"`. | ```python card = await render_template("rank-card", name=user.display_name, level=5) add_attachment(card) ``` ## end Stops the reply and the post-hook from running. ```python end() ``` ```python if user.is_bot: end() ``` > **end() does not stop the pre-hook:** The lines below `end()` still run. To stop at once and show a message, use `set_error("text")` instead. See [Checks](https://ccbot.app/docs/reference/checks). `end` is for slash commands only. ## set_error_log_channel Picks a channel where the bot posts errors from this run, like an error in the post-hook. ```python set_error_log_channel(channel) ``` ```python set_error_log_channel("bot-errors") ``` Errors also show on the dashboard under **Logs β†’ Error Logs**. ## load_embed Loads an embed you saved on the dashboard. Its `{ }` variables are filled in. ```python load_embed(embed_id, **values) ``` | Argument | What it does | |---|---| | `embed_id` | The ID of the saved embed. | | `**values` | Extra values for the embed's variables. They win over variables with the same name. | ```python embed = load_embed("level-up", level=12) send_message("level-ups", embed=embed) ``` `load_embed` works in commands and events. It fails with *"Embed with id: … not found"* when the ID is wrong. ## load_components Loads buttons and menus you saved on the dashboard under **Components**. ```python load_components(component_id, **values) ``` ```python send_message(channel, "Pick your roles:", components=load_components("role-panel")) ``` Pass extra values the same way as `load_embed`. Source: https://ccbot.app/docs/reference/response --- # Messages > Send, edit, delete and react to messages from a command or event. These functions work in commands (pre-hook and post-hook), events and component handlers. You do not need to write `await` in front of them. The bot adds it for you. Writing it is fine too. > **The reply is free:** The command's own reply (the **Response** box, or `set_content`) does not count toward any limit. Use `send_message` only for **extra** messages, like a log in another channel. ## Limits Each run of a command or event can send a fixed number of messages. Pre-hook and post-hook share one count. | Function | Free | Premium | |---|---|---| | `send_message` + `forward_message` (together) | 3 | 5 | | `send_dm` | 2 | 2 | Going over stops the script with *"Maximum messages sent. Can't send more."* ## send_message Sends a message to a channel in this server. Returns the sent message. ```python send_message( channel, content=None, embed=None, message=None, components=None, attachments=None, everyone_mentions=True, role_mentions=True, user_mentions=True, reply=None, ) ``` | Argument | Type | What it does | |---|---|---| | `channel` | channel, ID or name | Where to send. | | `content` | text | The message text. | | `embed` | embed | One embed, for example from `load_embed("id")`. | | `message` | `Message` | Text and embed together, built with `Message(...)`. | | `components` | `ComponentBuilder` or `View` | Buttons and menus. | | `attachments` | list | Files, from `load_file(...)` or `render_template(...)`. | | `everyone_mentions` | yes / no | Set `False` so `@everyone` does not ping. | | `role_mentions` | yes / no | Set `False` so role mentions do not ping. | | `user_mentions` | yes / no | Set `False` so user mentions do not ping. | | `reply` | message ID | Send as a reply to that message. | **Example: log who used a command** ```python log = get_channel("mod-log") send_message(log, f"{user.mention} used /{command_name} in {channel.mention}") ``` **Example: a message with an embed** ```python send_message(channel, message=Message( title="Server rules", description="1. Be kind\n2. No spam", color="#5865F2", )) ``` > **Watch out:** Do not use `channel.send(...)`. It is blocked. Always use `send_message`. ## send_dm Sends a direct message to a member. Fails when the member has DMs turned off. ```python send_dm(member, content=None, embed=None, message=None) ``` ```python send_dm(user, "Thanks for joining the giveaway!") ``` `member` can be a member or a member ID. ## edit_message Changes a message the bot sent before. ```python edit_message( channel, message_id, content=None, embed=None, message=None, components=None, attachments=None, ) ``` ```python msg = send_message(channel, "Counting…") edit_message(channel, msg.id, "Done! 3 of 3 checked.") ``` > **Note:** Discord only lets a bot edit its own messages. ## delete_message Deletes a message. ```python delete_message(channel_id, message_id) ``` ```python # In a message event: remove messages that contain a blocked word if "badword" in message.content.lower(): delete_message(channel_id, message_id) ``` The bot needs **Manage Messages** to delete other people's messages. ## forward_message Forwards a message into another channel, like Discord's own **Forward** button. ```python forward_message(channel, message, from_channel=None) ``` | Argument | What it does | |---|---| | `channel` | Where the forwarded copy goes. | | `message` | A message, or a message ID. | | `from_channel` | Where the message is. Needed only when `message` is an ID. | ```python forward_message("starboard", message) forward_message("starboard", 1290000000000000000, from_channel="general") ``` Both channels must be in this server. It counts toward the `send_message` limit. ## get_message Fetches a message by its ID. Returns `None` when the channel is not found. ```python get_message(channel_id, message_id) ``` ```python msg = get_message(channel.id, 1290000000000000000) set_content(f"That message says: {msg.content}") ``` ## purge_messages Deletes many messages at once. ```python purge_messages(channel_id, count, _filter=None, skip=0) ``` | Argument | What it does | |---|---| | `count` | How many messages to delete. | | `_filter` | A function. Only messages where it returns `True` are deleted. | | `skip` | Skip this many recent messages first. | ```python # Delete the last 20 messages from bots purge_messages(channel.id, 20, lambda m: m.author.is_bot) ``` > **Watch out:** Discord only bulk-deletes messages newer than **14 days**. Older ones are skipped. The bot needs **Manage Messages** and **Read Message History**. ## add_reactions Adds one or more reactions to a message. ```python add_reactions(message, *emojis) ``` ```python msg = send_message(channel, "Do you like pizza?") add_reactions(msg, "πŸ‘", "πŸ‘Ž") ``` For a custom emoji from your server, pass its name: `add_reactions(msg, "pepe_happy")`. ## remove_reactions Removes reactions one user added. ```python remove_reactions(message, user, *emojis) ``` ## remove_all_reactions Removes every reaction from a message, or every reaction of one emoji. ```python remove_all_reactions(message, emoji=None) ``` ## get_reaction_users Lists the users who reacted with an emoji. ```python get_reaction_users(message, emoji) ``` ```python msg = get_message(channel.id, giveaway_id) users = get_reaction_users(msg, "πŸŽ‰") users = [u for u in users if not u.is_bot] # skip the bot's own reaction winner = random(users) set_content(f"The winner is {winner.mention}!") ``` ## get_reaction_count Counts the reactions of one emoji on a message. Returns `0` when there are none. ```python get_reaction_count(message, emoji) ``` ## mention_message Makes a link that jumps to a message. ```python mention_message(channel_id, message_id) ``` ```python link = mention_message(channel.id, msg.id) send_message("mod-log", f"New report: {link}") ``` Source: https://ccbot.app/docs/reference/messages --- # Embeds > Build embeds in code, load the embeds you saved on the dashboard, and send files. An **embed** is the box Discord draws around a message, with a colored edge, a title, fields and images. You can make embeds in two ways: - **On the dashboard.** Open **Embeds**, design it, save it. Then load it in code with `load_embed`. - **In code.** Build one with `Message(...)`. A built embed goes anywhere a message goes: `set_embed`, `send_message(message=...)`, `send_dm(message=...)` and paginator pages. See [Messages](https://ccbot.app/docs/reference/messages) and [Response](https://ccbot.app/docs/reference/response). None of these functions need `await`. ## Message Builds a message: text, an embed, or both. Every argument is optional. ```python Message( content=None, title=None, url=None, color=None, description=None, fields=None, image=None, thumbnail=None, footer=None, footer_icon=None, timestamp=None, author=None, author_icon=None, author_url=None, embed=None, populate={}, ) ``` | Argument | What it does | |---|---| | `content` | Plain text above the embed. | | `title` | The embed title. | | `url` | Makes the title a link. | | `color` | The side color, as a hex code like `"#5865F2"`. | | `description` | The main text of the embed. | | `fields` | A list of fields. Each field is `(name, value, inline)`. | | `image` | A big image at the bottom. A link, a rendered image, or a loaded file. | | `thumbnail` | A small image in the top right. Same kinds as `image`. | | `footer` | Small text at the bottom. | | `footer_icon` | A small image next to the footer. | | `timestamp` | A date shown next to the footer. Use `get_time("now")`. | | `author` | Small text above the title. | | `author_icon` | A small image next to the author. | | `author_url` | Makes the author a link. | | `embed` | A ready embed to send as is. | | `populate` | Fills `{name}` placeholders in the text. See below. | The embed part is only made when you set at least one embed field. `Message(content="Hi")` sends plain text. **Example: a profile card** ```python set_response_message(Message( title=user.display_name, color="#5865F2", thumbnail=user.avatar_url, fields=[ ("Joined", f"", True), ("Roles", str(len(user.role_names)), True), ], footer=f"ID: {user.id}", )) ``` **Example: show a rendered image inside the embed** ```python card = await render_template("rank-card", name=user.display_name, level=5) send_message(channel, message=Message(title="Your rank", image=card)) ``` You do not need `attachments=` here. The image uploads with the message. ### Add fields one at a time ```python msg = Message(title="Top players") msg.add_field("1st", "Alex", True) msg.add_field("2nd", "Sam", True) ``` `add_field(name, value, inline=False)` skips a field when the name or value is empty. ### Fill placeholders with populate `populate` replaces `{name}` in the text with a value. Unknown names stay as written. ```python Message(description="Hello {who}!", populate={"who": user.mention}) ``` ## load_embed Loads an embed you saved on the dashboard. Variables in it, like `{user.mention}`, are filled in. ```python load_embed(embed_id, **values) ``` | Argument | What it does | |---|---| | `embed_id` | The embed's ID from the dashboard. | | `**values` | Extra values for the embed's placeholders. They win over your variables. | ```python set_embed(load_embed("welcome-card")) ``` ```python # Fill {card} in the saved embed with a rendered image card = await render_template("welcome", avatar=user.avatar_url) send_message(channel, embed=load_embed("welcome-card", card=card)) ``` Fails with *"Embed with id: ... not found"* when the ID is wrong. ## load_components Loads buttons and menus you saved under **Components** on the dashboard. ```python load_components(id) ``` ```python set_components(load_components("support-buttons")) ``` ## Attachment A file to send with a message. ```python Attachment(file, filename) ``` | Argument | What it does | |---|---| | `file` | The file's bytes. | | `filename` | The name Discord shows, like `"report.txt"`. | ```python data = "Name,Coins\nAlex,120\n".encode() send_message(channel, "Here is the list.", attachments=[Attachment(data, "coins.csv")]) ``` To send a file from a link, use `load_file` instead. See [Web and files](https://ccbot.app/docs/reference/web-and-files). ## Embed methods Sometimes you get a raw Discord embed, for example from `message.embeds`. You can read and change it with these: | Name | What it does | |---|---| | `title`, `description`, `url`, `color`, `timestamp` | Read the main parts. | | `author`, `footer`, `image`, `thumbnail`, `fields` | Read the other parts. | | `add_field(name, value, inline=False)` | Adds a field. | | `edit_field(index, ...)` | Changes a field. | | `remove_field(index)` | Removes a field. | | `set_author(name=..., url=..., icon=...)` | Sets the author line. | | `set_footer(text=..., icon=...)` | Sets the footer. | | `set_image(url)` | Sets the big image. | | `set_thumbnail(url)` | Sets the small image. | | `clear_fields()` | Removes all fields. | | `total_length()` | Counts every character in the embed. | > **Tip:** Use `Message(...)` or `load_embed(...)` when you can. They are shorter and handle images for you. ## Discord limits for embeds | Part | Most allowed | |---|---| | Title | 256 characters | | Description | 4,096 characters | | Fields | 25 | | Field name | 256 characters | | Field value | 1,024 characters | | Footer | 2,048 characters | | Whole embed | 6,000 characters | Source: https://ccbot.app/docs/reference/embeds --- # Members and roles > Find members, give and take roles, and kick, ban, time out or move people. These functions work in commands, events and component handlers. Most of them need no `await`. The bot adds it for you. The table shows the ones where you must write `await` yourself. | Write `await` yourself | No `await` needed | |---|---| | `get_or_fetch_member`, `getch_member`, `edit_member` | everything else on this page | > **The bot needs permission:** Every action here needs the matching Discord permission, like **Manage Roles** or **Ban Members**. The bot's own role must also be **above** the roles and members it changes. Discord rule. ## Limits Each run of a command or event can change a fixed number of roles. Pre-hook and post-hook share one count. | Function | Free | Premium | |---|---|---| | `add_roles` | 5 | 15 | | `remove_roles` | 5 | 15 | ## get_member Finds a member by ID. Looks only in the bot's memory, so it is fast. Returns `None` when the member is not found. ```python get_member(member_id) ``` `member_id` must be a number. Text like `"1234"` does not work. Use `int(...)` first. ```python member = get_member(int(user_id_text)) if member is None: set_error("That member is not in the server.") ``` ## get_or_fetch_member Finds a member by ID. When the bot does not have them in memory, it asks Discord. Returns `None` when the member is not in the server. ```python await get_or_fetch_member(member_id) ``` `getch_member` is a short name for the same function. ```python member = await getch_member(1290000000000000000) ``` ## list_members Lists the members who have a role. ```python list_members( role, ignored_roles=None, as_mention=True, role_cond="any", ignore_cond="any", ) ``` | Argument | What it does | |---|---| | `role` | A role, role ID or role name. A list of them also works. | | `ignored_roles` | A list of role IDs. Members with these roles are left out. | | `as_mention` | `True` gives mentions like `@Alex`. `False` gives member objects. | | `role_cond` | `"any"`: has any of the roles. `"all"`: has all of them. | | `ignore_cond` | `"any"` or `"all"`, the same way for `ignored_roles`. | ```python staff = list_members("Staff") set_content(f"Our staff: {', '.join(staff)}") ``` ## add_roles Gives one or more roles to a member. ```python add_roles(member, *roles, after=None) ``` | Argument | What it does | |---|---| | `member` | The member. | | `*roles` | Roles, role IDs or role names. Add as many as you like. | | `after` | A time like `"10 minutes"` or `"2 days"`. The role is added after that time. | ```python add_roles(user, "Verified", "Member") ``` > **Watch out:** Do not use `member.add_role(...)`. It is blocked. Always use `add_roles`. ## remove_roles Takes one or more roles away from a member. Same arguments as `add_roles`. ```python remove_roles(member, *roles, after=None) ``` **Example: a temporary role** ```python add_roles(user, "Event Pass") remove_roles(user, "Event Pass", after="24 hours") ``` A delayed change is saved, so it still happens after the bot restarts. ## has_role Checks if a member has **all** the roles you list. Returns `True` or `False`. ```python has_role(member, *roles) ``` ```python if has_role(user, "VIP"): set_content("Welcome to the VIP lounge!") ``` ## has_any_role Checks if a member has **at least one** of the roles. Returns `True` or `False`. ```python has_any_role(member, *roles) ``` ## has_permission Checks if a member has **all** the permissions you list. Returns `True` or `False`. ```python has_permission(member, *permissions) ``` Write permission names in lowercase with `_`, like `"manage_messages"`, `"kick_members"` or `"administrator"`. A wrong name stops the script and lists the valid names. ```python if has_permission(user, "manage_messages"): purge_messages(channel.id, 10) ``` ## has_any_permission Checks if a member has **at least one** of the permissions. Returns `True` or `False`. ```python has_any_permission(member, *permissions) ``` ## get_role Finds a role. Returns `None` when there is no match. ```python get_role(role) ``` `role` is a role ID (a number) or the exact role name. ```python vip = get_role("VIP") set_content(f"{vip.mention} has {len(list_members(vip))} members") ``` ## get_roles Finds several roles at once. Returns the ones that match. Names or IDs with no match are skipped. ```python get_roles(*roles) ``` To list every role in the server, use `server.get_roles().values()`. ## create_role Makes a new role. Returns the role. ```python create_role(name, **options) ``` Extra options go to Discord, for example `color`, `hoist` (show separately) and `mentionable`. ```python role = create_role("Event Winner", color=0xF1C40F, hoist=True) add_roles(user, role) ``` ## update_role Changes a role. Returns the changed role. ```python update_role(role, **options) ``` ```python update_role("Event Winner", name="Last Event Winner") ``` ## delete_role Deletes a role. ```python delete_role(role) ``` ## change_nick Changes a member's nickname. The bot cannot change the server owner's nickname. ```python change_nick(member, nick) ``` ```python change_nick(user, f"[AFK] {user.display_name}") ``` ## edit_member Changes several things about a member at once. ```python await edit_member(member, **options) ``` Options go to Discord, for example `nickname`, `mute`, `deaf` and `voice_channel`. ## kick_member Kicks a member from the server. They can join again with an invite. ```python kick_member(member, reason=None) ``` ```python kick_member(target, reason=f"Kicked by {user.display_name}") ``` ## ban_member Bans a member from the server. ```python ban_member(member, reason=None) ``` ## timeout_member Times out a member. They cannot talk or react until it ends. ```python timeout_member(member, time) ``` `time` is text like `"10 minutes"` or `"1 day"`. The longest timeout is **28 days**. ```python timeout_member(target, "1 hour") ``` ## remove_timeout Ends a member's timeout early. ```python remove_timeout(member) ``` ## mute_member Server-mutes a member in voice. `unmute_member` undoes it. ```python mute_member(member) unmute_member(member) ``` ## deaf_member Server-deafens a member in voice. `undeaf_member` undoes it. ```python deaf_member(member) undeaf_member(member) ``` ## move_member Moves a member to another voice or stage channel. With no channel, it disconnects them from voice. ```python move_member(member, channel=None) ``` ```python move_member(user, "AFK") ``` ## get_voice_state Gets a member's voice status, for example which voice channel they are in. Returns `None` when they are not in voice. ```python get_voice_state(member) ``` ```python state = get_voice_state(user) if state is None: set_error("Join a voice channel first.") ``` ## get_user_mentions Finds the users mentioned in a message or a text. ```python get_user_mentions(message) ``` `get_role_mentions` and `get_channel_mentions` do the same for roles and channels. ## get_permission Turns permission names into one permissions value. Useful when you create roles or channels. ```python get_permission(*permissions, safe=False) ``` With `safe=True`, wrong names are skipped instead of stopping the script. ```python create_role("Helpers", permissions=get_permission("manage_messages", "kick_members")) ``` Source: https://ccbot.app/docs/reference/members-and-roles --- # Channels > Find, create, change, lock and delete channels and threads. These functions work in commands, events and component handlers. When a function asks for a `channel`, you can pass: - a channel object, like the `channel` variable; - a channel ID, as a number; - the exact channel name, like `"general"`. A channel ID written as text, like `"1234"`, is read as a name and is not found. Use `int(...)` first. | Write `await` yourself | No `await` needed | |---|---| | `lock_channel`, `unlock_channel`, `set_slowmode` | everything else on this page | > **The bot needs permission:** Creating, changing and deleting channels needs **Manage Channels**. Locking needs permission to edit the channel's permissions. ## get_channel Finds one channel or thread. Returns `None` when there is no match. ```python get_channel(channel) ``` ```python log = get_channel("mod-log") if log is None: set_error("Make a #mod-log channel first.") ``` ## get_channels With no arguments, returns every channel in the server, without categories and threads. With arguments, returns the channels that match. ```python get_channels(*channels) ``` ```python count = len(get_channels()) set_content(f"This server has {count} channels.") ``` ## get_text_channels Returns every text channel. Announcement, forum and media channels are included. Threads are not. ```python get_text_channels() ``` ## get_voice_channels Returns every voice and stage channel. ```python get_voice_channels() ``` ## is_thread Checks if a channel is a thread. Returns `True` or `False`. ```python is_thread(channel) ``` ## create_text_channel Makes a text channel. Returns the new channel. ```python create_text_channel(name, **options) ``` Options go to Discord, for example `topic`, `category`, `nsfw` and `permission_overwrites`. ```python ticket = create_text_channel(f"ticket-{user.username}", topic=f"Ticket for {user.display_name}") send_message(ticket, f"{user.mention} a staff member will be with you soon.") ``` ## create_voice_channel Makes a voice channel. Returns the new channel. ```python create_voice_channel(name, **options) ``` ## create_category Makes a category. Returns the new category. ```python create_category(name, **options) ``` ## create_thread Makes a thread. Returns the new thread. ```python create_thread( channel, name=None, message=None, type="public", initial_message=None, ) ``` | Argument | What it does | |---|---| | `channel` | The channel to make the thread in. | | `name` | The thread name. Default is `"Unnamed"`. | | `message` | A message or message ID. The thread starts from that message. | | `type` | `"public"` or `"private"`. A thread from a message is always public. | | `initial_message` | Text the bot posts in the new thread. | ```python create_thread(channel, name=f"Help for {user.display_name}", type="private", initial_message=f"{user.mention} describe your problem here.") ``` A message can have only one thread. A second try fails with a clear message. ## update_channel Changes a channel. Returns the changed channel. ```python update_channel(channel, **options) ``` ```python update_channel(channel, name="closed-ticket", topic="Closed") ``` ## delete_channel Deletes a channel. ```python delete_channel(channel) ``` > **Watch out:** A deleted channel cannot come back. Its messages are lost. ## lock_channel Stops `@everyone` from sending messages in a channel. Other permissions stay as they are. ```python await lock_channel(channel=None, reason=None) ``` With no channel, it locks the channel where the command ran. ```python await lock_channel(reason=f"Locked by {user.display_name}") set_content("This channel is locked.") ``` ## unlock_channel Lets `@everyone` send messages again. ```python await unlock_channel(channel=None, reason=None) ``` ## set_slowmode Sets how many seconds each member must wait between messages. ```python await set_slowmode(seconds, channel=None, reason=None) ``` `seconds` goes from `0` (off) to `21600` (6 hours). Bigger numbers are cut to 6 hours. ```python await set_slowmode(10) set_content("Slowmode is on: one message every 10 seconds.") ``` ## PermissionOverwrites Sets who can see or use a new channel. Pass it as `permission_overwrites=` to `create_text_channel`, `create_voice_channel`, `create_category`, `create_thread` or `update_channel`. ```python perms = PermissionOverwrites() perms.add_role(role, allow=..., deny=...) perms.add_member(member, allow=..., deny=...) perms.remove(role_or_member) ``` `allow` and `deny` take a permissions value from `get_permission(...)`. **Example: a private ticket channel** ```python perms = PermissionOverwrites() # @everyone has the same ID as the server perms.add_role(server.id, deny=get_permission("view_channel")) perms.add_member(user, allow=get_permission("view_channel", "send_messages")) perms.add_role(get_role("Staff"), allow=get_permission("view_channel", "send_messages")) ticket = create_text_channel(f"ticket-{user.username}", permission_overwrites=perms) ``` See the [ticket system guide](https://ccbot.app/docs/guides/tickets) for the full version. Source: https://ccbot.app/docs/reference/channels --- # Storage > Save data between runs. Variables for small values, databases for tables, Mongo for big or nested data, and locks. A command forgets everything when it finishes. To keep data, like coins, levels or warnings, save it with one of these. | Use | For | Example | |---|---|---| | `server_var` | One value for the whole server | A counter, a "giveaway is open" flag | | `member_var` | One value per member | Coins, XP, warnings | | `temp_var` | A value kept in memory only | A short-lived flag | | `load_database` | A table you made on the dashboard | A shop, a list of tickets | | `SlashMongo` | Big or nested data | Inventories, leaderboards, logs | Not sure which to use? Read [Saving data](https://ccbot.app/docs/concepts/saving-data). ## server_var Reads a server variable. Returns `default` when the variable does not exist. ```python server_var(name, default=None) ``` ```python count = int(server_var("counter", 0)) ``` > **Variables come back as text:** Saved values are stored as text. `"5" + 1` fails. Turn the value into a number first with `int(...)` or `float(...)`. You can also see and edit server variables on the dashboard under **Variables**. ## update_server_var Saves a server variable. Makes it when it does not exist. ```python update_server_var(name, value) ``` ```python count = int(server_var("counter", 0)) + 1 update_server_var("counter", count) set_content(f"This command was used {count} times.") ``` ## member_var Reads a variable saved for one member. Returns `default` when it does not exist. ```python member_var(name, default=None, member=None) ``` With no `member`, it uses the person who ran the command or caused the event. ```python coins = int(member_var("coins", 0)) set_content(f"You have {coins} coins.") ``` ## update_member_var Saves a variable for one member. ```python update_member_var(name, value, member=None) ``` ```python coins = int(member_var("coins", 0, member=target)) update_member_var("coins", coins + 100, member=target) ``` ## temp_var Reads a value kept in the bot's memory. Returns `default` when it is not set. ```python temp_var(name, default=None) ``` > **Note:** Temp values are lost when the bot restarts. A value of `0`, `""` or `False` also gives back `default`. ## set_temp_var Keeps a value in memory for this server. ```python set_temp_var(name, value) ``` ## load_database Opens a table you made on the dashboard under **Variables β†’ Databases**. ```python db = load_database(database_id) ``` The database ID is on the dashboard. A wrong ID stops the script with *"Database not found."* Each row is a dictionary. It has an `id` and one key per column. Columns you do not fill in get their default value. ### db.find Returns every row that matches, as a list. ```python db.find(where=None, sort=None) ``` ### db.find_first Returns the first row that matches, or `None`. ```python db.find_first(where=None, sort=None) ``` ### db.insert Adds a row. Returns the new row with its `id`. ```python db.insert(data) ``` ### db.update Changes every row that matches. Returns `{"updated": count}`. ```python db.update(where, data) ``` ### db.delete Deletes every row that matches. Returns `{"deleted": count}`. ```python db.delete(where) ``` ### Filters `where` is a dictionary. Give a value to match it exactly, or a dictionary of filters. | Filter | Matches when the column… | |---|---| | `gt`, `gte` | is more than, or at least, the value | | `lt`, `lte` | is less than, or at most, the value | | `not` | is not the value | | `contains`, `not_contains` | has the text or item in it, or not | | `startswith`, `endswith` | starts or ends with the text | | `in`, `not_in` | is one of the values in a list, or not | | `has_all`, `has_any` | is a list that has all, or any, of the values | `sort` is a dictionary of column names and `"asc"` or `"desc"`. **Example: a shop** ```python shop = load_database("shop_items") cheap = shop.find(where={"price": {"lte": 500}}, sort={"price": "asc"}) set_content(listify([f"{item['name']}: {item['price']} coins" for item in cheap])) shop.insert({"name": "Golden Hat", "price": 1200}) shop.update({"name": "Golden Hat"}, {"price": 999}) shop.delete({"name": "Golden Hat"}) ``` ## SlashMongo Opens a MongoDB collection for this server. Use it for big or nested data. Each server has its own databases, so names never clash with other servers. ```python col = SlashMongo(db_name, collection_name="main") ``` Every method needs `await`. | Method | What it does | |---|---| | `await col.insert_one(data)` | Adds one document. | | `await col.insert_many(list)` | Adds many documents. | | `await col.find_one(filter)` | Returns the first match, or `None`. | | `await col.find(filter)` | Returns every match, as a list. | | `await col.update_one(filter, change)` | Changes the first match. | | `await col.update_many(filter, change)` | Changes every match. | | `await col.delete_one(filter)` | Deletes the first match. | | `await col.delete_many(filter)` | Deletes every match. | | `await col.count(filter=None)` | Counts matches. With no filter, counts all. | | `await col.aggregate(pipeline)` | Runs a MongoDB aggregation. | Filters and changes use normal MongoDB syntax, like `{"$inc": {"coins": 10}}`. **Example: an inventory** ```python inv = SlashMongo("economy", "inventory") await inv.update_one( {"user_id": str(user.id)}, {"$push": {"items": "Golden Hat"}}, upsert=True, ) doc = await inv.find_one({"user_id": str(user.id)}) set_content(f"Your items: {', '.join(doc['items'])}") ``` `find` and `aggregate` return at most **10,000** documents on free servers and **500,000** on premium. You can look at Mongo data on the dashboard under **Mongo Databases**. ## acquire_lock Stops the same thing running twice at once. When the lock is already taken, the script stops with an error. ```python acquire_lock(key, count=1, error=None) ``` | Argument | What it does | |---|---| | `key` | A name for the lock. | | `count` | How many runs may hold it at the same time. | | `error` | The message to show when the lock is taken. | ## release_lock Frees a lock so the next run can take it. ```python release_lock(key) ``` **Example: one purchase at a time per member** ```python key = f"buy-{user.id}" acquire_lock(key, error="Your last purchase is still going. Wait a moment.") coins = int(member_var("coins", 0)) update_member_var("coins", coins - 100) release_lock(key) ``` > **Watch out:** Always release the lock. When the script stops between the two calls, for example with `set_error`, the lock stays taken until the bot restarts. Source: https://ccbot.app/docs/reference/storage --- # Checks > Cooldowns, permission checks, role and channel rules, and custom error messages. A check stops the command when a rule is not met. The person sees an error message, and nothing else runs. Put checks at the **top** of the pre-hook, before any work. These functions work in slash commands, text commands and component handlers. You do not need to write `await` in front of them. > **Tip:** You do not need `if` around a check. Each one stops the command by itself, with a good message. ## cooldown Makes a member wait before using the command again. ```python cooldown(time, key=None, error_message=None, count=1) ``` | Argument | What it does | |---|---| | `time` | How long to wait, in words: `"30 seconds"`, `"1 hour"`, `"1 day"`. | | `key` | Who shares the cooldown. Default is the member who ran it. | | `error_message` | Your own message. It can use the placeholders below. | | `count` | How many uses are allowed before the wait starts. | ```python cooldown("1 day", error_message="You already claimed today. Come back %relative%.") coins = int(member_var("coins", 0)) + 100 update_member_var("coins", coins) set_content(f"You got 100 coins! You now have {coins}.") ``` **Placeholders** for `error_message`: | Placeholder | Becomes | Shows in Discord as | |---|---|---| | `%time%` | `5 minutes` | plain text | | `%relative%` | a Discord time tag | **in 5 minutes**, counting down by itself | | `%at%` | a Discord time tag | **9:46 PM**, in each reader's time zone | | `%timestamp%` | `1788731206` | the raw number of seconds | Use `%time%` for "try again after %time%". Use `%relative%` for "try again %relative%". **Who shares the cooldown** | `key` | Effect | |---|---| | not set | Each member has their own cooldown. | | `server.id` | One cooldown for the whole server. | | `channel.id` | One cooldown per channel. | | `[user.id, channel.id]` | One per member, per channel. | > **In a component handler:** In a component handler, `cooldown(time, key=None)` does not stop the script. It returns `False` when the click may go on, or the time left as text when it must wait. > > ```python > left = cooldown("10 seconds") > if left: > set_error(f"Slow down. Try again in {left}.") > ``` ## require_permission Lets the command run only for members with **all** these Discord permissions. Administrators always pass. ```python require_permission(*permissions, error_message=None) ``` ```python require_permission("ban_members") ``` Write permission names in lowercase with `_`. Common ones: `administrator`, `manage_server`, `manage_roles`, `manage_channels`, `manage_messages`, `kick_members`, `ban_members`, `timeout_members`, `mention_roles`, `send_messages`, `view_channel`. A wrong name stops the script and lists every valid name. ## allowed_roles Lets the command run only for members with **at least one** of these roles. ```python allowed_roles(*roles, error_message=None) ``` ```python allowed_roles("Staff", "Moderator", error_message="Only staff can use this.") ``` Roles can be role objects, role IDs or exact role names. ## banned_roles Stops members with **any** of these roles from running the command. ```python banned_roles(*roles, error_message=None) ``` ```python banned_roles("Muted") ``` ## allowed_channels Lets the command run only in these channels. ```python allowed_channels(*channels, error_message=None) ``` ```python allowed_channels("bot-commands", error_message="Use this in #bot-commands.") ``` ## banned_channels Stops the command in these channels. ```python banned_channels(*channels, error_message=None) ``` ## set_error Stops the script at once and shows your message as an error. ```python set_error(message) ``` ```python amount = int(amount) if amount <= 0: set_error("The amount must be more than 0.") ``` `set_error` works everywhere, including events. In a command, the message is the reply. > **Tip:** You can also set some rules on the dashboard, with no code. Open a command and pick **Allowed roles** and **Allowed channels**. Source: https://ccbot.app/docs/reference/checks --- # Views > Buttons and menus whose clicks are handled inside the same command. The best choice for games, confirmations and menus. A **View** is a set of buttons and menus. When someone clicks, a function in the same command runs. That function can see every variable of the command. Use a View when the clicks happen while the command is running: a "Yes / No" question, a game, a page of choices. Use a [component handler](https://ccbot.app/docs/reference/components) only for buttons that must keep working for days, like a role panel. Read [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus) to choose between the two. ## A full example ```python # pre-hook class Confirm(View): def __init__(self, user_id): self.user_id = user_id # only this member can click self.choice = None @button(label="Yes", style="success", custom_id="yes") async def yes(self, interaction, component): await defer(interaction, action="update") self.choice = "yes" self.disable_all_components() await self.update_view() self.stop() @button(label="No", style="danger", custom_id="no") async def no(self, interaction, component): await defer(interaction, action="update") self.choice = "no" self.disable_all_components() await self.update_view() self.stop() view = Confirm(user_id=user.id) view.interaction = interaction # lets update_view() change the reply set_view(view) set_content("Delete all your saved notes?") ``` ```python # post-hook if view.choice == "yes": send_message(channel, "Deleted.") elif view.choice is None: send_message(channel, "No answer. Nothing was deleted.") ``` What happens: 1. The pre-hook makes the View and calls `set_view`. 2. The bot sends the reply with the buttons and waits for clicks. 3. A click runs `yes` or `no`. `self.stop()` ends the View. 4. The post-hook runs. `view.choice` holds the answer. > **Rules for Views:** - Never call `super().__init__()` in your View. > - After `set_view(view)`, do not call `view.wait()`. The bot does it. > - Callbacks always look like `async def name(self, interaction, component)`. ## View attributes Set these on your View. | Attribute | What it does | |---|---| | `user_id` | A member ID, or a list of IDs, allowed to click. `None` lets anyone click. | | `interaction` | Set to `interaction` so `update_view()` changes the command's reply. | | `message` | Set when the View is on a message you sent. `send_message` sets it for you. | | `guild_id` | Set for you by `set_view` and `send_message`. | ## View methods ### self.stop Ends the View. `wait()` then returns `"stopped"`. ```python self.stop() ``` `stop()` also ends the callback. Lines after it do not run, so call it last. ### self.update_view Shows your changes to the buttons on the message. Needs `await`. ```python await self.update_view() ``` ### self.update_component Changes one button or menu. Use the `custom_id` you wrote in the decorator. ```python self.update_component(custom_id, **changes) ``` ```python self.count += 1 self.update_component("add", label=f"Clicked {self.count} times") await self.update_view() ``` ### self.disable_all_components Greys out every button and menu. Pass `ignore=[...]` to keep some active. ```python self.disable_all_components(ignore=None) ``` ### view.wait Waits for clicks until the View stops or nobody clicks for `timeout` seconds. Needs `await`. ```python await view.wait(timeout=180, disable_on_timeout=False, ignore_errors=False) ``` | Argument | What it does | |---|---| | `timeout` | Seconds with no clicks before the View ends. Each click starts the clock again. | | `disable_on_timeout` | Grey out the buttons when time runs out. | | `ignore_errors` | Keep the View going when a callback has an error. | Returns `"stopped"` or `"timeout"`. Only call `wait` yourself when you did **not** use `set_view`, for example when the View is on a message from `send_message`: ```python view = Poll() send_message(channel, "Vote now!", components=view) result = await view.wait(timeout=60, disable_on_timeout=True) ``` ## button Adds a button. Put it above an `async def` inside your View. ```python @button( label, custom_id=None, style="primary", is_disabled=False, emoji=..., row=1, url=None, hidden=False, ) ``` | Argument | What it does | |---|---| | `label` | The button text. | | `custom_id` | A name for the button. Needed for `update_component`. | | `style` | `primary` (blue), `secondary` (grey), `success` (green), `danger` (red), `link`. | | `emoji` | An emoji shown on the button. | | `row` | Which row, from 1 to 5. | | `url` | Makes it a link button. Link buttons do not run a callback. | | `hidden` | `True` hides the button until you show it with `update_component`. | A wrong `style` silently becomes `primary`. In signatures on this page, `...` means the argument is optional and has no default. Leave it out when you do not need it. Extra keyword arguments are kept in `component.metadata`, so a callback can read them. ## text_menu Adds a menu with your own options. ```python @text_menu( options, custom_id=None, placeholder=..., min_values=0, max_values=1, is_disabled=False, row=None, hidden=False, ) ``` `options` is a list of `TextOption`: ```python TextOption(label, value, description=..., emoji=..., default=False) ``` **What the member picked is in `interaction.values`.** It is always a list, even with `max_values=1`. ```python class Colors(View): @text_menu( options=[ TextOption(label="Red", value="red", emoji="πŸ”΄"), TextOption(label="Blue", value="blue", emoji="πŸ”΅"), ], custom_id="color", placeholder="Pick a color", ) async def pick(self, interaction, component): await defer(interaction, action="update") self.color = interaction.values[0] self.stop() ``` > **Watch out:** Use `interaction.values`, not `component.values`. `component` is what you declared, not what the member picked. ## role_menu Adds a menu that lists the server's roles. `interaction.values` holds the picked role IDs. ```python @role_menu(custom_id=None, placeholder=..., min_values=0, max_values=1, is_disabled=False, row=None, hidden=False) ``` ## user_menu Adds a menu that lists members. `interaction.values` holds the picked member IDs. Same arguments as `role_menu`. ```python @user_menu(custom_id=None, placeholder=..., min_values=0, max_values=1) ``` ## channel_menu Adds a menu that lists channels. `interaction.values` holds the picked channel IDs. ```python @channel_menu(channel_types, custom_id=None, placeholder=..., min_values=0, max_values=1) ``` `channel_types` is a list from: `text`, `voice`, `category`, `news`, `news-thread`, `public-thread`, `private-thread`, `forum`, `stage`. ## Layout rules Discord limits how buttons and menus fit on a message: - At most **5 rows**. - At most **5 buttons** in a row. - A menu takes a whole row. It cannot share a row with buttons or another menu. The bot stops with a clear message when a View breaks a rule. ## Answering a click Discord wants an answer to every click within **3 seconds**. Start each callback with one of these: | Call | Use when | |---|---| | `await defer(interaction, action="update")` | You change the message the buttons are on. | | `await defer(interaction, ephemeral=True)` | You send a new private message later. | | `await send_modal(form, interaction)` | You open a form. Opening a form is the answer. Do not defer too. | See [Interactions](https://ccbot.app/docs/reference/interactions) and [Modals](https://ccbot.app/docs/reference/modals). Source: https://ccbot.app/docs/reference/views --- # Components > Buttons and menus that keep working after the command ends. Build them with ComponentBuilder. `ComponentBuilder` makes rows of buttons and menus. Unlike a [View](https://ccbot.app/docs/reference/views), its buttons keep working after the command has finished. Use it for panels that people click for days, like a ticket panel or a role menu. A click goes to one of two places: | You set | The click runs | |---|---| | nothing | A **Button Click / Select Menu Change** event on the dashboard whose custom IDs include this button's `custom_id`. | | `on_click` or `on_select` | Your own function, in this script. | Read [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus) to choose between a View and a component. ## ComponentBuilder Makes an empty builder. ```python c = ComponentBuilder(interaction=None) ``` Pass `interaction` when you use `private=True`. Then send the builder with `set_components(c)`, or `send_message(channel, ..., components=c)`. ```python c = ComponentBuilder() c.add_button("Open a ticket", style="primary", custom_id="ticket-open") c.add_link_button("Rules", "https://example.com/rules") send_message("support", "Need help? Press the button.", components=c) ``` Then make an event on the dashboard: **Create β†’ Event**, type **Button Click / Select Menu Change**, custom ID `ticket-open`. Its code runs on every click. ## add_button Adds a button. ```python c.add_button( label, style="primary", emoji=..., is_disabled=False, custom_id=None, on_click=None, row=None, private=False, ) ``` | Argument | What it does | |---|---| | `label` | The button text. | | `style` | `primary`, `secondary`, `success` or `danger`. | | `emoji` | An emoji on the button. Leave it out for none. | | `custom_id` | The name the click carries. Needed for every button that is not a link. | | `on_click` | A `ComponentAction` to run on click. | | `row` | Which row to put it in. Default is the current row. | | `private` | `True`: only the member who ran the command can click. | ## add_link_button Adds a button that opens a website. Link buttons never run code. ```python c.add_link_button(label, url, emoji=..., is_disabled=False, row=None) ``` ## add_text_menu Adds a menu with your own options. ```python c.add_text_menu( options, custom_id=None, placeholder=..., min_values=0, max_values=1, is_disabled=False, on_select=None, row=None, private=False, ) ``` `options` is a list of `TextOption(label, value, description=..., emoji=..., default=False)`. ## add_role_menu Adds a menu that lists the server's roles. ```python c.add_role_menu(custom_id=None, placeholder=..., min_values=0, max_values=1, is_disabled=False, on_select=None, row=None, private=False) ``` ## add_user_menu Adds a menu that lists members. Same arguments as `add_role_menu`. ## add_channel_menu Adds a menu that lists channels. The first argument is a list of channel types: `text`, `voice`, `category`, `news`, `news-thread`, `public-thread`, `private-thread`, `forum`, `stage`. ```python c.add_channel_menu(["text", "voice"], custom_id="pick-channel") ``` ## add_row Starts a new row. Later buttons go into it. ```python c.add_row(row) c.remove_row(row) ``` Rules: at most 5 rows, at most 5 buttons in a row, and a menu takes a whole row. ## ComponentAction Runs your own function when the button or menu is used. ```python ComponentAction( type, ephemeral, callback, clickable=0, clicked=0, auto_defer=True, args=[], ) ``` | Argument | What it does | |---|---| | `type` | `"create"`: the answer is a new message. `"update"`: the answer changes the message with the button. | | `ephemeral` | `True`: the new message is visible to the clicker only. | | `callback` | Your function. | | `clickable` | How many clicks in total are allowed. `0` means no limit. | | `auto_defer` | `True`: the bot answers the click for you before the callback runs. | | `args` | Extra values passed to your function. | A button's callback gets `(interaction, *args)`. A menu's callback gets `(interaction, values, *args)`. ```python async def claim(interaction): add_roles(interaction.member, "Early Bird") await respond_interaction(interaction, "You got the Early Bird role!") c = ComponentBuilder() c.add_button("Claim", custom_id="claim-early", on_click=ComponentAction("create", True, claim, clickable=10)) set_components(c) ``` > **on_click lives in memory:** An `on_click` function is kept in the bot's memory. After the bot restarts, the button no longer runs it. For panels that must work for weeks, use a dashboard event instead. ## Button Makes one button on its own, outside a builder. ```python Button(label, style="primary", emoji=..., is_disabled=False, custom_id=None) ``` ## LinkButton Makes one link button on its own. ```python LinkButton(label, url, emoji=..., is_disabled=False) ``` ## Newer layout parts Discord's newer message layout uses these parts: `TextDisplay`, `Section`, `Thumbnail`, `MediaGallery`, `Container`, `Separator`, `File`, `Spacing` and `Color`. They are passed straight to Discord, so they follow Discord's own rules. For most messages, it is easier to design the layout on the dashboard under **Components**, then load it with [`load_components`](https://ccbot.app/docs/reference/response). Source: https://ccbot.app/docs/reference/components --- # Modals > Open a pop-up form, then read what the member typed. A **modal** is a pop-up form in Discord. It has a title and up to 5 text boxes. You open it in answer to a command or a click. When the member presses **Submit**, a **Modal Submit** event runs with what they typed. For a walk-through, read [Forms](https://ccbot.app/docs/concepts/forms). ## ModalBuilder Makes an empty form. ```python form = ModalBuilder(title, custom_id) ``` | Argument | What it does | |---|---| | `title` | The text at the top of the form. | | `custom_id` | The name the submit carries. Your Modal Submit event listens for it. | ## add_text_input Adds a text box. Each box gets its own row. ```python form.add_text_input( name, label, style="short", placeholder=..., value=..., required=True, min_length=0, max_length=4000, ) ``` | Argument | What it does | |---|---| | `name` | The key for this box in `values`. | | `label` | The text above the box. | | `style` | `"short"`: one line. `"long"`: a big box for many lines. | | `placeholder` | Grey hint text in the empty box. | | `value` | Text already filled in. | | `required` | `False` lets the member leave it empty. | | `min_length`, `max_length` | The shortest and longest answer allowed. | ## send_modal Opens the form. Needs `await`. ```python await send_modal(form) # from a command or a component handler await send_modal(form, interaction) # from a View button or menu ``` Opening the form **is** the answer to the command or click. Do not defer before it. > **Slash commands: turn off Show thinking:** A slash command answers Discord at once when **Show "thinking…"** is on. Then a form can no longer open. Turn it off under **Behavior** on the command. > **Inside a View:** Pass the click as the second argument: `send_modal(form, interaction)`. With one argument the form answers the command, which is already answered, and Discord refuses it. **Example: a feedback command** ```python # pre-hook of /feedback, with Show "thinking…" turned off form = ModalBuilder(title="Send feedback", custom_id="feedback-form") form.add_text_input("topic", "Topic", placeholder="Bug, idea, other…") form.add_text_input("details", "Tell us more", style="long", max_length=1000) await send_modal(form) ``` ## values In a **Modal Submit** event, `values` is a dictionary. Each key is a box `name`. Each value is what the member typed. ```python # Modal Submit event, custom ID: feedback-form await defer_response(ephemeral=True) send_message("feedback", f"**{values['topic']}** from {user.mention}\n{values['details']}") await respond_interaction(interaction, "Thanks! We got your feedback.") ``` An empty optional box gives `""`. ## get_modal_values Reads the answers from a submit, as the same dictionary as `values`. ```python get_modal_values(interaction) ``` You only need it when you have a submit outside a Modal Submit event, for example one you got from `wait_for("modal_submit")`. Source: https://ccbot.app/docs/reference/modals --- # Interactions > Answer clicks, menu picks and form submits in time, then send or change the reply. An **interaction** is something a member does that Discord expects an answer to: running a slash command, clicking a button, picking from a menu, or submitting a form. Discord gives the bot **3 seconds** to answer. With no answer, the member sees *"This interaction failed"*. ## Answering in time Only three things count as an answer: | Call | Use when | |---|---| | `defer_response(action="update")` | You will change the message the button is on. | | `defer_response(ephemeral=True)` | You will send a new message later. `ephemeral=True` makes it visible to the clicker only. | | `await send_modal(form)` | You open a form. Opening it is the answer. | Answer **first**, before reading a database or editing messages. Those can use up the 3 seconds. > **Slash commands answer for you:** With **Show "thinking…"** on, a slash command answers Discord at once. Your pre-hook then has as long as it needs. Turn it off only to open a form or to answer yourself. `respond_interaction` is **not** an answer. It changes an answer that already exists. Call `defer_response` first. ## defer_response Answers the current interaction now, and shows "thinking…" until you send the real reply. Works in component handlers, modal handlers and slash commands. ```python defer_response(ephemeral=False, action="create", modal=None) ``` | Argument | What it does | |---|---| | `ephemeral` | `True`: the reply that follows is visible to the clicker only. | | `action` | `"create"`: the reply is a new message. `"update"`: the reply changes the message with the button. | | `modal` | A form to open instead. Same as `send_modal(form)`. | Any `action` other than `"create"` is treated as `"update"`. ```python # Button Click event defer_response(ephemeral=True) coins = int(member_var("coins", 0)) respond_interaction(interaction, f"You have {coins} coins.") ``` ## defer Does the same as `defer_response`, but for an interaction you pass in. Use it in View callbacks, where the click is the callback's `interaction`. Needs `await`. ```python await defer(interaction, ephemeral=False, action="create", modal=None) ``` ```python @button(label="Next", custom_id="next") async def next_page(self, interaction, component): await defer(interaction, action="update") self.page += 1 await self.update_view() ``` ## respond_interaction Sends or changes the reply to an interaction you already answered. ```python respond_interaction( interaction, content=..., embed=..., message=None, attachments=..., components=..., empty=False, ) ``` | Argument | What it does | |---|---| | `interaction` | The interaction. In a handler, use the `interaction` variable. | | `content` | The text. | | `embed` | One embed. | | `message` | A `Message(...)` with text and an embed. | | `attachments` | A list of files. `None` removes existing files. | | `components` | A `ComponentBuilder` or a `View`. | | `empty` | `True` changes nothing. | Leave out an argument to keep what is there. Pass `None` to remove it. ```python # Button Click event: change the panel after a click defer_response(action="update") respond_interaction(interaction, content="Ticket closed.", components=None) ``` ## send_modal Opens a form as the answer. Needs `await`. See [Modals](https://ccbot.app/docs/reference/modals). ```python await send_modal(form) await send_modal(form, interaction) # inside a View callback ``` A handler that opens a form must **not** call `defer_response` first. That would be a second answer, and Discord refuses it. ## interaction The `interaction` variable holds the current interaction. Useful parts: | Part | What it is | |---|---| | `interaction.user`, `interaction.member` | Who did it. | | `interaction.channel_id`, `interaction.guild_id` | Where. | | `interaction.custom_id` | The button, menu or form name. Clicks and submits only. | | `interaction.values` | What was picked in a menu, as a list. Menus only. | | `interaction.command_name`, `interaction.options` | Slash commands only. | | `interaction.locale` | The member's Discord language. | See [Objects](https://ccbot.app/docs/reference/objects) for the full list. Source: https://ccbot.app/docs/reference/interactions --- # Paginators > Show long lists one page at a time, with Next and Previous buttons. A **paginator** shows pages one at a time. Members move between them with buttons. Use it for leaderboards, help menus, shop lists and image galleries. Use `PaginatorV2`. The older `Paginator` still works for old scripts. ## Limits | Thing | Limit | |---|---| | Paginators per run | 3 | | Pages per paginator | 200 | | Custom buttons per paginator | 20 | | `timeout` | 1 to 3600 seconds | ## A full example ```python p = PaginatorV2(interaction=interaction, show_first=True, show_last=True) members = sorted(server.get_members().values(), key=lambda m: m.joined_at) for start in range(0, len(members), 10): chunk = members[start:start + 10] lines = listify([m.display_name for m in chunk], prefix="number") p.add_page(Page(content=f"**Oldest members** (page %current_page%/%total_page%)\n{lines}")) await p.start() ``` > **Keep the last page on screen:** When the paginator ends, the command's own reply replaces it. Leave the **Response** box empty to keep the last page showing. ## PaginatorV2 Makes a paginator. ```python PaginatorV2( interaction=None, user_id=None, public=False, timeout=180, prev_text="Prev", next_text="Next", stop_text="Stop", first_text="First", last_text="Last", jump_text="Jump", auto_emojis=True, emoji_only=False, show_first=False, show_last=False, show_jump=False, show_stop=True, ) ``` | Argument | What it does | |---|---| | `interaction` | Pass `interaction` so the pages show in the command's reply. | | `user_id` | Who can press the buttons. Default is the member who ran the command. | | `public` | `True` lets anyone press the buttons. | | `timeout` | Seconds with no clicks before the paginator ends. | | `prev_text` … `jump_text` | The text on each button. | | `prev_emoji` … `jump_emoji` | An emoji on each button. | | `auto_emojis` | Add arrow emojis to the buttons. | | `emoji_only` | Show only emojis, no text. | | `show_first`, `show_last` | Show the **First** and **Last** buttons. | | `show_jump` | Show a **Jump** button to go to any page. | | `show_stop` | Show the **Stop** button. | Others who press the buttons get *"This paginator belongs to someone else."* ## add_page Adds a page. ```python p.add_page(page) p.add_page(build_function, *args) ``` A page can be text, an embed, a `Message(...)` or a `Page(...)`. `%current_page%` and `%total_page%` in the page text become the page numbers. ### Lazy pages Pass a **function** instead of a page. The function runs the first time someone opens that page, not before. It gets the extra arguments you pass. Its result is kept, so a page is built only once. Use this when each page does heavy work, like drawing an image. Pages nobody opens are never built. ```python async def card_page(member): img = await render_template("profile-card", name=member.display_name) return Page(content=member.mention, attachments=[img]) for m in list_members("Staff", as_mention=False): p.add_page(card_page, m) ``` The function can be normal or `async`. ## Page Holds what one page shows. ```python Page(content=None, embed=None, attachments=[], message=None) ``` | Argument | What it does | |---|---| | `content` | The text. | | `embed` | One embed. | | `attachments` | A list of files or `https://` links. | | `message` | A `Message(...)` with text and an embed. | ## add_button Adds a button of your own. ```python p.add_button( label, style="secondary", action="next", emoji=None, url=None, row=2, on_click=None, ) ``` | Argument | What it does | |---|---| | `action` | `first`, `prev`, `next`, `last`, `stop`, `jump` or `reload`. | | `url` | Makes it a link button. | | `on_click` | Your own function instead of an action. It gets `(paginator, interaction)`. | A wrong `action` stops the script and lists the valid ones. ```python p.add_button("Back to start", action="first") p.add_button("Help", url="https://example.com/help") ``` ## start Sends the first page. Needs `await`. ```python await p.start(channel=None, block=True) ``` | Argument | What it does | |---|---| | `channel` | Send the pages to this channel instead of the command's reply. | | `block` | `True`: the script waits until the paginator ends. `False`: the script goes on and the buttons keep working. | With `block=True`, returns `"stopped"` or `"timeout"`. ## wait Waits until the paginator ends. Returns `"stopped"` or `"timeout"`. Needs `await`. ```python await p.wait() ``` ## stop Ends the paginator. Needs `await`. ```python await p.stop() ``` ## Paginator (old) The first paginator. It blocks until it ends and has fewer options. Keep it for old scripts only. ```python p = Paginator(interaction=interaction, user_id=None, timeout=60, prev_text="Prev", next_text="Next", stop_text="Stop") p.add_page(page) p.add_button(label, style, action="next") ``` Its `action` can be `first`, `prev`, `next`, `last` or `stop`. Source: https://ccbot.app/docs/reference/paginators --- # Web and files > Call web APIs, download files, render image templates, ask AI, run saved scripts and find emojis. These functions reach outside the command: the web, your image templates, AI and your saved scripts. Write `await` in front of `fetch`, `render_template`, `ask_ai` and `load_script`. The bot adds it for `load_file`. ## Limits Each run has its own count. Pre-hook and post-hook share it. | What | Free | Premium | |---|---|---| | `fetch` different requests per run | 5 | 20 | | `load_file` different links per run | 5 | 20 | | Download size, each | 5 MB | 100 MB | | Download time, each | 20 seconds | 120 seconds | | `render_template` per minute, whole server | 20 | 120 | | Images inside one template | 5 | 15 | | `ask_ai` | not available | available | Asking for the same `fetch` or `load_file` link again in the same run is free. The bot reuses the first answer. > **Note:** The bot only fetches public web addresses. Links to private or local network addresses are refused. ## fetch Calls a web address and returns the answer. ```python await fetch(url, method="GET", to="json", dynamic=False, **options) ``` | Argument | What it does | |---|---| | `url` | The web address. | | `method` | `GET`, `POST`, `PUT`, `PATCH` or `DELETE`. | | `to` | How to read the answer: `json` (a dict or list), `text` or `bytes`. | | `dynamic` | `True` asks again every time, even for the same link. Use it for random answers. | | `**options` | Sent with the request, for example `headers=`, `json=` or `data=`. | Returns `None` when the site answers with an error (status 400 or higher), or when the answer cannot be read as `to`. **Example: a random cat fact** ```python data = await fetch("https://catfact.ninja/fact") if data is None: set_error("The cat fact site is down. Try again later.") set_content(data["fact"]) ``` **Example: send JSON to an API** ```python await fetch( "https://example.com/api/report", method="POST", json={"user": str(user.id), "reason": reason}, headers={"Authorization": "Bearer " + server_var("api_key")}, ) ``` > **Random answers:** Two calls to the same link in one run return the same answer. For a new random result each time, pass `dynamic=True`. ## load_file Downloads a file from a link so you can attach it to a message. ```python load_file(file, filename=None) ``` | Argument | What it does | |---|---| | `file` | The link to the file. | | `filename` | The name Discord shows. Optional. | Returns `None` when the download fails. Fails with *"File size is greater than 5MB"* when the file is too big for your plan. ```python pic = load_file("https://example.com/banner.png", "banner.png") send_message(channel, "New banner!", attachments=[pic]) ``` ## render_template Draws an image from one of your **Image Templates** and returns it as a file. ```python await render_template(name, **values) ``` | Argument | What it does | |---|---| | `name` | The template name from the dashboard. | | `**values` | A value for each variable in the template. `avatar` also fills the template's avatar picture. | ```python card = await render_template( "rank-card", name=user.display_name, avatar=user.avatar_url, level=member_var("level", 1), ) add_attachment(card) ``` Put the result in `attachments=`, `add_attachment(...)`, or a `Message(image=...)`. Fails when a variable has no value, and names the missing ones. On a free server, going over 20 renders a minute fails. On premium the call waits for a free slot. ## ask_ai **Premium only.** Asks an AI model a question and returns the answer as text. ```python await ask_ai( prompt, instructions=None, max_words=None, use_context=True, cache=False, ) ``` | Argument | What it does | |---|---| | `prompt` | The question. | | `instructions` | How the AI should answer, like a role or a tone. | | `max_words` | A shorter limit for the answer. The most is 1,024 words. | | `use_context` | `True` tells the AI about your server and its commands. | | `cache` | `True` reuses a recent answer to the same question. | ```python answer = await ask_ai(question, instructions="Answer in one friendly sentence.") set_content(answer) ``` The longest prompt depends on the plan: Starter 500, Basic 1,000, Advanced 2,000 characters. On a free server it fails with *"ask_ai is a premium feature"*. ## load_script Runs one of your saved **Scripts** inside this command. ```python await load_script(script_id, **variables) ``` | Argument | What it does | |---|---| | `script_id` | The script's ID from the dashboard. | | `**variables` | Extra variables the script can read. | The script sees all of your command's variables, plus the ones you pass. When it changes a plain variable, your command does not see the change. ```python await load_script("give-xp", amount=10) ``` A script cannot load another script. A disabled script fails and says why. ## emoji Finds an emoji and returns it ready to put in a message. ```python emoji(query, as_emoji=False) ``` | Argument | What it does | |---|---| | `query` | A custom emoji's name or ID from this server, or a name like `":fire:"`. | | `as_emoji` | `True` returns the emoji object instead of text. | ```python set_content(f"GG {emoji('pepe_happy')} {emoji(':tada:')}") ``` Returns nothing when no emoji matches. Source: https://ccbot.app/docs/reference/web-and-files --- # Scheduling > Do something later, or on a repeat. One-off actions, clocks and repeating jobs. There are three ways to make the bot act later: | Tool | Use it for | Runs | |---|---|---| | `schedule` | One ready action later, like "remove this role in 2 days". | Once | | `schedule_clock` | Your own code later, like a reminder. | Once | | `start_job` | Your own code on a repeat, like a daily post. | Again and again | Write `await` in front of every function on this page. Times are plain words, like `"in 10 minutes"`, `"tomorrow 5pm"` or `"next friday"`. They use your server's timezone, set in the dashboard settings. Without one, the bot uses UTC. ## Limits | What | Free | Starter | Basic | Advanced | |---|---|---|---|---| | `schedule` actions waiting at once | 20 | 100 | 250 | 1,000 | | Clocks waiting at once | 1,000 | 10,000 | 10,000 | 10,000 | | Repeating jobs | not available | available | available | available | A waiting action or clock frees its place when it runs. ## schedule Runs one ready-made action at a later time. Returns the schedule ID. ```python await schedule(action, when, data=None, member=None, channel=None) ``` | Argument | What it does | |---|---| | `action` | What to do. See the list below. | | `when` | When to do it, like `"in 2 hours"`. Must be in the future. | | `data` | The details for the action. A dict, or one plain value. | | `member` | The member to act on. Defaults to the person who ran the command. | | `channel` | The channel to act in. Defaults to the current channel. | **Example: a temporary role** ```python await schedule("remove_role", "in 7 days", data={"roles": ["Trial"]}) set_content("You have the Trial role for 7 days.") ``` **Example: post a message later** ```python await schedule("send_message", "tomorrow 9am", data="Good morning everyone!") ``` ### Actions you can schedule When you pass one plain value as `data`, it fills the field marked **main**. | Action | `data` fields | |---|---| | `send_message` | `content` (main), `embed`, `channel` | | `send_dm` | `content` (main), `embed`, `member` | | `add_role`, `remove_role` | `roles` (main), `member` | | `kick_member` | `member` (main), `reason` | | `ban_member` | `member` (main), `reason`, `delete_days` (0 to 7) | | `unban_member` | `user` (main), `reason` | | `delete_message` | `channel`, `message` | | `edit_message` | `channel`, `message`, `content`, `embed` | | `pin_message`, `unpin_message` | `channel`, `message` | | `purge_messages` | `amount` (main, 1 to 100), `channel` | | `lock_channel`, `unlock_channel` | `channel` (main) | | `set_slowmode` | `seconds` (main, up to 21,600), `channel` | | `update_channel` | `channel`, `name`, `topic`, `nsfw`, `slowmode` | | `delete_channel` | `channel` (main) | | `mute_member`, `unmute_member` | `member` (main) | | `create_poll` | `channel`, `question`, `answers` (2 to 10), `duration` (hours) | `embed` is a saved embed ID or a built embed. An unknown action fails and lists the valid ones. ## cancel_schedule Cancels a waiting `schedule` action. ```python await cancel_schedule(schedule_id) ``` ```python sid = await schedule("send_message", "in 1 hour", data="Break is over!") update_member_var("break_id", sid) # later, in another command: await cancel_schedule(member_var("break_id")) ``` Fails with *"No schedule found with id ..."* when it already ran or never existed. ## list_schedules Lists the waiting `schedule` actions of this server. ```python await list_schedules() ``` Returns a list of dicts with `id`, `action`, `run_at` and `data`. ```python waiting = await list_schedules() set_content(f"{len(waiting)} actions are waiting.") ``` ## schedule_clock Runs your **Clock** event once at a later time. Use it when the ready actions above are not enough. ```python await schedule_clock(custom_id, time, metadata) ``` | Argument | What it does | |---|---| | `custom_id` | A name for this clock. Your Clock event listens for it. | | `time` | When it fires, like `"in 30 minutes"`. | | `metadata` | Any data you want back when it fires, like a dict. | **Step 1.** In a command, set the clock: ```python await schedule_clock( f"remind_{user.id}", when, {"user_id": user.id, "channel_id": channel.id, "text": text}, ) set_content("Got it. I will remind you.") ``` **Step 2.** Make an event of type **Clock**. In its custom IDs, enter `remind_*`. The `*` matches any ending. ```python send_message(metadata["channel_id"], f"<@{metadata['user_id']}> Reminder: {metadata['text']}") ``` Only the first Clock event that matches runs. See [Events](https://ccbot.app/docs/getting-started/events) and [Reminders](https://ccbot.app/docs/guides/reminders). ## start_job Starts a repeating job. Each time it is due, it fires your **Clock** event with the job's `custom_id` and `metadata`. ```python await start_job( custom_id, freq, interval=1, date=None, day_name="", month=None, hour=None, minute=None, second=None, metadata=None, ) ``` | Argument | What it does | |---|---| | `custom_id` | A name for the job. Must be new in this server. | | `freq` | How often: `"hour"`, `"day"`, `"week"`, `"month"` or `"year"`. | | `interval` | Every how many. `2` with `"day"` means every 2 days. | | `date` | Day of the month, 1 to 31. | | `day_name` | Day of the week, in lowercase, like `"monday"`. | | `month` | Month of the year, 1 to 12. | | `hour`, `minute`, `second` | Time of day, in your server's timezone. | | `metadata` | A dict your Clock event gets each time. | What each `freq` needs: | `freq` | Needs | |---|---| | `hour` | `minute`, `second` | | `day` | `hour`, `minute`, `second` | | `week` | `day_name`, `hour`, `minute`, `second` | | `month` | `date`, `hour`, `minute`, `second` | | `year` | `month`, `date`, `hour`, `minute`, `second` | ```python # Every day at 9:00 await start_job("daily_quote", "day", hour=9, minute=0, second=0, metadata={"channel": "general"}) ``` > **Premium for repeats:** Jobs run only while the server has a paid plan. Without one, a job pauses itself when it is due. It picks up again after you renew. > **Note:** Run `start_job` from a command, once. A Clock event that starts its own job never runs, because the job must exist first. You can also make jobs on the dashboard, under **Jobs**. ## pause_job Pauses a running job. You can resume it from the dashboard. ```python await pause_job(custom_id) ``` ## cancel_job Stops a job and deletes it. ```python await cancel_job(custom_id) ``` Both fail when no job has that `custom_id`, or when it is not running. Source: https://ccbot.app/docs/reference/scheduling --- # Server events and polls > Create Discord server events and native polls from a command. A **server event** is the event Discord shows at the top of the channel list, with a date and an "Interested" button. A **poll** is Discord's own vote message. Write `await` in front of every function on this page. The bot needs the **Create Events** or **Manage Events** permission for events, and **Send Messages** for polls. ## create_event Creates a server event. Returns the event. ```python await create_event( name, start_time, type="external", end_time=None, location=None, channel=None, description=None, image=None, ) ``` | Argument | What it does | |---|---| | `name` | The event name. | | `start_time` | When it starts, like `"saturday 8pm"`. Must be in the future. | | `type` | `external`, `voice` or `stage`. | | `end_time` | When it ends. Must be after the start. | | `location` | Where it is, as text. Only for `external`. | | `channel` | The voice or stage channel. Only for `voice` and `stage`. | | `description` | More details about the event. | | `image` | A cover image. | What each `type` needs: | `type` | Needs | |---|---| | `external` | `location` and `end_time` | | `voice` | `channel` | | `stage` | `channel` | **Example: a movie night in a voice channel** ```python ev = await create_event( "Movie night", "friday 9pm", type="voice", channel="Cinema", description="Bring snacks.", ) set_content(f"Event made: **{ev.name}**") ``` **Example: an event outside Discord** ```python await create_event( "Meetup", "next saturday 2pm", location="Central Park", end_time="next saturday 5pm", ) ``` ## edit_event Changes an event. Pass only what you want to change. ```python await edit_event( event, name=None, start_time=None, end_time=None, location=None, channel=None, description=None, ) ``` `event` is the event, or its ID. Fails with *"Nothing to edit"* when you pass no changes. ```python await edit_event(event_id, start_time="friday 10pm") ``` ## cancel_event Deletes an event. ```python await cancel_event(event) ``` ## start_event Marks an event as started. ```python await start_event(event) ``` ## end_event Marks an event as ended. ```python await end_event(event) ``` > **Tip:** Save the event ID with `update_server_var("movie_event", ev.id)`. Then other commands can edit, start or end it. ## create_poll Posts a Discord poll. Returns the poll message. ```python await create_poll(question, answers, channel=None, duration=24, multiselect=False) ``` | Argument | What it does | |---|---| | `question` | The question. Up to 300 characters. | | `answers` | A list of 2 to 10 answers. Each up to 55 characters. Extra answers are dropped. | | `channel` | Where to post. Defaults to the current channel. | | `duration` | How long the poll stays open, in hours. 1 to 768 (32 days). | | `multiselect` | `True` lets people pick more than one answer. | ```python await create_poll( "Which game tonight?", ["Minecraft", "Among Us", "Valorant"], duration=4, ) set_content("Poll is up!") ``` To open a poll later, use `schedule("create_poll", ...)`. See [Scheduling](https://ccbot.app/docs/reference/scheduling). For a full walk-through, see the [Poll guide](https://ccbot.app/docs/guides/poll). Source: https://ccbot.app/docs/reference/server-events-and-polls --- # Time > Read times written in plain words, show dates that match each reader's clock, and wait. Most time functions take plain words, like `"tomorrow 5pm"`, `"in 2 hours"` or `"next friday"`. They read them in your server's timezone, set in the dashboard settings. Without one, the bot uses UTC. Only `sleep` needs `await`. ## get_time Turns plain words into a date and time. ```python get_time(time, tz=None) ``` | Argument | What it does | |---|---| | `time` | Plain words, like `"friday 8pm"`. | | `tz` | A timezone name, like `"Europe/London"`. Defaults to the server's. | Returns the time in UTC. Text it cannot read gives the current time. ```python start = get_time("in 3 days") set_content(f"Starts on {format_date(start, '%A')}.") ``` ## timestamp Makes a Discord timestamp. Discord shows it in each reader's own timezone. ```python timestamp(time, format="", tz=None) ``` | `format` | Shows like | |---|---| | `t` | 9:41 PM | | `T` | 9:41:30 PM | | `d` | 06/20/2026 | | `D` | June 20, 2026 | | `f` or empty | June 20, 2026 9:41 PM | | `F` | Saturday, June 20, 2026 9:41 PM | | `R` | in 2 hours, or 3 days ago | ```python set_content(f"The giveaway ends {timestamp('in 24 hours', 'R')}.") ``` `R` counts on its own. You do not need to edit the message. > **Tip:** Already have a Unix time, like `user.unix_joined_at`? Write `` yourself: `f""`. ## format_date Writes a date as text, in the server's timezone. ```python format_date(when=None, format="%B %d, %Y", tz=None) ``` `when` is plain words, a date, or nothing for now. `format` uses Python date codes. ```python format_date() # "June 20, 2026" format_date("next monday", "%A") # "Monday" ``` | Code | Means | |---|---| | `%Y` | Year, like 2026 | | `%B` / `%b` | Month name, full or short | | `%m` | Month number | | `%d` | Day of the month | | `%A` / `%a` | Weekday, full or short | | `%H:%M` | 24-hour time | | `%I:%M %p` | 12-hour time | > **Note:** Use `format_date` instead of `.strftime()`. Calling `.strftime()` yourself fails here. ## time_ago Says how long ago, or how far ahead, a time is. ```python time_ago(when=None, tz=None, future=False) ``` ```python time_ago("3 days ago") # "3 days ago" ``` ## natural_day Says "today", "tomorrow" or "yesterday", or else a short date. ```python natural_day(when=None, format="%b %d", tz=None) ``` ## natural_date A short date. It drops the year when it is this year. ```python natural_date(when=None, tz=None) # "Jun 20" ``` ## natural_delta Turns a length of time into words. ```python natural_delta(value, months=True, minimum_unit="seconds") ``` `value` is a length of time, or a number of seconds. ```python natural_delta(3700) # "an hour" ``` ## precise_delta Like `natural_delta`, but exact. ```python precise_delta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ``` ```python precise_delta(3700) # "1 hour, 1 minute and 40 seconds" ``` ## get_delta The time left until a future time. ```python get_delta(future, tz=None) ``` ```python left = get_delta("next saturday 8pm") set_content(f"{precise_delta(left, minimum_unit='minutes')} to go!") ``` ## sleep Waits a number of seconds before the next line runs. Write `await` yourself. ```python await sleep(seconds) ``` The most is 86,400 seconds (one day). ```python msg = send_message(channel, "This message deletes itself in 10 seconds.") await sleep(10) delete_message(channel.id, msg.id) ``` > **Tip:** For waits longer than a few minutes, use [Scheduling](https://ccbot.app/docs/reference/scheduling). A scheduled action still runs after the bot restarts. A `sleep` does not. ## Number helpers These make numbers easier to read. | Function | Example | Result | |---|---|---| | `intcomma(value)` | `intcomma(1234567)` | `1,234,567` | | `intword(value)` | `intword(1200000)` | `1.2 million` | | `ordinal(value)` | `ordinal(3)` | `3rd` | | `apnumber(value)` | `apnumber(4)` | `four` | | `natural_size(value)` | `natural_size(3000000)` | `3.0 MB` | | `fractional(value)` | `fractional(1.5)` | `1 1/2` | ## Other time tools | Name | What it is | |---|---| | `to_utc(time)` | Turns a date with a timezone into UTC. | | `datetime(year, month, day, hour=12, minute=0, second=0)` | Makes a date by hand. | | `pydatetime` | Python's date tools: `datetime`, `date`, `time`, `timedelta`, `timezone`. | | `pytz`, `zoneinfo`, `timezone` | Timezone tools. | Source: https://ccbot.app/docs/reference/time --- # Waiting > Pause a command until someone sends a message, reacts, clicks or submits a form. `wait_for` lets a command ask a question and wait for the answer, all in one run. Write `await` in front of `wait_for`. > **Tip:** For buttons and menus, use a [View](https://ccbot.app/docs/reference/views) instead. It handles clicks for you and is shorter. ## wait_for Waits for the next event that passes your check. Returns the event. ```python await wait_for( event_name, timeout=None, check=None, reset_timeout_on_check_fail=False, raise_error=False, ) ``` | Argument | What it does | |---|---| | `event_name` | What to wait for. See the list below. | | `timeout` | Seconds to wait. `None` waits with no end. | | `check` | A function that gets each event and returns `True` for the one you want. `predicate` is another name for it. | | `reset_timeout_on_check_fail` | `True` starts the timer again after each event that fails the check. | | `raise_error` | `True` stops with an error on timeout. Otherwise you get `None`. | | `event_name` | Waits for | |---|---| | `"message"` | A new message | | `"message_delete"` | A deleted message | | `"reaction"` | Any reaction added or removed | | `"reaction_add"` | A reaction added | | `"reaction_remove"` | A reaction removed | | `"interaction"` | Any click, menu pick or form submit | | `"modal_submit"` | A form (modal) submitted | | `"clock"` | A clock you dispatched | > **Always add a check:** `wait_for` hears events from every server the bot is in. Always check the channel and the person. Always set a `timeout`, so the command does not wait forever. **Example: a guessing game** ```python # pre-hook secret = randomNumber(1, 10) set_content("I picked a number from 1 to 10. Guess it in the chat!") ``` ```python # post-hook def same_person(evt): return evt.channel_id == channel.id and evt.author_id == user.id evt = await wait_for("message", timeout=30, check=same_person) if evt is None: send_message(channel, "Time is up!") elif evt.content.strip() == str(secret): send_message(channel, f"Yes! It was {secret}.") else: send_message(channel, f"No, it was {secret}.") ``` The question is in the reply, so the wait goes in the post-hook. The post-hook runs after the reply is sent. See [How commands run](https://ccbot.app/docs/getting-started/how-commands-run). ## What you get back You get an **event**, not the message itself. The message ID is `evt.message_id`. There is no `evt.id`. | `event_name` | Attributes on the event | |---|---| | `"message"` | `content`, `author`, `author_id`, `member`, `message`, `message_id`, `channel_id`, `guild_id`, `embeds`, `is_bot`, `is_human`, `is_webhook` | | `"message_delete"` | `message_id`, `old_message`, `channel_id`, `guild_id` | | `"reaction_add"`, `"reaction_remove"` | `emoji_name`, `emoji_id`, `user_id`, `member`, `message_id`, `channel_id`, `guild_id`, `is_animated` | ```python evt = await wait_for("message", timeout=60, check=same_person) if evt is not None: delete_message(evt.channel_id, evt.message_id) # not evt.id ``` **Example: wait for a reaction** ```python msg = send_message(channel, "React with βœ… to confirm.") add_reactions(msg, "βœ…") def confirmed(evt): return evt.message_id == msg.id and evt.user_id == user.id and evt.emoji_name == "βœ…" evt = await wait_for("reaction_add", timeout=60, check=confirmed) ``` ## dispatch Fires a clock event right now, in this server. Your **Clock** event, or a `wait_for("clock")`, picks it up. ```python dispatch(event) ``` `event` must be a `ClockEvent(custom_id, metadata, server)`. Anything else fails with *"This event can't be dispatched"*. ```python dispatch(ClockEvent("refresh_board", {"by": user.id}, server)) ``` To fire a clock later instead, use `schedule_clock`. See [Scheduling](https://ccbot.app/docs/reference/scheduling). ## Event classes You can pass these instead of a name, or check for them in your function: `MessageCreateEvent`, `MessageDeleteEvent`, `ReactionEvent`, `ReactionAddEvent`, `ReactionRemoveEvent`, `ComponentInteraction`, `ModalInteraction`. Source: https://ccbot.app/docs/reference/waiting --- # Utilities > Small helpers for lists, numbers, random picks, loops and JSON, plus the Python you can use. These helpers save you from writing the same few lines again and again. None of them need `await`, except `loop` and `ping`, which the bot awaits for you. ## listify Turns a list into lines with bullets or numbers. ```python listify(items, prefix="dot", new_line=True) ``` | Argument | What it does | |---|---| | `items` | The list. | | `prefix` | `dot` gives β€’, `dash` gives -, `number` gives 1. 2. 3. | | `new_line` | `False` puts everything on one line. | ```python set_content(listify(user.role_names, "number")) ``` ```text 1. Member 2. Gamer 3. VIP ``` ## find Returns the first item that passes your check, or `None`. ```python find(items, check) ``` ```python vip = find(user.roles, lambda r: r.name == "VIP") if vip is None: set_error("You need the VIP role.") ``` ## max_concat Adds text to a string only if it still fits under a length. Returns `[text, overflowed]`. ```python max_concat(prev, new, max_length) ``` ```python text = "" for name in names: text, full = max_concat(text, name + "\n", 2000) if full: break set_content(text) ``` Good for staying under Discord's 2,000-character message limit. ## to_id_array Turns a list of members, roles or channels into a list of their IDs. ```python to_id_array(items) ``` ```python if 123456789012345678 in to_id_array(user.roles): set_content("You have the role.") ``` ## mention_command Makes a clickable mention of one of your slash commands. ```python mention_command(command_name) ``` ```python set_content(f"Try {mention_command('daily')} to claim coins.") ``` Works for subcommands too, like `mention_command("shop buy")`. Falls back to plain `/name` text when the command is not synced yet. ## get_color Turns a hex color into a number. Bad input gives white. ```python get_color(code) ``` ```python get_color("#5865F2") # 5793266 ``` ## ping The bot's connection delay to Discord, in milliseconds. ```python ping(precision=2) ``` ```python set_content(f"Pong! {ping()} ms") ``` ## random Picks one item at random. ```python random(items, *more, exclude=None) ``` ```python random(["rock", "paper", "scissors"]) random("heads", "tails") random(range(1, 7), exclude=[6]) ``` ## randomNumber A random whole number from `a` to `b`. Both ends can come up. ```python randomNumber(a, b) ``` ```python roll = randomNumber(1, 6) ``` ## number Turns text into a number. Bad or empty input gives `default`. ```python number(val, default=0) ``` ```python number("42") # 42 number("3.5") # 3.5 number("abc", 1) # 1 ``` Handy for saved values, which come back as text. ## loop Runs a function for each item, or a number of times. The bot awaits it for you. ```python loop(items, callback, *args, **kwargs) ``` | Argument | What it does | |---|---| | `items` | A list, or a number of times. | | `callback` | A named function. It gets `(item, index, *args)`. | ```python lines = [] def add_line(member, index): lines.append(f"{index + 1}. {member.mention}") loop(winners, add_line) set_content("\n".join(lines)) ``` Inside the function: - `break_loop(data)` stops the loop. `loop` returns `data`. - `iterate_again()` runs the same item again, up to 10 times. > **Note:** `loop` takes a named function, not a `lambda`. Loops can nest 3 deep. A plain `for` loop works too, and is often simpler. ## JSON | Function | What it does | |---|---| | `json_parse(text)` | Text to a dict or list. Same as `load_json`. | | `json_gen(obj)` | A dict or list to text. Same as `to_json`. | ```python data = json_parse('{"coins": 10}') update_member_var("profile", json_gen({"coins": data["coins"] + 5})) ``` ## calc Works out a math sum written as text. Safe to use on what users type. ```python calc("2 + 3 * 4") # 14 ``` ## Python you can use Most everyday Python works: `if`, `for`, `while`, functions, classes, lists, dicts, f-strings, `try` / `except`. **Built in:** `int`, `float`, `bool`, `str`, `list`, `set`, `tuple`, `dict`, `len`, `max`, `min`, `sum`, `abs`, `round`, `pow`, `divmod`, `sorted`, `reversed`, `zip`, `enumerate`, `range`, `all`, `any`, `next`, `map`, `filter`, `isinstance`, `slice`. **Also here:** `text` (same as `str`), `count` (same as `len`), `double` (same as `float`), `emojize`. **Errors you can raise or catch:** `Exception`, `ValueError`, `TypeError`, `KeyError`, `ReferenceError`. **Modules, ready to use:** `math`, `string`, `collections`, `pyrandom` (Python's `random`), `fuzz` (fuzzy text matching). `map` and `filter` return lists, not iterators. > **Not available:** `import` does nothing, because everything is already loaded. `print` does not exist. To see a value, put it in the reply with `set_content`. See [Common mistakes](https://ccbot.app/docs/concepts/common-mistakes) and [Limits](https://ccbot.app/docs/concepts/limits) for loop limits. Source: https://ccbot.app/docs/reference/utilities --- # Objects > Every attribute you can read on a user, server, channel, message, role and interaction. Your code gets ready-made objects, like `user`, `server` and `channel`. This page lists what you can read on each one. Use these names exactly. A guessed name, like `user.name`, fails. > **Read here, act with helpers:** Read attributes from these objects. To do something, like send, add a role or edit, use the helper functions in the API reference. `channel.send(...)`, `message.respond(...)`, `member.add_role(...)` and `member.remove_role(...)` are blocked. Which objects you get depends on where your code runs. See [Event variables](https://ccbot.app/docs/reference/event-variables). ## user The member who ran the command, or the member an event is about. | Attribute | What it is | |---|---| | `id` | The user ID. | | `mention` | A ping, like `@Alex`. | | `username` | The account name. | | `global_name` | The display name set on the account. | | `display_name` | The name shown in this server. Use this for most text. | | `nickname` | The server nickname, or `None`. | | `avatar_url` | The avatar link, or `None`. | | `banner_url` | The banner link, or `None`. | | `display_avatar_url` | The avatar link, with Discord's default as a fallback. | | `roles` | The member's roles. | | `role_names` | Role names, without @everyone. | | `role_mentions` | Role pings, without @everyone. | | `role_ids` | Role IDs. | | `top_role` | The highest role. | | `is_bot` | `True` for bots. | | `is_boosting` | `True` when they boost the server. | | `is_timed_out` | `True` when they are timed out. | | `joined_at` | When they joined the server. | | `created_at` | When the account was made. | | `unix_joined_at` | Join time as a Unix number, for ``. | | `unix_created_at` | Account age as a Unix number. | | `premium_since` | When they started boosting, or `None`. | | `accent_color` | Their profile color. | | `status` | Their online status. | Methods: `get_roles()`, `get_top_role()`, `get_guild()`, `get_presence()`, `communication_disabled_until()`. ```python set_content(f"{user.display_name} joined .") ``` ## server The server the code runs in. | Attribute | What it is | |---|---| | `id` | The server ID. | | `name` | The server name. | | `description` | The server description. | | `member_count` | How many members it has. | | `owner_id` | The owner's user ID. | | `owner` | The owner. | | `icon_url` | The icon link, or `None`. | | `banner_url` | The banner link, or `None`. | | `created_at` | When the server was made. | | `premium_tier` | The boost level, 0 to 3. | | `premium_subscription_count` | How many boosts it has. | | `vanity_url_code` | The custom invite code, or `None`. | | `verification_level` | The verification setting. | | `preferred_locale` | The server language. | | `features` | Discord features the server has. | | `rules_channel_id` | The rules channel ID. | | `system_channel_id` | The system messages channel ID. | | `afk_channel_id` | The AFK voice channel ID. | Methods: `get_member(id)`, `get_members()`, `get_channel(id)`, `get_channels()`, `get_role(id)`, `get_roles()`, `get_emoji(id)`, `get_emojis()`, `get_voice_state(user)`, `get_my_member()`. ```python set_content(f"**{server.name}** has {server.member_count} members and {server.premium_subscription_count} boosts.") ``` ## channel A text, voice or thread channel. | Attribute | What it is | |---|---| | `id` | The channel ID. | | `name` | The channel name. | | `mention` | A link, like `#general`. | | `type` | The kind of channel. | | `topic` | The channel topic. | | `position` | Its place in the list. | | `parent_id` | The category ID. | | `is_nsfw` | `True` for age-restricted channels. | | `rate_limit_per_user` | The slowmode, in seconds. | | `last_message_id` | The newest message ID. | | `created_at` | When it was made. | Methods: `get_guild()`, `fetch_message(id)`, `fetch_history()`, `fetch_pins()`. ## message A Discord message, for example in a message event. | Attribute | What it is | |---|---| | `id` | The message ID. | | `content` | The text. | | `author` | Who sent it. | | `member` | The sender as a member. | | `channel_id` | Where it was sent. | | `created_at` | When it was sent. | | `edited_timestamp` | When it was last edited, or `None`. | | `attachments` | Files on the message. | | `embeds` | Embeds on the message. | | `reactions` | Reactions on the message. | | `stickers` | Stickers on the message. | | `poll` | The poll, if it is one. | | `is_pinned` | `True` when pinned. | | `mentions_everyone` | `True` when it pings @everyone. | | `user_mentions_ids` | IDs of users it pings. | | `role_mention_ids` | IDs of roles it pings. | | `channel_mention_ids` | IDs of channels it links. | | `referenced_message` | The message it replies to. | | `webhook_id` | Set when a webhook sent it. | Methods: `get_member_mentions()`, `get_role_mentions()`, `make_link()`. To edit or delete, use `edit_message` and `delete_message`. See [Messages](https://ccbot.app/docs/reference/messages). ## Role What you get from `get_role(...)`, `user.roles` or `user.top_role`. | Attribute | What it is | |---|---| | `id` | The role ID. | | `name` | The role name. | | `mention` | A ping, like `@VIP`. | | `color` | The role color. | | `position` | Its height in the role list. | | `permissions` | What the role allows. | | `is_hoisted` | `True` when shown apart in the member list. | | `is_mentionable` | `True` when anyone can ping it. | | `is_managed` | `True` when a bot or app owns it. | | `unicode_emoji` | The role icon emoji, if any. | | `created_at` | When it was made. | ## interaction The slash command, click or form submit that started the code. | Attribute | What it is | |---|---| | `id` | The interaction ID. | | `guild_id` | The server ID. | | `channel_id` | The channel ID. | | `member` | Who used it. | | `user` | Who used it, as a user. | | `locale` | The user's Discord language. | | `created_at` | When it happened. | | `command_name` | The command name. Slash commands only. | | `options` | The options typed in. Slash commands only. | | `custom_id` | The button, menu or form ID. Clicks and forms only. | | `values` | What was picked in a menu, as a list. Clicks only. | | `message` | The message the button is on. Clicks only. | There is no `interaction.get_channel()`. Use the `channel` variable, or `get_channel(...)`. To reply, use `defer_response`, `respond_interaction` and `send_modal`. See [Interactions](https://ccbot.app/docs/reference/interactions). ## Embed objects See [Embeds](https://ccbot.app/docs/reference/embeds) for what you can read and change on an embed. ## Constants | Name | Use | |---|---| | `Permissions` | Permission names, like `Permissions.BAN_MEMBERS`. | | `Color` | A color, like `Color.from_hex_code("#ff0000")`. | | `Spacing` | Gap sizes for the separator in new-style messages. | Source: https://ccbot.app/docs/reference/objects --- # Event variables > The ready-made variables your code gets in each kind of command and event. Your code starts with some variables already filled in, like `user` and `server`. Which ones you get depends on what started the code. The tables below list them. Using a variable that is not in the list fails with a "not defined" error. All helper functions in the API reference are there too, unless a section says otherwise. To read what is on each object, see [Objects](https://ccbot.app/docs/reference/objects). ## Slash command | Variable | What it is | |---|---| | `user` | The member who ran the command. | | `channel` | The channel it ran in. | | `server` | The server. | | `interaction` | The slash command interaction. | | `command_name` | The command name. | | `unix_timestamp` | The current time as a Unix number. | | `variables` | Your saved server variables. | | Each option | Every option, under its own name. An option `amount` is the variable `amount`. | Only in the post-hook, after the reply is sent: | Variable | What it is | |---|---| | `response` | The reply message. | | `response_dm` | The DM reply, when the reply went to DMs. | | `response_feedback` | The short "Command executed" note, when the reply went to DMs or another channel. | | `view` | Your View, after it finished. | | `pre_hook_failed` | `True` when the pre-hook stopped with an error. | | `pre_hook_cooldown` | `True` when a cooldown stopped the command. | | `pre_hook_error` | The pre-hook's error, if any. | > **Options win:** An option named `user` replaces the `user` variable. Name options so they do not clash, like `target` instead of `user`. See [How commands run](https://ccbot.app/docs/getting-started/how-commands-run) for the order of pre-hook, reply and post-hook. ## Text command The same as a slash command, with two changes: - `message` is the message that ran the command. There is no `interaction`. - Saved variables load first, so a built-in name wins over a variable with the same name. See [Text commands](https://ccbot.app/docs/getting-started/text-commands). ## Member Join, Leave and Update | Variable | What it is | |---|---| | `user` | The member. | | `server` | The server. | | `event` | The raw Discord event. | | `unix_timestamp` | The current time. | | `old_user` | The member before the change. Leave and Update only. | | `added_role` | The role ID, or list of IDs, that was added. Update only. | | `removed_role` | The role ID, or list of IDs, that was removed. Update only. | There is no `channel` and no `message`. `message_id` and `channel_id` are `None`. ## Message Runs for every new message. **Premium only.** | Variable | What it is | |---|---| | `user` | The author. | | `message` | The message. | | `channel` | Where it was sent. | | `message_id`, `channel_id` | Their IDs. | | `server`, `event`, `unix_timestamp` | As above. | > **Skip bots first:** This event also runs for bot messages, your own bot's too. Start with a check, or your bot can answer itself forever: > > ```python > if user is None or user.is_bot: > return > ``` ## Reaction Runs when someone adds or removes one of the emojis you list on the event. | Variable | What it is | |---|---| | `user` | Who reacted. | | `channel` | The channel. | | `emoji_name` | The emoji, or the custom emoji's name. | | `emoji_id` | The custom emoji's ID. `None` for normal emojis. | | `action` | `"add"` or `"remove"`. | | `message_id`, `channel_id` | The message and its channel. | | `server`, `event`, `unix_timestamp` | As above. | There is no `message` object. Get it with `get_message(channel_id, message_id)`. ## Voice Join, Leave and Change | Variable | What it is | |---|---| | `user` | The member. | | `channel` | The channel they joined. On Leave, the channel they left. | | `old_channel` | The channel before. `None` on a fresh join. | | `server`, `event`, `unix_timestamp` | As above. | > **Note:** Voice Join also runs when someone moves between channels. Check `old_channel is None` to know it was a fresh join. ## Thread Runs when a thread is made, changed or deleted. | Variable | What it is | |---|---| | `channel`, `thread` | The thread. | | `action` | `"create"`, `"update"` or `"delete"`. | | `user` | The thread owner. `None` on delete. | | `parent_id` | The channel the thread is in. | | `thread_id` | The thread ID. | | `event` | The raw Discord event. | There is no `server` here. Use `event.get_guild()`. ## Clock Runs when a clock or a repeating job fires. See [Scheduling](https://ccbot.app/docs/reference/scheduling). | Variable | What it is | |---|---| | `custom_id` | The clock or job name. | | `metadata` | The data you gave when you set it. | | `server`, `event`, `unix_timestamp` | As above. | In the event's custom IDs, list exact names, or end with `*` to match many, like `remind_*`. ## Vote (top.gg) Runs when someone votes for your server on top.gg. | Variable | What it is | |---|---| | `user` | The voter. `None` when they left the server. | | `user_id` | The voter's ID. Always set. | | `weekend` | `True` for a double-value weekend vote. | | `vote_count` | The server's total votes, this one included. | | `server`, `event`, `unix_timestamp` | As above. | ## Button Click / Select Menu Change Runs when someone clicks a saved button or picks from a saved menu. List the component IDs on the event. | Variable | What it is | |---|---| | `interaction` | The click. `interaction.values` holds menu picks. | | `user` | Who clicked. | | `custom_id` | Which button or menu. | | `message` | The message the button is on. | | `channel` | The channel. | | `message_id`, `channel_id` | Their IDs. | | `server`, `unix_timestamp` | As above. | Also here: `defer_response`, `respond_interaction`, `send_modal`, `cooldown`, `require_permission`, `allowed_roles`, `banned_roles`, `allowed_channels`, `banned_channels`. > **Answer within 3 seconds:** Call `defer_response()` first, then `respond_interaction(...)`. Or open a form with `await send_modal(form)` as the first answer. See [Interactions](https://ccbot.app/docs/reference/interactions). ## Modal Submit Runs when someone sends a form. The event's custom IDs list the form IDs. | Variable | What it is | |---|---| | `interaction` | The submit. | | `user` | Who sent it. | | `custom_id` | The form ID. | | `values` | A dict of each field's ID to what they typed. | | `channel`, `channel_id` | The channel. | | `server`, `unix_timestamp` | As above. | ```python answer = values.get("reason", "") ``` There is no `message`. The same 3-second rule and helpers as button clicks apply. See [Forms](https://ccbot.app/docs/concepts/forms). ## Autocomplete Runs while someone types into a command option that has autocomplete. You return the suggestions. | Variable | What it is | |---|---| | `interaction` | The autocomplete request. | | `option` | The option being typed. `option.value` is the text so far. | | `server`, `channel_id`, `unix_timestamp` | As above. | There is no `user`. Reply in under 3 seconds with `respond_options`. Write `await` yourself. ```python await respond_options(options, auto_filter=True) ``` | Argument | What it does | |---|---| | `options` | A list of text, or of `AutocompleteChoice(name=..., value=...)`. Only 25 show. | | `auto_filter` | `True` keeps only choices that contain what was typed. | ```python await respond_options([ AutocompleteChoice(name="Red team", value="red"), AutocompleteChoice(name="Blue team", value="blue"), ]) ``` `name` is what people see. `value` is what your command gets. Do not `fetch` or `sleep` here: it is too slow. Source: https://ccbot.app/docs/reference/event-variables --- # /8ball > Ask the magic 8-ball a question and get a random answer. Ask a yes-or-no question. The bot picks a random answer. ```text /8ball question: Will I win the giveaway? ``` > 🎱 **Will I win the giveaway?** > Signs point to yes. ## Setup Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `8ball` | | Description | `Ask the magic 8-ball` | **Arguments:** | Type | Name | Required | |---|---|---| | Text | `question` | Yes | ## Code **Pre-hook:** ```python answer = random([ "It is certain.", "Without a doubt.", "Yes, definitely.", "Signs point to yes.", "Ask again later.", "Cannot predict now.", "Don't count on it.", "My sources say no.", "Very doubtful.", ]) ``` **Content / Reply Message:** ```text 🎱 **{question}** {answer} ``` `random` picks one item from the list. The reply shows it with `{answer}`, and the question with `{question}`. ## Make it yours Add or remove answers in the list. Each answer is text in quotes, followed by a comma. ## Related - [Variables](https://ccbot.app/docs/getting-started/variables) explains `{ }` in the reply. - [/coinflip](https://ccbot.app/docs/examples/coinflip) is another random pick. Source: https://ccbot.app/docs/examples/8ball --- # /coinflip > Flip a coin. Heads or tails. The bot flips a coin and shows the side. > πŸͺ™ @Alex flipped a coin: **Heads**! ## Setup Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `coinflip` | | Description | `Flip a coin` | No arguments. ## Code **Pre-hook:** ```python side = random(["Heads", "Tails"]) ``` **Content / Reply Message:** ```text πŸͺ™ {user.mention} flipped a coin: **{side}**! ``` ## Make it a bet Add a **Text** argument named `guess`, with two **Choices**: `Heads` and `Tails`. Then change the pre-hook: ```python side = random(["Heads", "Tails"]) result = "You guessed right!" if side == guess else "Better luck next time." ``` And the reply: ```text πŸͺ™ It landed on **{side}**. {result} ``` ## Related - [/8ball](https://ccbot.app/docs/examples/8ball) picks from a longer list. - [Economy](https://ccbot.app/docs/guides/economy) lets members bet coins. Source: https://ccbot.app/docs/examples/coinflip --- # /avatar > Show a member's profile picture in large size. Shows the profile picture of a member, or your own. ```text /avatar member: @Alex ``` The reply is an embed with the picture at full size. ## Setup Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `avatar` | | Description | `Show someone's avatar` | **Arguments:** | Type | Name | Required | |---|---|---| | Member | `member` | No | ## Code **Pre-hook:** ```python target = member or user picture = str(target.display_avatar_url) set_embed(Message( title=f"{target.display_name}'s avatar", url=picture, image=picture, color="#5865F2", )) ``` Leave the **Content / Reply Message** box empty. | Part | Job | |---|---| | `member or user` | Uses the picked member, or you when nobody is picked. | | `display_avatar_url` | The server avatar if they set one, else their main avatar. | | `url=picture` | Makes the title a link to the full image. | | `image=picture` | Shows the picture large inside the embed. | ## Related - [Embeds](https://ccbot.app/docs/reference/embeds) lists every `Message(...)` field. - [Objects](https://ccbot.app/docs/reference/objects) lists what you can read from a member. Source: https://ccbot.app/docs/examples/avatar --- # /serverinfo > Show your server's name, owner, members, boosts and age in one embed. Shows facts about your server in a neat embed. > **My Server** > Owner: @Sam Β· Members: 1,204 Β· Boosts: 7 (level 2) > Created: March 3, 2021 ## Setup Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `serverinfo` | | Description | `About this server` | No arguments. ## Code **Pre-hook:** ```python created = int(server.created_at.timestamp()) set_embed(Message( title=server.name, description=server.description, thumbnail=server.icon_url, color="#5865F2", fields=[ ("Owner", f"<@{server.owner_id}>", True), ("Members", intcomma(server.member_count), True), ("Boosts", f"{server.premium_subscription_count} (level {int(server.premium_tier)})", True), ("Roles", str(len(server.get_roles())), True), ("Channels", str(len(server.get_channels())), True), ("Created", f" ()", True), ], )) ``` Leave the **Content / Reply Message** box empty. | Part | Job | |---|---| | `fields=[...]` | Each field is `(name, value, inline)`. `True` puts fields side by side. | | `intcomma(...)` | Writes 1204 as 1,204. | | `` | A Discord timestamp. Each reader sees the date in their own format. | | `` | Shows "3 years ago". | > **Note:** `server.description` and `server.icon_url` are empty when the server has none. The embed then leaves them out. ## Related - [/avatar](https://ccbot.app/docs/examples/avatar) shows a member's picture. - [Objects](https://ccbot.app/docs/reference/objects) lists everything you can read from `server`. - [Embeds](https://ccbot.app/docs/reference/embeds) lists every `Message(...)` field. Source: https://ccbot.app/docs/examples/serverinfo --- # /rank > Show a member's level and XP, with a progress bar to the next level. Shows the level and XP of a member, or of yourself. > **Alex** is level **4** > `β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘` 350 / 500 XP This page has two parts: 1. A **Message** event that gives XP for chatting. 2. The `/rank` command that shows it. Both use the same saved values: `xp` and `level`, stored per member. > **Premium:** Message events only run on servers with a paid plan. The event menu shows it as **MESSAGE (Pro Only)**. On a free server, give XP from a command instead, for example a `/daily` command. ## Give XP for chatting Open **Create β†’ Event**. Set **Type** to **MESSAGE (Pro Only)**. Paste into **Code**: ```python if not user.is_bot: last = int(member_var("xp_last", 0)) # One reward per member per minute, so spam earns nothing extra. if unix_timestamp - last >= 60: update_member_var("xp_last", unix_timestamp) xp = int(member_var("xp", 0)) + random(range(15, 26)) level = int(member_var("level", 0)) need = 100 * (level + 1) if xp >= need: xp = xp - need level = level + 1 update_member_var("level", level) send_message(channel, f"πŸŽ‰ {user.mention} reached level {level}!") update_member_var("xp", xp) ``` Each message gives 15 to 25 XP, at most once a minute. Level 1 needs 100 XP, level 2 needs 200, and so on. ## The /rank command Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `rank` | | Description | `Show your level` | **Arguments:** | Type | Name | Required | |---|---|---| | Member | `member` | No | **Pre-hook:** ```python target = member or user xp = int(member_var("xp", 0, member=target)) level = int(member_var("level", 0, member=target)) need = 100 * (level + 1) filled = min(10, xp * 10 // need) bar = "β–ˆ" * filled + "β–‘" * (10 - filled) ``` **Content / Reply Message:** ```text **{target.display_name}** is level **{level}** `{bar}` {xp} / {need} XP ``` When nobody is picked, `member` is empty, so `member or user` falls back to the member who ran the command. ## Notes - Saved values come back as text. `int(...)` turns them into numbers. - `member_var` reads one member's value. Pass `member=` to read someone else's. - Change `100 * (level + 1)` in **both** places to make levels harder or easier. ## Related - [Levels](https://ccbot.app/docs/guides/levels) builds a full level system with role rewards. - [Saving data](https://ccbot.app/docs/concepts/saving-data) compares member variables, databases and Mongo. - [Storage](https://ccbot.app/docs/reference/storage) lists `member_var` and `update_member_var`. Source: https://ccbot.app/docs/examples/rank --- # /remind > DM yourself a note after a set time. Saves a note now and DMs it to you later. ```text /remind when: 30 minutes note: Join the voice call ``` > Got it! I will DM you in 30 minutes. Thirty minutes later, in your DMs: > ⏰ Reminder: Join the voice call ## Setup Create a slash command and switch to **Advanced**. | Field | Value | |---|---| | Command Name | `remind` | | Description | `Remind me about something` | Under **Behavior**, turn on **Only the user sees the reply**. **Arguments:** | Type | Name | Required | Description | |---|---|---|---| | Text | `when` | Yes | For example 10 minutes, 2 hours, 3 days | | Text | `note` | Yes | What to remind you about | ## Code **Pre-hook:** ```python await schedule("send_dm", f"in {when}", data=f"⏰ Reminder: {note}") set_content(f"Got it! I will DM you {timestamp(f'in {when}', 'R')}.") ``` Leave the **Content / Reply Message** box empty. ## Good to know - `schedule` works on every plan. A free server can have **20** actions waiting at once. Paid plans get more. An action stops counting once it has run. - Members with DMs turned off do not get the reminder. The [Reminders guide](https://ccbot.app/docs/guides/reminders) shows how to ping in the channel instead. - If the bot cannot read `when`, the member sees *"Schedule time must be in the future"*. ## Related - [Reminders](https://ccbot.app/docs/guides/reminders) explains each part, step by step. - [Scheduling](https://ccbot.app/docs/reference/scheduling) lists every action `schedule` can run. Source: https://ccbot.app/docs/examples/remind