/event — Command Reference¶
Access¶
All /event subcommands require Chapter Organizer or higher.
Event Context — event_id Resolution¶
Most commands accept an optional event_id. When omitted, the bot matches the current channel ID against Events.organizer_thread and Events.Discord_event URLs in Grist. If no match is found, an error is returned with instructions to re-run with an explicit ID.
event_id is the UUID text field (Events.event_ID), not the Grist row number.
/event new¶
/event new [date] [region] [structured_event]
All three params are required. region and structured_event use Discord autocomplete from the configured ChoiceList values. date is a free-text string parsed flexibly: 6/14, June 14, 2025-06-14, TBD. Time defaults to 10:00 America/Los_Angeles when not specified.
Duplicate detection — before creating anything, the bot checks Events for rows in the same region within ±2 days. If a match is found, an ephemeral embed shows the existing event with three options:
- Use existing / Link thread — behavior depends on what already exists:
- Post + thread exist: confirms with no changes.
- Post exists, no thread: creates an organizer thread and links it.
- Neither exists: creates an organizer thread only. Run
/event postseparately to create the public post. - Create new event — proceeds with full creation despite the potential duplicate.
- Cancel — aborts with no writes.
Creation flow:
- Ephemeral confirmation embed with parsed values (date, region, type, template, location, prereqs, counts-for-ToG). Edit details and Edit notes buttons open modals for corrections before confirming.
- Grist row created in
Events. - Organizer thread created in
#firearms-instruction-workstream(public thread). Initial organizer post (roles + RSVP placeholder) posted and pinned.Events.organizer_threadandEvents.organizer_post_message_idwritten to Grist. - Ephemeral reply with Grist row ID and organizer thread link, with a reminder to run
/event post.
Run /event post when ready to create the member-facing forum post in #range-days-and-events.
If the command is run from inside an existing thread in #firearms-instruction-workstream, that thread is used as the organizer thread instead of creating a new one.
Optional params:
- location — location nickname or URL; autocompletes from Event_locations. Defaults to the region's configured location if omitted. When resolution matches a known Event_locations nickname (explicit match, or single-region auto-default), the matched row is also written to Events.location (Reference(Event_locations)) — consumed by /mileage request to auto-fill the pit when run from this event's organizing thread. Left unset if the input is a raw URL/free text or multiple regions make the default ambiguous.
- prereqs — override prereqs; autocompletes from configured values. Defaults to per-type defaults. Pass none to clear.
- counts_for_tog — boolean override for whether this event counts toward Toucher of Grass. Defaults to per-type setting.
/event post¶
/event post
Creates the member-facing forum post in #range-days-and-events. Run from inside the organizing thread — the event is inferred from the thread automatically. No parameters.
Errors if run outside an organizing thread (use /event info to find the thread link), or if the event already has a post.
/event info¶
/event info [event_id]
Shows the organizer thread and event post links for an event. Use this to find the organizing thread before running /event post, or to check what's been set up.
event_id autocompletes from events within the past 14 days and the next 25 upcoming, sorted by date. TBD/undated events appear at the end.
/event edit¶
/event edit [event_id]
Opens an ephemeral dropdown listing each block of the live event forum post with a short excerpt. Selecting a block opens a prepopulated modal with the full block text. Submitting the modal replaces only that block in the post.
Works regardless of which template was used or whether the post has been edited before. The live embeds in the Discord message are the source of truth — no template or section name knowledge required.
event_id is optional; inferred from the current organizing thread if omitted.
/event role add / /event role remove¶
/event role add [role] [member1] [...] [event_id]
/event role remove [role] [member1] [...] [event_id]
role autocompletes: instructor, TA, greeter, medic. Up to 5 members at once.
Each member is resolved to a People row via Discord snowflake. If any member cannot be resolved, the entire operation is aborted with an error listing which ones failed.
On success:
- Updates the corresponding ReferenceList column in Grist (instructors, TAs, greeter, medic).
- Edits the roles block in the live event forum post.
- Refreshes the pinned organizer post.
- Ephemeral confirmation with member mentions and post update status.
For competition events (structured_event contains Comp), the instructor role is labeled Match Director in posts.
/event attendance multi-add¶
/event attendance multi-add [event_id]
Multi-step interactive flow for logging post-event attendance and no-shows.
Do not log instructors, TAs, medic, or other non-shooters as attendees. Use /event role add / /event role remove to keep role assignments current.
Step 1 — Select from accepted RSVPs. Paginated in groups of 25 if there are more. Previous page and Next page buttons carry selections across pages. Continue advances to step 2 with accumulated selections.
Step 2 — UserSelect for members not on the RSVP list (extras). Up to 25. Continue resolves these to People rows immediately (rather than waiting for the final modal) so step 3 can exclude anyone just confirmed as attending here.
Step 3 — Select who was a no-show, from accepted RSVPs not already accounted for as an attendee (by this run's step 1/2 selections, or by a prior run's persisted Events.attendees). Skipped entirely if there's no one left to ask about — an accepted RSVP is never assumed to be a no-show just because they weren't picked as an attendee; someone who withdrew their RSVP, for instance, never reaches this list at all, since that already drops their RSVP out of "accepted." Same pagination/carry-forward mechanics as step 1.
Step 4 — UserSelect for no-shows not on the RSVP list at all. Up to 25.
Step 5 — Modal with an optional guests text field for non-member names (free text, comma-separated, appended to Events.guests_comma_sep_).
On submit:
- Merges selected people IDs into Events.attendees and Events.no_shows (deduplicates both). Nobody ever ends up in both lists for the same event — if this run's selections put someone in attendees who was previously flagged as a no-show, the no-show flag is cleared.
- Re-fetches the event to get the computed grass_touchers field.
- Posts a summary to the organizer thread:
**Attendance logged**
**Touchers of grass (2):** @alice, @bob
**Everyone else (3):** @carol, @dave, @eve
**No-shows (1):** @frank
Guests: "Alex Smith"
The grass touchers section only appears for events where counts_for_ToG is true; it always shows even if zero, so the absence of touchers is explicit. The no-shows line only appears when Events.no_shows is non-empty.
/event attendance add¶
/event attendance add [member] [event_id]
Adds a single member to Events.attendees. Warns if already present. If the member was previously flagged as a no-show for this event, clears them from Events.no_shows at the same time (noted in the confirmation reply). Posts a one-line log to the organizer thread.
/event attendance remove¶
/event attendance remove [member] [event_id]
Removes a single member from Events.attendees. Warns if not present. Posts a one-line log to the organizer thread. Doesn't touch Events.no_shows.
/event noshow add¶
/event noshow add [member]
Adds a single member to Events.no_shows, for corrections outside the full multi-add wizard. Warns if already present. If the member is currently logged as an attendee, clears them from Events.attendees at the same time (noted in the confirmation reply) — a no-show flag always wins over a stale attendee record, same rule as multi-add's combined write. Posts a one-line log to the organizer thread.
/event noshow remove¶
/event noshow remove [member]
Removes a single member from Events.no_shows. Warns if not present. Posts a one-line log to the organizer thread. Doesn't touch Events.attendees.
/event attendance add-guest¶
/event attendance add-guest
Pops up a free-text modal. Enter one or more guest names (comma-separated or however you like). The text is appended to any existing Events.guests_comma_sep_ value. Posts a log line to the organizer thread.
/event attendance tog-add-role¶
/event attendance tog-add-role
Requires Chapter Representative or above.
For every attendee on the event who is marked as a Toucher of Grass (Events.grass_touchers):
- Grants the Toucher of Grass Discord role (if the member is in the server and doesn't already have it).
- Creates a row in the Grist
touchers_of_grasstable with theirdiscord_id(snowflake), skipping members already recorded there.
Posts a summary to the organizer thread showing who was granted the role, who already had it, and any errors.
Only runs on events where counts_for_ToG is true.
/event attendance list¶
/event attendance list
Posts the current attendance list to the organizer thread, grouped by Touchers of Grass and everyone else. Shows role assignments (Greeter, Instructors, TAs, Medic) and any logged guests.
Organizer Thread¶
Each event has a pinned post in its organizer thread (in #firearms-instruction-workstream) that is kept in sync automatically. The post shows:
- Event post URL
- Current role assignments (Greeter, Instructors/Match Director, TAs, Medic)
- RSVPs grouped by status (Accepted / Waitlisted / Interest Noted / Pending) with gear/flag annotations
- Legend and footnotes for annotated RSVPs
The post is refreshed on every RSVP status change, /rsvp list, and /rsvp identify completion. The message ID is stored in Events.organizer_post_message_id to avoid repeated pin scans.
Legend:
^ toucher of grass
º has taken [event type] before
~ joined <2mo ago
🔫 needs firearm
🪣 needs holster
🥽 needs eye protection
👂 needs ear protection
❓ needs other
[n mo] = months since vetted