toyo-discord-bot
Bot for the TOYO Discord with customized commands.
TOYO Bot Slash Commands
The toyobot, as all bots in the TOYO discord are, is limited to the #robot-mayhem channel.
/yoto-playlist
Description:
Get info about a playlist (private to you).
Parameters:
- url (requried): yoto.io url to fetch data from
- show: true/false; defaults to false; if true, the response will be displayed to all users.
Notes:
- By default, the response will only be visible to the user to sent the command. Use
show: trueto overide this and publish the response to the whole channel. - All command executions that are visible to the whole channel will expose the full command, including the parameters. yoto.io URLs that are for physical cards or non-shared MYO playlists should NOT be shared publicly, ever.
Examples:
/yoto-playlist url: https://yoto.io/2u1gx?g4K9YqFNigES=5qiiuZqxtx7hu

/yoto-playlist url: https://yoto.io/2u1gx?g4K9YqFNigES=5qiiuZqxtx7hu show: true

/yoto-store
Description:
Get info from the yoto store
Parameters:
- url (required): yotoplay.com url for the card or card pack listing.
Notes:
- This will also sometimes work for wayback urls from the Internet Archive.
- Geographicly limited store pages may be restircted and unable to parse properly. This is untested.
Examples:
/yoto-store url:https://us.yotoplay.com/products/frog-and-toad-audio-collection

