robo.js icon indicating copy to clipboard operation
robo.js copied to clipboard

@robojs/giveaways - One‑click Discord giveaways

Open Pkmmte opened this issue 2 months ago • 3 comments

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, and settings 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 end supported.
  • 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/cron is present.
  • Optional web API (only if @robojs/server is 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 start

    • prize (string, required)
    • duration (string like 10m|1h|2d or 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 buttoncustom_id = ga:enter:<id>
  • (Optional) Leave buttonga:leave:<id>

Messages/Embeds

  • Start post: Title 🎉 Giveaway: <prize>; fields for Ends, 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:

  1. Declarative defaults (config file) — e.g., config/plugins/robojs/giveaways.mjs with fields like defaults.winners, defaults.duration, defaults.buttonLabel, and limits.maxDurationDays.
  2. 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|reset operates on the same stored values.

Data Model (conceptual)

Giveaway

  • id (ulid)
  • guildId, channelId, messageId
  • prize
  • winnersCount
  • endsAt (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)

  • active ids, recent ids (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/cron present):

    • 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/server is installed. See @robojs/ai for reference.

Routes (JSON):

  • GET /api/giveaways?guildId=...{ active: Giveaway[], recent: Giveaway[] }
  • GET /api/giveaways/:guildId/:idGiveaway
  • PATCH/POST /api/giveaways/:guildId/:id → mutate status (end|cancel|reroll) or certain fields
  • GET /api/giveaways/:guildId/settingsGuildSettings
  • PATCH /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 Enter button 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/server installed.

Pkmmte avatar Oct 06 '25 05:10 Pkmmte

Hi @Pkmmte, can you assign me this task?

AdityaTel89 avatar Oct 06 '25 06:10 AdityaTel89

@AdityaTel89 Sure, task has been assigned! Feel free to reach out either here or on our Discord if you have any questions.

Pkmmte avatar Oct 06 '25 08:10 Pkmmte

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! 🎉

AdityaTel89 avatar Oct 08 '25 10:10 AdityaTel89