You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build a first-party-quality plugin for Robo.js that turns any bot into a lightweight helpdesk: open/assign/escalate/close tickets in Discord threads, with optional web APIs (if @robojs/server is installed) so maintainers can build dashboards later. The plugin must feel Robo-native: file-based commands/events, Sage returns, Middleware for guards, Flashcore for durable storage, and State for ephemeral context.
Tech requirements
Language: TypeScript (ESM).
Package name: @robojs/tickets.
Follow Robo’s TS/ESM conventions.
Goals
Zero-config install: npx robo add @robojs/tickets. The plugin should “just work” after minimal in-guild setup.
Guild-level setup only: Channel/role choices are configured inside the guild (via slash commands/modals). No role/channel IDs in /config files.
Automatic integrations: If packages exist, use them—no toggles:
@robojs/cron → stale or auto-close flows (optional).
Detection should be runtime (dynamic import) like how @robojs/server automatically switches engines (Node http → Fastify).
Web API when server present: If @robojs/server is installed, expose REST endpoints under /api/tickets/**, mirroring how @robojs/ai reveals /api/ai/chat.
Non-Goals
No SLAs (first-response/resolution timers).
No external DB requirement; Flashcore by default (adapters optional later).
User stories
Members can run /ticket open to create a private thread with staff visibility.
Staff can assign/claim, add watchers, escalate to moderation, close/archive—using buttons/selects.
Admins can run /ticket setup inside the server to choose intake channel and staff roles (no file config).
Discord requirements
Use private threads for each ticket in a configured intake channel. Bot needs CREATE_PRIVATE_THREADS, SEND_MESSAGES_IN_THREADS, MANAGE_THREADS. Threads should be archived on close.
Slash commands, buttons, selects, and modals must follow Discord interaction rules.
Persistence: Flashcore KV with guildId namespace (tickets:${guildId}:*).
Ephemeral: State for cooldowns and in-flight UI context.
Commands (file-based)
/ticket open [category] [priority] → modal for subject/details → create private thread + header message w/ buttons.
/ticket setup (admin only) → interactive wizard to pick intake channel & staff roles (writes to Flashcore).
/ticket list [status] (staff) → recent tickets.
/ticket assign [user] (staff), /ticket close [reason] (staff), /ticket add-watcher [user], /ticket transfer [user|role].
Robo auto-registers commands from /src/commands/**; return values are delivered by Sage.
Events & middleware
interactionCreate handlers for modals/buttons/selects (routes by customId prefixes like tickets:*).
Middleware tickets.guard.ts: role guard + per-user open cooldown; short-circuit with a friendly message (Sage).
Thread lifecycle
Create private thread in the intake channel; map archiveMinutes to Discord’s valid auto-archive durations.
On Close: post summary, archive thread, persist closedAt, append to history.
Optional (auto-detected): if @robojs/cron exists, schedule stale/auto-archive tasks.
Web API (enabled only if @robojs/server is installed)
Robo’s server plugin maps files in /src/api to routes (default /api prefix). We’ll follow the same pattern and expose a simple JSON API for dashboards, similar to how @robojs/ai exposes /api/ai/chat.
Routes (proposal):
GET /api/tickets — list tickets (query: status, limit, cursor).
GET /api/tickets/:id — fetch one.
POST /api/tickets — create (subject, details, category, priority, openerId).
API routes follow @robojs/server’s /src/api → /api/** mapping.
Example snippets
/src/commands/ticket/open.ts
importtype{CommandResult}from'robo.js'import{ModalBuilder,TextInputBuilder,ActionRowBuilder,TextInputStyle}from'discord.js'exportdefaultasync(interaction): Promise<CommandResult>=>{constmodal=newModalBuilder().setCustomId('tickets:open').setTitle('Open a Ticket')constsubject=newTextInputBuilder().setCustomId('tickets:subject').setLabel('Subject').setStyle(TextInputStyle.Short).setRequired(true)constdetails=newTextInputBuilder().setCustomId('tickets:details').setLabel('Describe the issue').setStyle(TextInputStyle.Paragraph)modal.addComponents(newActionRowBuilder<TextInputBuilder>().addComponents(subject),newActionRowBuilder<TextInputBuilder>().addComponents(details))awaitinteraction.showModal(modal)return}
Uses modals per Discord interactions; Sage handles deferral/replies.
Durable storage via Flashcore; private thread creation aligns with Robo usage patterns.
/src/middleware/01-tickets.guard.ts
import{State}from'robo.js'constcooldown=newState('tickets:cooldown')exportdefaultasync({ record, payload })=>{if(!record?.key?.startsWith('ticket/'))returnconst[interaction]=payloadconstkey=`${interaction.user.id}:open`constnow=Date.now()constuntil=cooldown.get<number>(key)if(until&&until>now){return{abort: true,result: `Please wait ${Math.ceil((until-now)/1000)}s before opening another ticket.`}}cooldown.set(key,now+30_000)}
Middleware short-circuits execution with friendly messages.
Web API route (only when @robojs/server is present): /src/api/tickets/index.ts
importtype{RoboRequest,RoboReply}from'@robojs/server'import{Flashcore}from'robo.js'exportdefaultasync(req: RoboRequest,reply: RoboReply)=>{if(req.method==='GET'){const{ status ='open'}=req.queryasany// fetch & page results from Flashcore by guild context (to be defined)return{items: [],nextCursor: null}}if(req.method==='POST'){constbody=awaitreq.json()// create a ticket record + (optionally) thread, if openerId & guild context providedreturn{id: 'new-ticket-id'}}returnreply.code(405).send('Method not allowed')}
Cron (@robojs/cron): if installed, provide optional stale reminders/auto-close jobs.
These follow Robo’s “plugins add capabilities instantly” philosophy and the pattern of server engine auto-selection.
Acceptance criteria
TypeScript only; compiles to ESM and runs in robo dev/start.
Guild-level setup works: /ticket setup selects intake channel & staff roles (persisted in Flashcore). No role/channel IDs in /config.
/ticket open creates a private thread, posts a header, and persists ticket.
Assign/Close/Watch actions via buttons/selects update the Flashcore record and the thread header; Close archives the thread.
Middleware guard enforces per-user cooldown and staff-role checks.
Auto integrations: If any of @robojs/moderation, @robojs/i18n, @robojs/analytics, @robojs/cron are installed, features activate automatically.
Web API exists only when @robojs/server is present and exposes at least: GET /api/tickets, GET /api/tickets/:id, POST /api/tickets, PATCH /api/tickets/:id.
Docs: README covers install (npx robo add @robojs/tickets), permissions, commands, Web API, and integration behavior.
Summary
Build a first-party-quality plugin for Robo.js that turns any bot into a lightweight helpdesk: open/assign/escalate/close tickets in Discord threads, with optional web APIs (if
@robojs/serveris installed) so maintainers can build dashboards later. The plugin must feel Robo-native: file-based commands/events, Sage returns, Middleware for guards, Flashcore for durable storage, and State for ephemeral context.Goals
Zero-config install:
npx robo add @robojs/tickets. The plugin should “just work” after minimal in-guild setup.Guild-level setup only: Channel/role choices are configured inside the guild (via slash commands/modals). No role/channel IDs in
/configfiles.Automatic integrations: If packages exist, use them—no toggles:
@robojs/moderation→ escalate/attach moderation actions.@robojs/i18n→ localize messages.@robojs/analytics→ emit ticket events.@robojs/cron→ stale or auto-close flows (optional).Detection should be runtime (dynamic import) like how
@robojs/serverautomatically switches engines (Node http → Fastify).Web API when server present: If
@robojs/serveris installed, expose REST endpoints under/api/tickets/**, mirroring how@robojs/aireveals/api/ai/chat.Non-Goals
User stories
/ticket opento create a private thread with staff visibility./ticket setupinside the server to choose intake channel and staff roles (no file config).Discord requirements
CREATE_PRIVATE_THREADS,SEND_MESSAGES_IN_THREADS,MANAGE_THREADS. Threads should be archived on close.Architecture
Data model (Flashcore, per-guild namespace)
tickets:${guildId}:*).Statefor cooldowns and in-flight UI context.Commands (file-based)
/ticket open [category] [priority]→ modal for subject/details → create private thread + header message w/ buttons./ticket setup(admin only) → interactive wizard to pick intake channel & staff roles (writes to Flashcore)./ticket list [status](staff) → recent tickets./ticket assign [user](staff),/ticket close [reason](staff),/ticket add-watcher [user],/ticket transfer [user|role].Robo auto-registers commands from
/src/commands/**; return values are delivered by Sage.Events & middleware
interactionCreatehandlers for modals/buttons/selects (routes bycustomIdprefixes liketickets:*).tickets.guard.ts: role guard + per-user open cooldown; short-circuit with a friendly message (Sage).Thread lifecycle
archiveMinutesto Discord’s valid auto-archive durations.closedAt, append tohistory.@robojs/cronexists, schedule stale/auto-archive tasks.Web API (enabled only if
@robojs/serveris installed)Robo’s server plugin maps files in
/src/apito routes (default/apiprefix). We’ll follow the same pattern and expose a simple JSON API for dashboards, similar to how@robojs/aiexposes/api/ai/chat.Routes (proposal):
GET /api/tickets— list tickets (query:status,limit,cursor).GET /api/tickets/:id— fetch one.POST /api/tickets— create (subject, details, category, priority, openerId).PATCH /api/tickets/:id— assign/close/escalate/update fields.POST /api/tickets/:id/watchers— add watcher.DELETE /api/tickets/:id/watchers/:userId— remove watcher.Implementation notes:
/src/api/tickets/**(file-route mapping). Return values or throw viaRoboResponseas needed.TypeScript & ESM conventions
"type": "module"; align with Robo’s TS guidance. (Resources for ESM/TS interop if needed.)File layout (generated by the plugin)
@robojs/server’s/src/api→/api/**mapping.Example snippets
/src/commands/ticket/open.ts/src/events/interactionCreate/tickets.modals.ts/src/middleware/01-tickets.guard.tsWeb API route (only when
@robojs/serveris present):/src/api/tickets/index.ts@robojs/server“file → route” behavior; throwing/returning shapes responses.Integrations (auto-detected)
At runtime, attempt
import()and proceed if successful; do not require config flags.@robojs/moderation): add “Escalate to moderation” action to create/link a case.@robojs/i18n): if present, wrap strings witht('tickets:key'); ship a defaulten/tickets.json.@robojs/analytics): emittickets_open,tickets_assign,tickets_close.@robojs/cron): if installed, provide optional stale reminders/auto-close jobs.These follow Robo’s “plugins add capabilities instantly” philosophy and the pattern of server engine auto-selection.
Acceptance criteria
robo dev/start./ticket setupselects intake channel & staff roles (persisted in Flashcore). No role/channel IDs in/config.@robojs/moderation,@robojs/i18n,@robojs/analytics,@robojs/cronare installed, features activate automatically.@robojs/serveris present and exposes at least:GET /api/tickets,GET /api/tickets/:id,POST /api/tickets,PATCH /api/tickets/:id.npx robo add @robojs/tickets), permissions, commands, Web API, and integration behavior.References
@robojs/server: API routes via/src/api, request/reply types, throwable responses.@robojs/ai: shows the pattern for exposing web APIs when the server plugin is present (/api/ai/chat). We’ll mirror this for/api/tickets/**.How to claim (Hacktoberfest)