@robojs/giveaways - One‑click Discord giveaways
Summary
A community‑built Robo.js plugin that makes giveaways effortless for members and safe for moderators. Users click Enter on a giveaway message to join; the bot picks winners automatically when the timer expires, posts results, and offers admin tools to end early, reroll, or cancel. The plugin ships with reliable persistence, optional web endpoints, and a small imperative settings API so teams can build dashboards later.
Goals
- Delightful for regular users: zero‑friction entry, celebratory results, optional winner DMs.
- Reliable for staff: predictable timing, restart‑safe storage, simple rerolls/cancels.
- Easy to adopt: sane defaults, per‑guild settings, clean commands.
- Extensible: optional analytics/i18n hooks (not required), optional web API, exported settings API.
User Stories
- Member: “I saw a giveaway, tapped Enter, got a confirmation, and later saw winners announced.”
- Moderator: “I can start a giveaway with basic options, restrict by role or account age, and reroll if needed.”
- Owner: “I set server defaults once (duration, winners, labels) and they apply to future giveaways.”
Acceptance Criteria
- Slash commands implemented:
start,end,reroll,cancel,list,info, andsettings get/set/reset. - Button interaction for joining (and optionally leaving) with clear user feedback.
- Persistence via Flashcore (durable across bot restarts); active giveaways are recovered on startup.
- Automatic ending at the configured time and manual
/giveaway endsupported. - Winner selection is fair (no duplicates, handles not-enough entrants), and results are posted back to the original message.
- Reroll selects new winners from remaining eligible entrants; logs a minimal reroll history in the record.
- Cancel updates post and prevents further entries.
- Per-guild settings: change and persist defaults via slash commands and an imperative API (exported) for future dashboards.
- Scheduling works without extra deps (fallback timer/sweep) and upgrades automatically to cron when
@robojs/cronis present. - Optional web API (only if
@robojs/serveris installed): list giveaways, fetch details, and mutate status/settings. - Permission checks for moderator actions (e.g., Manage Guild or a configured manager role).
- Idempotent finalize: no double endings after restarts or rapid repeated triggers.
- Clear UX: tidy embeds, obvious call-to-action, friendly ephemeral responses and error states.
- Tests encouraged but not required; if present, include a simple Flashcore stub/mocks.
Features
Core
- Start giveaway with embed + Enter button.
- Auto end at time; manual end.
- Reroll, cancel, list, info.
- Role allow/deny and minimum account age.
- One entry per user (optionally extendable to weighted entries later).
Optional (nice‑to‑have, not required for acceptance)
- Winner DMs and post‑end follow‑ups.
- i18n/analytics hooks.
Command & Interaction Design
Slash Commands (top‑level: /giveaway)
-
/giveaway startprize(string, required)duration(string like10m|1h|2dor timestamp)winners(int, default 1)channel(optional; default current)allow_roles[],deny_roles[](optional)min_account_age_days(int, optional)
-
/giveaway end— ends now, draws winners. -
/giveaway reroll— rerolls N winners from remaining eligible entrants. -
/giveaway cancel— cancels without drawing winners. -
/giveaway list— list active and recent completed giveaways. -
/giveaway info— details by message ID or internal ID. -
/giveaway settings get|set|reset— manage per‑guild defaults and restrictions.
Component Interactions
- Enter button →
custom_id = ga:enter:<id> - (Optional) Leave button →
ga:leave:<id>
Messages/Embeds
- Start post: Title
🎉 Giveaway: <prize>; fields forEnds,Winners,Hosted by; primary Enter button. - End post: Mark as Ended; list winners; keep the message as audit history.
Per‑Guild Settings
Two ways to control behavior:
- Declarative defaults (config file) — e.g.,
config/plugins/robojs/giveaways.mjswith fields likedefaults.winners,defaults.duration,defaults.buttonLabel, andlimits.maxDurationDays. - Imperative API (exported) — a small module exported by the plugin to read/update persisted settings for a given
guildId. Useful for dashboards and integrations.
Settings surface (conceptual):
defaults:winners,duration,dmWinners,buttonLabel.limits:maxWinners,maxDurationDays.restrictions:allowRoleIds,denyRoleIds,minAccountAgeDays.
The slash command
settings set|get|resetoperates on the same stored values.
Data Model (conceptual)
Giveaway
id(ulid)guildId,channelId,messageIdprizewinnersCountendsAt(ms)startedBy(userId)status(active | ended | cancelled)allowRoleIds[],denyRoleIds[],minAccountAgeDays(optional)entries(userId → weight)winners[](userIds)rerolls[][](each reroll is an array of new winner userIds)createdAt,finalizedAt?
Indexes (namespaced by guild)
activeids,recentids (capped)by-message:<messageId> → id
Scheduling
-
Default (no extra deps):
- On start: scan active giveaways and schedule local timers; sweep periodically for long‑range expirations.
-
Upgrade path (if
@robojs/cronpresent):- Schedule precise end jobs and persist job ids; recover them on startup.
Both modes must be restart‑safe and idempotent when finalizing.
Optional Web API (for future dashboards)
Only enabled if
@robojs/serveris installed. See @robojs/ai for reference.
Routes (JSON):
GET /api/giveaways?guildId=...→{ active: Giveaway[], recent: Giveaway[] }GET /api/giveaways/:guildId/:id→GiveawayPATCH/POST /api/giveaways/:guildId/:id→ mutate status (end|cancel|reroll) or certain fieldsGET /api/giveaways/:guildId/settings→GuildSettingsPATCH /api/giveaways/:guildId/settings→ update settings
Architecture / File Layout
src/
commands/
giveaway/
start.ts
end.ts
reroll.ts
cancel.ts
list.ts
info.ts
settings/
get.ts
set.ts
reset.ts
events/
interactionCreate.ts // handles Enter/Leave buttons
ready.ts // recovery & scheduling on boot
middleware/
giveaways-guard.ts // permission & basic validation
api/ // OPTIONAL; only if @robojs/server is installed
giveaways/
[guildId]/index.get.ts
[guildId]/[id].get.ts
[guildId]/[id].patch.ts
[guildId]/settings.get.ts
[guildId]/settings.patch.ts
Security & Fairness
- Require appropriate permissions for admin commands.
- Enforce one entry per user (unless weighted mode is enabled).
- Transparent winner selection (documented approach; no duplicates).
- Respect channel visibility; avoid leaking entrant lists publicly.
- Handle deleted channels/messages gracefully; fail safely.
Edge Cases & Reliability
- Idempotent finalization: if already ended/cancelled, ignore subsequent triggers.
- Restart recovery: re‑schedule pending ends; skip any already finalized.
- Insufficient entrants: allow partial winner lists; explain outcome.
- Role/age changes mid‑giveaway: re‑validate at drawing time.
- Rate limiting: basic per‑user debounce on Enter clicks (avoid spam).
Testing (encouraged, not required)
- Unit tests for winner selection and eligibility checks.
- Storage tests with a Flashcore stub; verify recovery path on startup.
- Interaction tests for
Enterbutton and basic slash command flows.
Done When
- The acceptance criteria checklist at the top is fully checked in review.
- README explains setup, required permissions, commands, and optional integrations.
- The plugin runs in a fresh Robo.js project and behaves gracefully with or without
@robojs/cron/@robojs/serverinstalled.
Hi @Pkmmte, can you assign me this task?
@AdityaTel89 Sure, task has been assigned! Feel free to reach out either here or on our Discord if you have any questions.
Hi! @Pkmmte, I've resolved issue #449 by implementing the giveaway bot functionality. The bot is now working as expected and I've submitted a PR. Please review when you get a chance. Thanks! 🎉