Development Notes
- Write the code in the
/srcfolder. Helper functions can be included as their own.jsfiles but will then need to be marked withexportand thenimport { name } from './file.js'wherever they are going to be used. server.jsis the main bundle of code that is used by the server. This is what is hosted in cloudflare.- DEV Use
npm ngrokto host the code locally.- If doing this, the Discord Application will need to be updated for where it points. Get the ngrok url and paste it in the
Interactions Endpoint URLfield. - Note: I probably need to create a dev-bot on discord and a dev discord so this can be tested outside production. the issue is that things perform differently in dev versus prod because prod is run from cloudflare workers directly.
- If doing this, the Discord Application will need to be updated for where it points. Get the ngrok url and paste it in the
- PROD Use
npm run deployto push the updated code to Wrangler/cloudflare - SECRETS Secrets/constants can be pushed into cloudflare
- push directly from the dev command line like this
npx wrangler secret put NAME_OF_VARIABLE, or - use the webui at cloudflare to create Secrets
- To Use: then import them into the code using
const token = process.env.NAME_OF_VARIABLE;within the javascript file.
- push directly from the dev command line like this
TODO: figure out how to do the wrangler deploy from github actions- This comes from the guide here.
- cloudflare guide to create the worker, then follow This Guide to set up a sample app. For the most part I just copied content out of the sample app into the new worker i created. Its janky, but it worked. shut up.
Discord Doc links
Pins to relevant documentation that i've been using.
- Message Type Flags
- Application commands
- Slash Commands & Sample Interaction JSON
- Registering Commands
Building New Commands
src/commands.js- Add a new command using the template. It needs a name for the const to use, the name value is what a user enters in the chat/yoto-store-info parameters go here, description is what pops up when the user is typing. Options can be used.export const GET_STORE_PAGE_COMMAND = { name: 'yoto-store-info', description: 'Get information about a listing from the Yoto store.', };register.js- Line 1 Update the import line to include the new command constant:import { AWW_COMMAND, GET_STORE_PAGE_COMMAND } from './commands.js';register.js- Line 37 Add the new command constant into the PUT command body string.body: JSON.stringify([AWW_COMMAND, GET_STORE_PAGE_COMMAND]),server.js- Line 11 Add a new import line to include the new command constant and command function:import { GET_STORE_PAGE_COMMAND, GET_STORE_PAGE_EXEC } from './commands.js';server.js- Add a new command processor for therouter.postfunction:case GET_STORE_PAGE_COMMAND.name.toLowerCase():{ return GET_STORE_PAGE_EXEC(request, env, interaction); }server.js- Add a newrouter.getfunction call:router.get('/yoto-store-page', (request, env) => { return YOTO_STORE_PAGE_EXEC(request, env, "webget"); });command.js(or some other helper file) - Write the code to process the actions inside theYOTO_STORE_PAGE_EXECfunction created.export function YOTO_STORE_PAGE_EXEC(request, env, interaction) { return new JsonResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: "Pong!", } }) };- On the command line (in the working folder) run
npm run registerto register the commands with Discord - If possible to test, Test using ngrok
npm run ngrok - Deploy directly to cloudflare (see above)
npm run publish - Publish the code to GitHub
This junk below came from some copilot ideation and has not been implemented yet.
The code it generated lives in a file called copilot_draft_toyobot.js. As any of it becomes usable it will be converted into commands and pushed into the ./toyobot/src folder structure.
Notes
- Rate Limits:
- Ensure your bot handles API rate limits gracefully to avoid being throttled by either Discord or Google.
- Security:
- Do not hardcode sensitive tokens (Discord bot token or Google credentials) in your code. Use environment variables or a secure configuration file.
- Error Handling:
- The bot includes basic error handling but can be expanded to cover specific issues like invalid ranges or insufficient permissions.
- Permissions:
- Ensure the Service Account used for the Google Sheets API has "Editor" permissions for the spreadsheet.
- Ensure the Service Account used for the Google Sheets API has "Editor" permissions for the
links_from_discordspreadsheet. - Ensure the Service Account has "Reader" access to the
Card_DBspreadsheet. - Ensure the Google Sheets API service account has "Editor" access to the
points_fetch_logspreadsheet.
- Spreadsheet ID:
- Replace the placeholder YOUR_DISCORD_USERS_SPREADSHEET_ID with the actual ID of your Google Sheet.
- Customization:
- You can extend the commands to include additional metadata, such as a description for the URL or a timestamp.
- You can modify the headers array to match the actual column headers in your spreadsheet.
How It Works
- Command:
archive-application-status- The spreadsheet query function uses the
message.author.id(Discord user ID) to find matching data in the spreadsheet. - Assumes the spreadsheet has Discord user IDs in column
Aand application statuses in columnB. Update therangeand index logic if your structure is different. - Gracefully handles cases where the user ID does not exist in the spreadsheet or when the spreadsheet is empty.
- The bot sends a read-only request to the specified range in the Google Spreadsheet.
- The bot formats the retrieved data and sends it back to the Discord channel as a reply.
- The spreadsheet query function uses the
- Command:
link-email- When a user types
link-email user@example.com, the bot stores the provided email and the user’s Discord ID in theDiscord_Usersspreadsheet. - Uses the
spreadsheets.values.appendmethod from the Google Sheets API to insert new rows into the sheet. - Ensures the provided email contains an "@" symbol to check for validity. You can add more complex validation logic if needed.
- Stores the Discord user ID in column
Aand the email address in columnBof theDiscord_Usersspreadsheet. - Replies to the Discord channel with success or error messages based on the outcome.
- Handles errors such as invalid email input or issues connecting to the Google Sheets API.
- When a user types
- Command:
submit-card- Users can type
submit-card https://example.comto submit a URL for storage in thelinks_from_discordspreadsheet. - Uses spreadsheets.values.append from the Google Sheets API to insert new rows into the sheet.
- Stores the Discord user ID in column A and the submitted URL in column B of the
links_from_discordspreadsheet. - Ensures the provided URL starts with "http" to confirm it's valid. You can extend this logic for more robust validation if needed.
- Provides feedback to the user, confirming whether the URL was successfully submitted or if an error occurred.
- Handles errors related to URL submission or connectivity issues with the Google Sheets API.
- Users can type
- Command:
check-card- Returns information about the card if the identifier is known.
- Exact ID Match: Users can type
check-card hMknito submit an identifier to search for.- If a 5-character identifier is provided, the bot performs an exact, case-sensitive match in column
A. - Returns an error if multiple entries are found with the same ID. This is a data integrity problem that needs to be solved by an administrator.
- If a 5-character identifier is provided, the bot performs an exact, case-sensitive match in column
- Non-Case-Sensitive Title Search: Users can type
check-card Make Your Own Guideto submit a title to search for.- If the search term is a string (not a 5-character ID), the bot performs a non-case-sensitive match in column
C. - Provides a list of matching IDs if multiple results are found, prompting the user to search by ID for detailed information.
- If the search term is a string (not a 5-character ID), the bot performs a non-case-sensitive match in column
- Ensures only one match is returned for ID-based searches.
- Handles cases where no matches are found.
- Returns data in the format of
header: datafor columns A, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, and R.
- Command:
fetch-points- When the user types
fetch-points, the bot retrieves the message author's Discord ID and username. - Uses the Google Sheets API to search the spreadsheet for the Discord ID in column
A. - Retrieves the points from column
K(10th index in the array). - Formats the Mee6 command
/give-item member:discordID item:Yak Point amount:Xusing the retrieved points. - Logs the execution (add logic to send the command to the bot in Discord if necessary).
- Appends a log entry to the points_fetch_log spreadsheet, recording the username, Discord ID, and points awarded.
- Handles cases where the Discord ID doesn't exist or points are invalid (not a number).
- Spreadsheet Structures: Points Spreadsheet, Points Fetch Log
- The Mee6 command must be executed in a Discord channel where Mee6 is active. You can use the Discord API to send the command to the appropriate channel programmatically.
- Includes error handling for invalid Discord IDs, invalid points values, and connectivity issues with the Google Sheets API.
- When the user types
Example Spreadsheet Structure
Application_Status
| Column A (Discord ID) | Column B (Application Status) |
|---|---|
| 123456789012 | Submitted |
| 987654321098 | Approved |
Discord_Users
| Column A (Discord ID) | Column B (Email) |
|---|---|
| 123456789012 | user1@example.com |
| 987654321098 | user2@example.com |
links_from_discord
| Column A (Discord ID) | Column B (URL) |
|---|---|
| 123456789012 | https://example.com |
| 987654321098 | https://anotherlink.com |
Card_DB
| Column A (ID) | Column B (url) | Column C (Title) | Column D | ... | Column R | | --- | --- | --- | --- | --- | | ABC12 | https://link | MyCardTitle | Data1 | ... | Data2 | | DEF34 | https://link2 | AnotherTitle | Data3 | ... | DataM |
points_db
| Column A (Discord ID) | Column B (Points Available) |
|---|---|
| 123456789012 | 10 |
| 987654321098 | 0 |
points_log
| Column A (Discord ID) | Column B (Points Given) | Column C (Action Date) |
|---|---|---|
| 123456789012 | 5 | Mar 10 2025 |
| 987654321098 | 23 | Jan 1 2025 |