diff --git a/assets/docs/ShaiWatcher.png b/assets/docs/ShaiWatcher.png new file mode 100644 index 0000000..6e13833 Binary files /dev/null and b/assets/docs/ShaiWatcher.png differ diff --git a/assets/docs/android-chrome-192x192.png b/assets/docs/android-chrome-192x192.png new file mode 100644 index 0000000..fcd7ecc Binary files /dev/null and b/assets/docs/android-chrome-192x192.png differ diff --git a/assets/docs/android-chrome-512x512.png b/assets/docs/android-chrome-512x512.png new file mode 100644 index 0000000..3ae3887 Binary files /dev/null and b/assets/docs/android-chrome-512x512.png differ diff --git a/assets/docs/apple-touch-icon.png b/assets/docs/apple-touch-icon.png new file mode 100644 index 0000000..b195a4c Binary files /dev/null and b/assets/docs/apple-touch-icon.png differ diff --git a/assets/docs/cmd.html b/assets/docs/cmd.html new file mode 100644 index 0000000..0cfe6e2 --- /dev/null +++ b/assets/docs/cmd.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + +__TITLE__ + + + +
+
+
__TITLE__
+
+ +
+
+
+ +
+ +
+ +
+ Sections: User · Moderator · All + + +
+
+ +
+ +
+
+
+
+ + + + +
+ + +
+
+ + +
+ + +
+ + + +
+ + + +
Copied!
+
+ + + + diff --git a/assets/docs/commands/AutoVCCog.avc_cleanup_now.brief.html b/assets/docs/commands/AutoVCCog.avc_cleanup_now.brief.html new file mode 100644 index 0000000..435bab5 --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_cleanup_now.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /avc_cleanup_now [Moderator]

+

Immediately deletes empty Auto-VC rooms that have been idle past the configured delay, then renumbers the rest.

+ +
/avc_cleanup_now
+ +

+ Replies “Cleanup pass complete.” on success. +

diff --git a/assets/docs/commands/AutoVCCog.avc_cleanup_now.details.html b/assets/docs/commands/AutoVCCog.avc_cleanup_now.details.html new file mode 100644 index 0000000..e91c9bf --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_cleanup_now.details.html @@ -0,0 +1,23 @@ +

What it does

+ + +
+ +

When to use

+ + +
+ +

Permissions & notes

+ diff --git a/assets/docs/commands/AutoVCCog.avc_renumber.brief.html b/assets/docs/commands/AutoVCCog.avc_renumber.brief.html new file mode 100644 index 0000000..6efd4ff --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_renumber.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /avc_renumber [Moderator]

+

Force a rename of all managed rooms to a clean numeric order without deleting anything.

+ +
/avc_renumber
+ +

+ Replies “Renumbered.” on success. +

diff --git a/assets/docs/commands/AutoVCCog.avc_renumber.details.html b/assets/docs/commands/AutoVCCog.avc_renumber.details.html new file mode 100644 index 0000000..ed92d80 --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_renumber.details.html @@ -0,0 +1,19 @@ +

What it does

+

Renames tracked rooms to {prefix} 1, {prefix} 2, … in creation order. No rooms are deleted.

+ +
+ +

When to use

+ + +
+ +

Permissions & notes

+ diff --git a/assets/docs/commands/AutoVCCog.avc_status.brief.html b/assets/docs/commands/AutoVCCog.avc_status.brief.html new file mode 100644 index 0000000..032e03e --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_status.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /avc_status

+

Shows current Auto-VC setup and the list of managed rooms.

+ +
/avc_status
+ +

+ Output includes trigger channel, target category, name prefix, cleanup delay, and each tracked room with its current state. +

diff --git a/assets/docs/commands/AutoVCCog.avc_status.details.html b/assets/docs/commands/AutoVCCog.avc_status.details.html new file mode 100644 index 0000000..7f614a0 --- /dev/null +++ b/assets/docs/commands/AutoVCCog.avc_status.details.html @@ -0,0 +1,22 @@ +

What it shows

+ + +
+ +

Example output

+
+Auto-VC status:
+Trigger: <#1234567890> | Category: <#2345678901> | Prefix: `Room` | Delay: 30s
+- #1: Room 1 — 3 inside
+- #2: Room 2 — empty | idle 12s
+
+ +

+ This command is read-only and safe to run anytime. +

diff --git a/assets/docs/commands/NickNudgeCog.clear_nick_reviews.brief.html b/assets/docs/commands/NickNudgeCog.clear_nick_reviews.brief.html new file mode 100644 index 0000000..86b1030 --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.clear_nick_reviews.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /clear_nick_reviews [Moderator]

+

Delete all pending nickname review records for this server.

+ +
/clear_nick_reviews
+ +

+Replies with how many pending entries were removed (ephemeral). +

diff --git a/assets/docs/commands/NickNudgeCog.clear_nick_reviews.details.html b/assets/docs/commands/NickNudgeCog.clear_nick_reviews.details.html new file mode 100644 index 0000000..862acce --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.clear_nick_reviews.details.html @@ -0,0 +1,21 @@ +

What it does

+ + +
+ +

Result

+

+ Shows: Cleared N pending nickname review(s). (ephemeral) +

+ +
+ +

Permissions & notes

+ diff --git a/assets/docs/commands/NickNudgeCog.recreate_nick_review.brief.html b/assets/docs/commands/NickNudgeCog.recreate_nick_review.brief.html new file mode 100644 index 0000000..68d4493 --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.recreate_nick_review.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /recreate_nick_review [Moderator]

+

Recreate a single user’s missing pending review.

+ +
/recreate_nick_review user: @Member
+ +

+If they’re already verified or already have a pending review, you’ll get a short explanation instead (ephemeral). +

diff --git a/assets/docs/commands/NickNudgeCog.recreate_nick_review.details.html b/assets/docs/commands/NickNudgeCog.recreate_nick_review.details.html new file mode 100644 index 0000000..dac7239 --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.recreate_nick_review.details.html @@ -0,0 +1,25 @@ +

What it does

+ + +
+ +

When to use

+ + +
+ +

Permissions & notes

+ diff --git a/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.brief.html b/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.brief.html new file mode 100644 index 0000000..d8c6ff5 --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.brief.html @@ -0,0 +1,8 @@ +

Quick usage — /recreate_nick_reviews [Moderator]

+

Scan the server and recreate any missing pending reviews for users who claimed but never got a review opened.

+ +
/recreate_nick_reviews
+ +

+Replies: Recreated X review(s); skipped Y. (ephemeral) +

diff --git a/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.details.html b/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.details.html new file mode 100644 index 0000000..844b39d --- /dev/null +++ b/assets/docs/commands/NickNudgeCog.recreate_nick_reviews.details.html @@ -0,0 +1,22 @@ +

What it does

+ + +
+ +

When to use

+ + +
+ +

Permissions & notes

+ diff --git a/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.brief.html b/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.brief.html new file mode 100644 index 0000000..299c0e6 --- /dev/null +++ b/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.brief.html @@ -0,0 +1,17 @@ +

/pirate_cards_rebuild

+

Rebuild or update pirate cards for every known pirate in the configured channel.

+ +

Usage

+
/pirate_cards_rebuild
+ + + +

What it does

+ diff --git a/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.details.html b/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.details.html new file mode 100644 index 0000000..9e841fc --- /dev/null +++ b/assets/docs/commands/PirateCardsCog.pirate_cards_rebuild.details.html @@ -0,0 +1,44 @@ +

/pirate_cards_rebuild — Rebuild all pirate cards

+

Runs a full pass that makes sure each verified pirate has a fresh, accurate card in the configured channel.

+ +

Access

+ + +

What it updates

+ + +

Behavior

+
    +
  1. For each pirate in the internal list: + +
  2. +
  3. Works serially per guild to avoid race conditions.
  4. +
+ +

Output

+

Ephemeral summary, e.g.:

+
Rebuilt/updated 27 pirate cards.
+ +

Setup notes

+ + +

When to run it

+ diff --git a/assets/docs/commands/PirateReportCog.edit_pirate.brief.html b/assets/docs/commands/PirateReportCog.edit_pirate.brief.html new file mode 100644 index 0000000..0032867 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.edit_pirate.brief.html @@ -0,0 +1,16 @@ +

Quick usage — /edit_pirate [Moderator]

+ +

Edit an existing pirate entry. Use this when a player changes nickname or account tag.

+ + + +
/edit_pirate
+ +

+ You must provide at least one of: new nickname or new account.
+ The account format is strict: # + five digits. Duplicate accounts are blocked. +

diff --git a/assets/docs/commands/PirateReportCog.edit_pirate.details.html b/assets/docs/commands/PirateReportCog.edit_pirate.details.html new file mode 100644 index 0000000..6ef7f26 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.edit_pirate.details.html @@ -0,0 +1,24 @@ +

What it does

+

+ Updates a pirate record that’s already approved. Typical cases: the player renamed their character, + or moved to a new account tag. +

+ +
+ +

Form fields

+ + +
+ +

Rules & feedback

+ diff --git a/assets/docs/commands/PirateReportCog.encounter.brief.html b/assets/docs/commands/PirateReportCog.encounter.brief.html new file mode 100644 index 0000000..f1a72f6 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.encounter.brief.html @@ -0,0 +1,21 @@ +

Quick usage — /encounter

+ +

Log a pirate encounter so threat scores stay fresh. Takes ~10 seconds.

+ + + +

Rate limit: you can report the same pirate once every 10 minutes.

+ +
+
/encounter MuadDib#12345 · group 3 · kills 1 · destructive yes · skill 4
+
/encounter SandStalker · group 5 · kills 0 · destructive no · skill 2
+
+ +

After you submit, you’ll get a private “Encounter recorded” message and the pirate list updates shortly after.

diff --git a/assets/docs/commands/PirateReportCog.encounter.details.html b/assets/docs/commands/PirateReportCog.encounter.details.html new file mode 100644 index 0000000..a981410 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.encounter.details.html @@ -0,0 +1,75 @@ +

What it does

+

+ /encounter adds a single encounter to the pirate’s history. These entries feed the + threat score so the pirate list reflects how dangerous someone actually is right now. +

+ +
+ +

Form fields

+ + +
+ /encounter MuadDib#12345 · group 3 · kills 1 · destructive yes · skill 4 +
+ +
+ +

Validation & messages

+ + +
+ +

How threat is calculated (short)

+

+ Each encounter contributes to a weighted score (0–100): +

+ +

+ The weights are configurable (defaults: Kill 0.35, Destruction 0.30, Group 0.20, Skill 0.15). After you submit, + the bot recomputes the pirate’s threat level and encounter count, then refreshes the list. +

+ +
+ +

Tips

+ + +
+ +

Common errors

+ diff --git a/assets/docs/commands/PirateReportCog.encounters_migrate_ids.brief.html b/assets/docs/commands/PirateReportCog.encounters_migrate_ids.brief.html new file mode 100644 index 0000000..757355d --- /dev/null +++ b/assets/docs/commands/PirateReportCog.encounters_migrate_ids.brief.html @@ -0,0 +1,12 @@ +

Quick usage — /encounters_migrate_ids [Moderator]

+ +

+ Converts old encounter records stored by character name to the + canonical account (Name#12345). +

+ +
/encounters_migrate_ids
+ +

+ Useful after adding pirates where past encounters referenced only nicknames. +

diff --git a/assets/docs/commands/PirateReportCog.encounters_migrate_ids.details.html b/assets/docs/commands/PirateReportCog.encounters_migrate_ids.details.html new file mode 100644 index 0000000..8371efc --- /dev/null +++ b/assets/docs/commands/PirateReportCog.encounters_migrate_ids.details.html @@ -0,0 +1,27 @@ +

What it does

+

+ Scans stored encounters and rewrites any identifier that is a character name to the correct + account for that pirate. Keeps data consistent for threat calculations. +

+ +
+ +

How matching works

+ + +
+ +

Result

+ + +

+ Moderator-only. +

diff --git a/assets/docs/commands/PirateReportCog.remove_pirate.brief.html b/assets/docs/commands/PirateReportCog.remove_pirate.brief.html new file mode 100644 index 0000000..1a7051c --- /dev/null +++ b/assets/docs/commands/PirateReportCog.remove_pirate.brief.html @@ -0,0 +1,9 @@ +

Quick usage — /remove_pirate [Moderator]

+ +

Remove a pirate from the approved list by account.

+ +
/remove_pirate account_name: Name#12345
+ +

+ Account format must be Name#12345. If the account isn’t found, you’ll get “Pirate not found.” +

diff --git a/assets/docs/commands/PirateReportCog.remove_pirate.details.html b/assets/docs/commands/PirateReportCog.remove_pirate.details.html new file mode 100644 index 0000000..751212d --- /dev/null +++ b/assets/docs/commands/PirateReportCog.remove_pirate.details.html @@ -0,0 +1,19 @@ +

What it does

+

Deletes the matching pirate record from the approved list.

+ +
+ +

Parameters

+ + +
+ +

Behavior & messages

+ diff --git a/assets/docs/commands/PirateReportCog.report.brief.html b/assets/docs/commands/PirateReportCog.report.brief.html new file mode 100644 index 0000000..9745d67 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.report.brief.html @@ -0,0 +1,24 @@ +

What it does

+

+ Sends a player to the Pirate Review Queue. Mods get a card with ✅/❌. + If you include a Discord media link, it will preview for them. +

+ +

Quick use — /report

+ + +

Limits

+ + +

+ Tip: Images show inside the card; videos stay as a link so the inline player works. +

diff --git a/assets/docs/commands/PirateReportCog.report.details.html b/assets/docs/commands/PirateReportCog.report.details.html new file mode 100644 index 0000000..6d64dd6 --- /dev/null +++ b/assets/docs/commands/PirateReportCog.report.details.html @@ -0,0 +1,48 @@ +

Submit a pirate report

+

+ Use /report to send a player to the Pirate Review Queue. + Mods see a compact card with your info and can approve or reject with one click. +

+ +

Form fields

+ + +

What happens next

+
    +
  1. You get a short “thanks” message in the channel.
  2. +
  3. Mods receive an embed with ✅/❌. If you added an image, it shows inside the card. + If it’s a video, the URL is kept above the card so the inline player works.
  4. +
  5. A “Jump to message” button lets mods see your original context quickly.
  6. +
  7. When a mod decides, the card updates to show Approved/Rejected with who and when. + Your small ack message in the channel is edited to reflect the result.
  8. +
  9. If approved, the player is added to the pirate list (threat level starts at 0).
  10. +
+ +

Rules & common errors

+ +

+ Typical messages: “Invalid account format”, “URL must be a Discord media link”, “A report for this player is already pending”. +

+ +

Related actions

+ diff --git a/assets/docs/commands/PiratesListCog.pirates_list_refresh.brief.html b/assets/docs/commands/PiratesListCog.pirates_list_refresh.brief.html new file mode 100644 index 0000000..397c41e --- /dev/null +++ b/assets/docs/commands/PiratesListCog.pirates_list_refresh.brief.html @@ -0,0 +1,9 @@ +

Quick usage — /pirates_list_refresh [Moderator]

+

Rebuild the compact pirates list in the configured channel. Use this after adding/removing pirates or when encounter stats change.

+ +
/pirates_list_refresh
+ + diff --git a/assets/docs/commands/PiratesListCog.pirates_list_refresh.details.html b/assets/docs/commands/PiratesListCog.pirates_list_refresh.details.html new file mode 100644 index 0000000..6c191d6 --- /dev/null +++ b/assets/docs/commands/PiratesListCog.pirates_list_refresh.details.html @@ -0,0 +1,55 @@ +

What it does

+ + +
+ +

Entry format

+
+- Character (Account#12345) [Threat%]
+  - In group: bucket. Destructive: bucket. Encounters: N. Last: <t:UNIX:R>
+
+ + +
+ +

When to use

+ + +
+ +

Behavior & notes

+ + +
+ +

Config that affects output

+ + +
+ +

Permissions

+ diff --git a/assets/docs/commands/PowerActionsCog.restart.brief.html b/assets/docs/commands/PowerActionsCog.restart.brief.html new file mode 100644 index 0000000..b6d1538 --- /dev/null +++ b/assets/docs/commands/PowerActionsCog.restart.brief.html @@ -0,0 +1,10 @@ +

Quick usage — /power restart [Moderator]

+

Safely restarts the bot after logging a clear reason to the modlog.

+ +
/power restart reason:"Restarting to enable new permissions sync after config change"
+ + diff --git a/assets/docs/commands/PowerActionsCog.restart.details.html b/assets/docs/commands/PowerActionsCog.restart.details.html new file mode 100644 index 0000000..70c4202 --- /dev/null +++ b/assets/docs/commands/PowerActionsCog.restart.details.html @@ -0,0 +1,61 @@ +

What it does

+ + +
+ +

Reason requirements

+

The reason must be specific enough to audit later:

+ + +

Examples

+
+
+
✅ Good
+
/power restart reason:"Reloading cogs after changing threat weights and enabling nick loop; avoids inconsistent state."
+
+
+
❌ Bad
+
/power restart reason:"update"
+
+
+ +
+ +

What you’ll see

+ + +
+ +

Config that affects it

+ + +
+ +

Permissions

+ diff --git a/assets/docs/commands/ReactionRoleCog.nick_same.brief.html b/assets/docs/commands/ReactionRoleCog.nick_same.brief.html new file mode 100644 index 0000000..66f5c4a --- /dev/null +++ b/assets/docs/commands/ReactionRoleCog.nick_same.brief.html @@ -0,0 +1,18 @@ +

/nick_same

+

Nick matches my in-game name. Tells mods your current server nickname (or global display name if you haven’t set one) already matches your in-game name, and opens a quick review.

+ +

Usage

+
/nick_same
+ + + +

Tips

+ diff --git a/assets/docs/commands/ReactionRoleCog.nick_same.details.html b/assets/docs/commands/ReactionRoleCog.nick_same.details.html new file mode 100644 index 0000000..b00bc9a --- /dev/null +++ b/assets/docs/commands/ReactionRoleCog.nick_same.details.html @@ -0,0 +1,38 @@ +

/nick_same — Nickname claim

+

Use this when your server nickname already matches your in-game character name. It opens a moderator review so you can get (or keep) access without typing anything else.

+ +

What it does

+ + +

How to use

+
/nick_same
+ + +

Typical flow

+
    +
  1. You run /nick_same. The bot replies “Thanks — your nickname claim was sent for moderator review.”
  2. +
  3. Mods see a review card with your previous nick (if known) and the current one.
  4. +
  5. They press ✅ to verify or ❌ to reject. You keep (or lose) Full Access depending on Rules/Engagement and the result.
  6. +
+ +

Good to know

+ + +

Troubleshooting

+ diff --git a/assets/docs/commands/SpicePayCog.spicepay.brief.html b/assets/docs/commands/SpicePayCog.spicepay.brief.html new file mode 100644 index 0000000..1bddaec --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay.brief.html @@ -0,0 +1,25 @@ +

/spicepay

+

Open the Spice Pay wizard. Step through participants, roles, payout type (Sand or Melange), and weighting. Shows a live preview and lets you post the result.

+ +

Usage

+
/spicepay [participants] [force_new]
+ + +

Quick flow

+
    +
  1. Enter the total yield and participant count.
  2. +
  3. Add/edit each participant (name, active %, owner roles).
  4. +
  5. Toggle payout: Sand ⟷ Melange (set refinery yield for Melange).
  6. +
  7. Adjust weighting factors or use a preset.
  8. +
  9. Finish → preview → Post to channel.
  10. +
+ +

Notes

+ \ No newline at end of file diff --git a/assets/docs/commands/SpicePayCog.spicepay.details.html b/assets/docs/commands/SpicePayCog.spicepay.details.html new file mode 100644 index 0000000..44741f8 --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay.details.html @@ -0,0 +1,72 @@ +

/spicepay — Guided payout

+

Calculate a fair split for a spice run. The wizard keeps things simple and transparent for the team.

+ +

Start

+
/spicepay [participants] [force_new]
+ + +

Setup modal

+ + +

Editing participants

+

For each slot:

+ +

Use Add / Edit participant, Previous/Next to navigate. The preview shows filled vs. empty slots and highlights “owner-only”.

+ +

Payout type

+ +

When switching to Melange, set the refinery yield (integer) in the modal.

+ +

Weighting (the math, simplified)

+ + +

Controls

+ + +

Validation & limits

+ + +

Posting

+

The post includes:

+ +

Footnote: “0% = owner only”.

diff --git a/assets/docs/commands/SpicePayCog.spicepay_cancel.brief.html b/assets/docs/commands/SpicePayCog.spicepay_cancel.brief.html new file mode 100644 index 0000000..a97a1d6 --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_cancel.brief.html @@ -0,0 +1,10 @@ +

/spicepay_cancel

+

Cancel your active Spice Pay session. Clears everything for you so you can start fresh.

+ +

Usage

+
/spicepay_cancel
+ + diff --git a/assets/docs/commands/SpicePayCog.spicepay_cancel.details.html b/assets/docs/commands/SpicePayCog.spicepay_cancel.details.html new file mode 100644 index 0000000..683a82a --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_cancel.details.html @@ -0,0 +1,11 @@ +

/spicepay_cancel — Cancel session

+

Discards your current wizard state. Handy if you mis-entered totals or want to restructure the roster.

+ +

What it does

+ + +

Next steps

+

Run /spicepay to start again. You can optionally pass participants or just set them in the first modal.

diff --git a/assets/docs/commands/SpicePayCog.spicepay_config.brief.html b/assets/docs/commands/SpicePayCog.spicepay_config.brief.html new file mode 100644 index 0000000..94afb1d --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_config.brief.html @@ -0,0 +1,10 @@ +

/spicepay_config

+

Show server SpicePay weights. Read-only view of the defaults the wizard uses.

+ +

Usage

+
/spicepay_config
+ + diff --git a/assets/docs/commands/SpicePayCog.spicepay_config.details.html b/assets/docs/commands/SpicePayCog.spicepay_config.details.html new file mode 100644 index 0000000..4f5bedd --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_config.details.html @@ -0,0 +1,19 @@ +

/spicepay_config — Server weights

+

Shows the current default weighting used by the Spice Pay wizard.

+ +

Fields

+ + +

Where to change

+

Configure via environment variables or your INI, then restart the bot. The wizard’s Adjust weighting factors lets users override per-run; those don’t change the server defaults.

+ +

Tips

+ diff --git a/assets/docs/commands/SpicePayCog.spicepay_resume.brief.html b/assets/docs/commands/SpicePayCog.spicepay_resume.brief.html new file mode 100644 index 0000000..88b8cc9 --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_resume.brief.html @@ -0,0 +1,10 @@ +

/spicepay_resume

+

Reopen your active Spice Pay session. Useful if you closed the wizard message by accident.

+ +

Usage

+
/spicepay_resume
+ + diff --git a/assets/docs/commands/SpicePayCog.spicepay_resume.details.html b/assets/docs/commands/SpicePayCog.spicepay_resume.details.html new file mode 100644 index 0000000..55ebc80 --- /dev/null +++ b/assets/docs/commands/SpicePayCog.spicepay_resume.details.html @@ -0,0 +1,14 @@ +

/spicepay_resume — Resume wizard

+

Brings back the current session UI (if you already started one with /spicepay).

+ +

When to use

+ + +

Behavior

+ diff --git a/assets/docs/commands/UserCardsCog.usercards_rescan.brief.html b/assets/docs/commands/UserCardsCog.usercards_rescan.brief.html new file mode 100644 index 0000000..52cf031 --- /dev/null +++ b/assets/docs/commands/UserCardsCog.usercards_rescan.brief.html @@ -0,0 +1,18 @@ +

/usercards_rescan

+

Re-check everyone and refresh the user cards. Also repairs Roles/RoE/nickname claims from the live reaction messages, and re-opens any missing nickname reviews.

+ +

Usage

+
/usercards_rescan
+ + + +

What it does

+ diff --git a/assets/docs/commands/UserCardsCog.usercards_rescan.details.html b/assets/docs/commands/UserCardsCog.usercards_rescan.details.html new file mode 100644 index 0000000..93c88f2 --- /dev/null +++ b/assets/docs/commands/UserCardsCog.usercards_rescan.details.html @@ -0,0 +1,47 @@ +

/usercards_rescan — Reconcile & refresh all cards

+

One-shot maintenance pass that makes the server’s user cards match reality.

+ +

Access

+ + +

What it fixes

+
    +
  1. Rules / RoE agreement + +
  2. +
  3. Nickname claim & reviews + +
  4. +
  5. User cards + +
  6. +
+ +

Expected output

+

The command replies (ephemeral) with counts like:

+
Reconciled from messages. Changes — Rules: 3, RoE: 2, Nickname (added): 1, Nickname (removed): 0. Refreshed cards for 154 members.
+ +

Setup notes

+ + +

Tips

+ diff --git a/assets/docs/commands/__commands__.json b/assets/docs/commands/__commands__.json index 9e26dfe..2dc3c08 100644 --- a/assets/docs/commands/__commands__.json +++ b/assets/docs/commands/__commands__.json @@ -1 +1,91 @@ -{} \ No newline at end of file +{ + "PirateReportCog.report": { + "details_md": "## What it does\nSubmit a player to the **Pirate Review Queue**. Mods get a compact card with the details and can approve ❇️ or reject ❌ with one click. If you include proof, it shows inline so they can decide faster.\n\n---\n\n## How to use — `/report`\nWhen you run **/report**, a small form opens:\n\n- **In-game nickname** \n The name they use in game. Example: `SandStalker`\n\n- **Account (Name#12345)** \n Must end with a `#` and **five digits**. Example: `SomeUser#12345` \n > If you only know the nickname, try to get the account tag too. It removes ambiguity later.\n\n- **Proof (Discord media URL — optional but strongly encouraged)** \n Paste a **direct Discord CDN** link so it previews nicely:\n - Accepts: `https://cdn.discordapp.com/...` or `https://media.discordapp.net/...`\n - Works with: **png, jpg, jpeg, gif, webp, mp4, webm, mov**\n - How to get the link: open the image/video in Discord ➜ “Open in browser” ➜ copy the address.\n\n> **Tip:** Images appear inside the mod card. Videos are kept as a plain link above the card so the inline video player works.\n\n---\n\n## What happens after you submit\n- You get a quick **thank-you** in the channel.\n- Mods receive an embed in their review channel with ✅ and ❌.\n- There’s a **“Jump to message”** button on the mod card so they can see the original context.\n- When a mod picks ✅/❌, the card is updated to show **Approved/Rejected** (with who decided and when). The small ack message in your channel is also edited to reflect the result.\n\nIf approved, the player is added to the pirate list (threat level starts at 0 and grows with encounters).\n\n---\n\n## Rules & limits (so you don’t run into errors)\n- **Account format:** must be `Name#12345`. Five digits. No spaces at the end.\n- **Proof link:** must be a Discord media link (see above). Non-Discord links are blocked.\n- **Duplicates:** already-approved players can’t be re-reported; exact pending duplicates are blocked too.\n- **Rate limit:** one report per user every **60 seconds**.\n\nCommon messages you might see:\n- `Invalid account format` → fix to `Name#12345`.\n- `URL must be a Discord media link` → use cdn.discordapp.com or media.discordapp.net.\n- `A report for this player is already pending` → a mod is already reviewing one.\n- `This player is already in the pirate list` → no need to report again.\n\n---\n\n## Good proof examples\n- Screenshot of **names** and actions in the **event log**.\n- Short clip (10–30s) that shows **what happened** and **who did it**.\n- If the situation is complex, add a quick text summary when you submit.\n\n---\n\n## Related actions\n- **/encounter** — log a new encounter with a known pirate. \n Fields:\n - *Pirate (name or account)* — `MuadDib` or `MuadDib#12345` (account preferred)\n - *Group size* — integer ≥ 1\n - *Kills* — integer ≥ 0 (0 = none/unknown)\n - *Destructive?* — `yes` / `no`\n - *Perceived Skill* — 0–5 (0 = unknown) \n Limits: one encounter per same pirate per reporter every **10 minutes**. These entries automatically update the pirate’s **threat level** and **encounter count**.\n\n- **/edit_pirate** *(moderators)* — update a pirate’s nickname/account.\n- **remove_pirate** *(moderators, hybrid)* — remove an approved entry.\n- **encounters_migrate_ids** *(moderators, hybrid)* — migrate old encounter identifiers.\n\n---\n\n## FAQ\n**Q: My proof link won’t accept.** \nA: It must be a direct Discord CDN URL (ends in a known image/video extension). Open the media in browser from Discord and copy that address.\n\n**Q: I only know their nickname and it says “ambiguous”.** \nA: Use the **account** form `Name#12345`. Nicknames can be shared.\n\n**Q: Do I have to add proof?** \nA: Optional, but it **really** helps mods decide quickly. Screenshots are usually enough.\n", + "brief_html": "

What it does

\n

\n Sends a player to the Pirate Review Queue. Mods get a card with ✅/❌.\n If you include a Discord media link, it will preview for them.\n

\n\n

Quick use — /report

\n\n\n

Limits

\n\n\n

\n Tip: Images show inside the card; videos stay as a link so the inline player works.\n

\n", + "details_html": "

Submit a pirate report

\n

\n Use /report to send a player to the Pirate Review Queue.\n Mods see a compact card with your info and can approve or reject with one click.\n

\n\n

Form fields

\n\n\n

What happens next

\n
    \n
  1. You get a short “thanks” message in the channel.
  2. \n
  3. Mods receive an embed with ✅/❌. If you added an image, it shows inside the card.\n If it’s a video, the URL is kept above the card so the inline player works.
  4. \n
  5. A “Jump to message” button lets mods see your original context quickly.
  6. \n
  7. When a mod decides, the card updates to show Approved/Rejected with who and when.\n Your small ack message in the channel is edited to reflect the result.
  8. \n
  9. If approved, the player is added to the pirate list (threat level starts at 0).
  10. \n
\n\n

Rules & common errors

\n\n

\n Typical messages: “Invalid account format”, “URL must be a Discord media link”, “A report for this player is already pending”.\n

\n\n

Related actions

\n\n" + }, + "PirateReportCog.encounter": { + "brief_html": "

Quick usage — /encounter

\n\n

Log a pirate encounter so threat scores stay fresh. Takes ~10 seconds.

\n\n\n\n

Rate limit: you can report the same pirate once every 10 minutes.

\n\n
\n
/encounter MuadDib#12345 · group 3 · kills 1 · destructive yes · skill 4
\n
/encounter SandStalker · group 5 · kills 0 · destructive no · skill 2
\n
\n\n

After you submit, you’ll get a private “Encounter recorded” message and the pirate list updates shortly after.

\n", + "details_html": "

What it does

\n

\n /encounter adds a single encounter to the pirate’s history. These entries feed the\n threat score so the pirate list reflects how dangerous someone actually is right now.\n

\n\n
\n\n

Form fields

\n\n\n
\n /encounter MuadDib#12345 · group 3 · kills 1 · destructive yes · skill 4\n
\n\n
\n\n

Validation & messages

\n\n\n
\n\n

How threat is calculated (short)

\n

\n Each encounter contributes to a weighted score (0–100):\n

\n\n

\n The weights are configurable (defaults: Kill 0.35, Destruction 0.30, Group 0.20, Skill 0.15). After you submit,\n the bot recomputes the pirate’s threat level and encounter count, then refreshes the list.\n

\n\n
\n\n

Tips

\n\n\n
\n\n

Common errors

\n\n" + }, + "PirateReportCog.edit_pirate": { + "brief_html": "

Quick usage — /edit_pirate [Moderator]

\n\n

Edit an existing pirate entry. Use this when a player changes nickname or account tag.

\n\n\n\n
/edit_pirate
\n\n

\n You must provide at least one of: new nickname or new account.
\n The account format is strict: # + five digits. Duplicate accounts are blocked.\n

\n", + "details_html": "

What it does

\n

\n Updates a pirate record that’s already approved. Typical cases: the player renamed their character,\n or moved to a new account tag.\n

\n\n
\n\n

Form fields

\n\n\n
\n\n

Rules & feedback

\n\n" + }, + "PirateReportCog.encounters_migrate_ids": { + "brief_html": "

Quick usage — /encounters_migrate_ids [Moderator]

\n\n

\n Converts old encounter records stored by character name to the\n canonical account (Name#12345).\n

\n\n
/encounters_migrate_ids
\n\n

\n Useful after adding pirates where past encounters referenced only nicknames.\n

\n", + "details_html": "

What it does

\n

\n Scans stored encounters and rewrites any identifier that is a character name to the correct\n account for that pirate. Keeps data consistent for threat calculations.\n

\n\n
\n\n

How matching works

\n\n\n
\n\n

Result

\n\n\n

\n Moderator-only.\n

\n" + }, + "PirateReportCog.remove_pirate": { + "brief_html": "

Quick usage — /remove_pirate [Moderator]

\n\n

Remove a pirate from the approved list by account.

\n\n
/remove_pirate account_name: Name#12345
\n\n

\n Account format must be Name#12345. If the account isn’t found, you’ll get “Pirate not found.”\n

\n", + "details_html": "

What it does

\n

Deletes the matching pirate record from the approved list.

\n\n
\n\n

Parameters

\n\n\n
\n\n

Behavior & messages

\n\n" + }, + "AutoVCCog.avc_cleanup_now": { + "brief_html": "

Quick usage — /avc_cleanup_now [Moderator]

\n

Immediately deletes empty Auto-VC rooms that have been idle past the configured delay, then renumbers the rest.

\n\n
/avc_cleanup_now
\n\n

\n Replies “Cleanup pass complete.” on success.\n

\n", + "details_html": "

What it does

\n\n\n
\n\n

When to use

\n\n\n
\n\n

Permissions & notes

\n\n" + }, + "AutoVCCog.avc_renumber": { + "brief_html": "

Quick usage — /avc_renumber [Moderator]

\n

Force a rename of all managed rooms to a clean numeric order without deleting anything.

\n\n
/avc_renumber
\n\n

\n Replies “Renumbered.” on success.\n

\n", + "details_html": "

What it does

\n

Renames tracked rooms to {prefix} 1, {prefix} 2, … in creation order. No rooms are deleted.

\n\n
\n\n

When to use

\n\n\n
\n\n

Permissions & notes

\n\n" + }, + "AutoVCCog.avc_status": { + "brief_html": "

Quick usage — /avc_status

\n

Shows current Auto-VC setup and the list of managed rooms.

\n\n
/avc_status
\n\n

\n Output includes trigger channel, target category, name prefix, cleanup delay, and each tracked room with its current state.\n

\n", + "details_html": "

What it shows

\n\n\n
\n\n

Example output

\n
\nAuto-VC status:\nTrigger: <#1234567890> | Category: <#2345678901> | Prefix: `Room` | Delay: 30s\n- #1: Room 1 — 3 inside\n- #2: Room 2 — empty | idle 12s\n
\n\n

\n This command is read-only and safe to run anytime.\n

\n" + }, + "NickNudgeCog.clear_nick_reviews": { + "brief_html": "

Quick usage — /clear_nick_reviews [Moderator]

\n

Delete all pending nickname review records for this server.

\n\n
/clear_nick_reviews
\n\n

\nReplies with how many pending entries were removed (ephemeral).\n

\n", + "details_html": "

What it does

\n\n\n
\n\n

Result

\n

\n Shows: Cleared N pending nickname review(s). (ephemeral)\n

\n\n
\n\n

Permissions & notes

\n\n" + }, + "NickNudgeCog.recreate_nick_review": { + "brief_html": "

Quick usage — /recreate_nick_review [Moderator]

\n

Recreate a single user’s missing pending review.

\n\n
/recreate_nick_review user: @Member
\n\n

\nIf they’re already verified or already have a pending review, you’ll get a short explanation instead (ephemeral).\n

\n", + "details_html": "

What it does

\n\n\n
\n\n

When to use

\n\n\n
\n\n

Permissions & notes

\n\n" + }, + "NickNudgeCog.recreate_nick_reviews": { + "brief_html": "

Quick usage — /recreate_nick_reviews [Moderator]

\n

Scan the server and recreate any missing pending reviews for users who claimed but never got a review opened.

\n\n
/recreate_nick_reviews
\n\n

\nReplies: Recreated X review(s); skipped Y. (ephemeral)\n

\n", + "details_html": "

What it does

\n\n\n
\n\n

When to use

\n\n\n
\n\n

Permissions & notes

\n\n" + }, + "PiratesListCog.pirates_list_refresh": { + "brief_html": "

Quick usage — /pirates_list_refresh [Moderator]

\n

Rebuild the compact pirates list in the configured channel. Use this after adding/removing pirates or when encounter stats change.

\n\n
/pirates_list_refresh
\n\n\n", + "details_html": "

What it does

\n\n\n
\n\n

Entry format

\n
\n- Character (Account#12345) [Threat%]\n  - In group: bucket. Destructive: bucket. Encounters: N. Last: <t:UNIX:R>\n
\n\n\n
\n\n

When to use

\n\n\n
\n\n

Behavior & notes

\n\n\n
\n\n

Config that affects output

\n\n\n
\n\n

Permissions

\n\n" + }, + "PowerActionsCog.power_restart": { + "brief_html": "

Quick usage — /power restart [Moderator]

\n

Safely restarts the bot after logging a clear reason to the modlog.

\n\n
/power restart reason:\"Restarting to enable new permissions sync after config change\"
\n\n\n", + "details_html": "

What it does

\n\n\n
\n\n

Reason requirements

\n

The reason must be specific enough to audit later:

\n\n\n

Examples

\n
\n
\n
✅ Good
\n
/power restart reason:\"Reloading cogs after changing threat weights and enabling nick loop; avoids inconsistent state.\"
\n
\n
\n
❌ Bad
\n
/power restart reason:\"update\"
\n
\n
\n\n
\n\n

What you’ll see

\n\n\n
\n\n

Config that affects it

\n\n\n
\n\n

Permissions

\n\n" + }, + "PowerActionsCog.restart": { + "brief_html": "

Quick usage — /power restart [Moderator]

\n

Safely restarts the bot after logging a clear reason to the modlog.

\n\n
/power restart reason:\"Restarting to enable new permissions sync after config change\"
\n\n\n", + "details_html": "

What it does

\n\n\n
\n\n

Reason requirements

\n

The reason must be specific enough to audit later:

\n\n\n

Examples

\n
\n
\n
✅ Good
\n
/power restart reason:\"Reloading cogs after changing threat weights and enabling nick loop; avoids inconsistent state.\"
\n
\n
\n
❌ Bad
\n
/power restart reason:\"update\"
\n
\n
\n\n
\n\n

What you’ll see

\n\n\n
\n\n

Config that affects it

\n\n\n
\n\n

Permissions

\n\n" + }, + "ReactionRoleCog.nick_same": { + "brief_html": "

/nick_same

\n

Nick matches my in-game name. Tells mods your current server nickname (or global display name if you haven’t set one) already matches your in-game name, and opens a quick review.

\n\n

Usage

\n
/nick_same
\n\n\n\n

Tips

\n\n", + "details_html": "

/nick_same — Nickname claim

\n

Use this when your server nickname already matches your in-game character name. It opens a moderator review so you can get (or keep) access without typing anything else.

\n\n

What it does

\n\n\n

How to use

\n
/nick_same
\n\n\n

Typical flow

\n
    \n
  1. You run /nick_same. The bot replies “Thanks — your nickname claim was sent for moderator review.”
  2. \n
  3. Mods see a review card with your previous nick (if known) and the current one.
  4. \n
  5. They press ✅ to verify or ❌ to reject. You keep (or lose) Full Access depending on Rules/Engagement and the result.
  6. \n
\n\n

Good to know

\n\n\n

Troubleshooting

\n\n" + }, + "SpicePayCog.spicepay": { + "brief_html": "

/spicepay

\n

Open the Spice Pay wizard. Step through participants, roles, payout type (Sand or Melange), and weighting. Shows a live preview and lets you post the result.

\n\n

Usage

\n
/spicepay [participants] [force_new]
\n\n\n

Quick flow

\n
    \n
  1. Enter the total yield and participant count.
  2. \n
  3. Add/edit each participant (name, active %, owner roles).
  4. \n
  5. Toggle payout: Sand ⟷ Melange (set refinery yield for Melange).
  6. \n
  7. Adjust weighting factors or use a preset.
  8. \n
  9. Finish → preview → Post to channel.
  10. \n
\n\n

Notes

\n", + "details_html": "

/spicepay — Guided payout

\n

Calculate a fair split for a spice run. The wizard keeps things simple and transparent for the team.

\n\n

Start

\n
/spicepay [participants] [force_new]
\n\n\n

Setup modal

\n\n\n

Editing participants

\n

For each slot:

\n\n

Use Add / Edit participant, Previous/Next to navigate. The preview shows filled vs. empty slots and highlights “owner-only”.

\n\n

Payout type

\n\n

When switching to Melange, set the refinery yield (integer) in the modal.

\n\n

Weighting (the math, simplified)

\n\n\n

Controls

\n\n\n

Validation & limits

\n\n\n

Posting

\n

The post includes:

\n\n

Footnote: “0% = owner only”.

\n" + }, + "SpicePayCog.spicepay_cancel": { + "brief_html": "

/spicepay_cancel

\n

Cancel your active Spice Pay session. Clears everything for you so you can start fresh.

\n\n

Usage

\n
/spicepay_cancel
\n\n\n", + "details_html": "

/spicepay_cancel — Cancel session

\n

Discards your current wizard state. Handy if you mis-entered totals or want to restructure the roster.

\n\n

What it does

\n\n\n

Next steps

\n

Run /spicepay to start again. You can optionally pass participants or just set them in the first modal.

\n" + }, + "SpicePayCog.spicepay_config": { + "brief_html": "

/spicepay_config

\n

Show server SpicePay weights. Read-only view of the defaults the wizard uses.

\n\n

Usage

\n
/spicepay_config
\n\n\n", + "details_html": "

/spicepay_config — Server weights

\n

Shows the current default weighting used by the Spice Pay wizard.

\n\n

Fields

\n\n\n

Where to change

\n

Configure via environment variables or your INI, then restart the bot. The wizard’s Adjust weighting factors lets users override per-run; those don’t change the server defaults.

\n\n

Tips

\n\n" + }, + "SpicePayCog.spicepay_resume": { + "brief_html": "

/spicepay_resume

\n

Reopen your active Spice Pay session. Useful if you closed the wizard message by accident.

\n\n

Usage

\n
/spicepay_resume
\n\n\n", + "details_html": "

/spicepay_resume — Resume wizard

\n

Brings back the current session UI (if you already started one with /spicepay).

\n\n

When to use

\n\n\n

Behavior

\n\n" + }, + "help": { + "brief_html": "

!help

\n

Show a quick overview of commands or details about a specific command.

\n\n

Usage

\n
!help\n!help spicepay\n!help pirate\n!help encounter
\n\n\n", + "details_html": "

!help — Command help & search

\n

Use !help to see what the bot can do, or pass a keyword/command to jump straight to specifics.

\n\n

What it does

\n\n\n

How to use it

\n
    \n
  1. Overview: !help — shows a concise command list.
  2. \n
  3. Filter: !help spice — narrows to commands that match “spice”.
  4. \n
  5. Exact command: !help encounter — shows usage for a single command if available.
  6. \n
\n\n

Examples

\n
!help\n!help pirate\n!help spicepay\n!help encounter
\n\n

Notes & tips

\n\n\n

Troubleshooting

\n\n" + }, + "PirateCardsCog.pirate_cards_rebuild": { + "brief_html": "

/pirate_cards_rebuild

\n

Rebuild or update pirate cards for every known pirate in the configured channel.

\n\n

Usage

\n
/pirate_cards_rebuild
\n\n\n\n

What it does

\n\n", + "details_html": "

/pirate_cards_rebuild — Rebuild all pirate cards

\n

Runs a full pass that makes sure each verified pirate has a fresh, accurate card in the configured channel.

\n\n

Access

\n\n\n

What it updates

\n\n\n

Behavior

\n
    \n
  1. For each pirate in the internal list:\n \n
  2. \n
  3. Works serially per guild to avoid race conditions.
  4. \n
\n\n

Output

\n

Ephemeral summary, e.g.:

\n
Rebuilt/updated 27 pirate cards.
\n\n

Setup notes

\n\n\n

When to run it

\n\n" + }, + "UserCardsCog.usercards_rescan": { + "brief_html": "

/usercards_rescan

\n

Re-check everyone and refresh the user cards. Also repairs Roles/RoE/nickname claims from the live reaction messages, and re-opens any missing nickname reviews.

\n\n

Usage

\n
/usercards_rescan
\n\n\n\n

What it does

\n\n", + "details_html": "

/usercards_rescan — Reconcile & refresh all cards

\n

One-shot maintenance pass that makes the server’s user cards match reality.

\n\n

Access

\n\n\n

What it fixes

\n
    \n
  1. Rules / RoE agreement\n \n
  2. \n
  3. Nickname claim & reviews\n \n
  4. \n
  5. User cards\n \n
  6. \n
\n\n

Expected output

\n

The command replies (ephemeral) with counts like:

\n
Reconciled from messages. Changes — Rules: 3, RoE: 2, Nickname (added): 1, Nickname (removed): 0. Refreshed cards for 154 members.
\n\n

Setup notes

\n\n\n

Tips

\n\n" + } +} \ No newline at end of file diff --git a/assets/docs/commands/help.brief.html b/assets/docs/commands/help.brief.html new file mode 100644 index 0000000..29536ff --- /dev/null +++ b/assets/docs/commands/help.brief.html @@ -0,0 +1,12 @@ +

!help

+

Show a quick overview of commands or details about a specific command.

+ +

Usage

+
!help
+!help spicepay
+!help pirate
+!help encounter
+ + diff --git a/assets/docs/commands/help.details.html b/assets/docs/commands/help.details.html new file mode 100644 index 0000000..6fe30a2 --- /dev/null +++ b/assets/docs/commands/help.details.html @@ -0,0 +1,32 @@ +

!help — Command help & search

+

Use !help to see what the bot can do, or pass a keyword/command to jump straight to specifics.

+ +

What it does

+ + +

How to use it

+
    +
  1. Overview: !help — shows a concise command list.
  2. +
  3. Filter: !help spice — narrows to commands that match “spice”.
  4. +
  5. Exact command: !help encounter — shows usage for a single command if available.
  6. +
+ +

Examples

+
!help
+!help pirate
+!help spicepay
+!help encounter
+ +

Notes & tips

+ + +

Troubleshooting

+ diff --git a/assets/docs/favicon-16x16.png b/assets/docs/favicon-16x16.png new file mode 100644 index 0000000..c04d2a4 Binary files /dev/null and b/assets/docs/favicon-16x16.png differ diff --git a/assets/docs/favicon-32x32.png b/assets/docs/favicon-32x32.png new file mode 100644 index 0000000..419db6e Binary files /dev/null and b/assets/docs/favicon-32x32.png differ diff --git a/assets/docs/favicon.ico b/assets/docs/favicon.ico new file mode 100644 index 0000000..74206b0 Binary files /dev/null and b/assets/docs/favicon.ico differ diff --git a/assets/docs/site.webmanifest b/assets/docs/site.webmanifest new file mode 100644 index 0000000..85c602e --- /dev/null +++ b/assets/docs/site.webmanifest @@ -0,0 +1,12 @@ +{ + "name": "ShaiWatcher", + "short_name": "ShaiWatcher", + "icons": [ + { "src": "/assets/docs/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/assets/docs/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } + ], + "theme_color": "#0f172a", + "background_color": "#0b0f14", + "display": "standalone", + "start_url": "/" +} diff --git a/bot.py b/bot.py index efab1b3..8117f58 100644 --- a/bot.py +++ b/bot.py @@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice # Version consists of: # Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update) -VERSION = "0.4.0.0.a5" +VERSION = "0.4.1.0.a1" # ---------- Env loading ---------- diff --git a/dev/offline_preview.py b/dev/offline_preview.py index 780fb47..1aa0165 100644 --- a/dev/offline_preview.py +++ b/dev/offline_preview.py @@ -6,7 +6,7 @@ Run from anywhere: # optional (forces root if your layout is odd) # export SHAI_PROJECT_ROOT=/absolute/path/to/shaiwatcher # export SHAI_DOCS_HOST=127.0.0.1 - # export SHAI_DOCS_PORT=8911 + # export SHAI_DOCS_PORT=8910 # export SHAI_OFFLINE=1 python3 offline_preview.py """ @@ -91,7 +91,7 @@ async def main(): os.environ.setdefault("SHAI_OFFLINE", "1") # Bind docs to localhost by default while testing os.environ.setdefault("SHAI_DOCS_HOST", "127.0.0.1") - os.environ.setdefault("SHAI_DOCS_PORT", "8911") + os.environ.setdefault("SHAI_DOCS_PORT", "8910") os.environ.setdefault("SHAI_DOCS_TITLE", "ShaiWatcher (Offline Preview)") # Optional: isolate data file so we don't touch prod paths @@ -137,7 +137,7 @@ async def main(): pass print("[OFFLINE] Docs: http://%s:%s/" - % (os.environ.get("SHAI_DOCS_HOST", "127.0.0.1"), + % (os.environ.get("SHAI_DOCS_HOST", "0.0.0.0"), os.environ.get("SHAI_DOCS_PORT", "8911"))) print("[OFFLINE] This runner does NOT connect to Discord.") diff --git a/modules/docs_site/docs_site.py b/modules/docs_site/docs_site.py index f989d9a..90d0340 100644 --- a/modules/docs_site/docs_site.py +++ b/modules/docs_site/docs_site.py @@ -1,4 +1,3 @@ -# modules/docs_site/docs_site.py import json import threading import traceback @@ -228,13 +227,11 @@ def _command_usage_slash(cmd: app_commands.Command) -> str: def _iter_all_app_commands(bot: commands.Bot): """Yield (scope_tag, top_level_command) including global and per-guild trees.""" out = [] - # Global try: for cmd in bot.tree.get_commands(): out.append(("", cmd)) except Exception: pass - # Per-guild for g in list(getattr(bot, "guilds", []) or []): try: cmds = bot.tree.get_commands(guild=g) @@ -394,18 +391,52 @@ def _row_key_candidates(row: Dict[str, Any]) -> List[str]: keys.append(base) return keys -def _scan_md_files() -> Dict[str, Dict[str, Any]]: +def _scan_doc_files() -> Dict[str, Dict[str, Any]]: + """ + Scans assets/docs/commands for: + - .md -> details_md + - .html -> details_html + - .brief.html -> brief_html + - .details.html -> details_html (wins over .html) + """ out: Dict[str, Dict[str, Any]] = {} if not _DETAILS_DIR.is_dir(): return out + + def _put(key: str, field: str, text: str): + if key not in out: + out[key] = {} + out[key][field] = text + + # .md legacy for p in _DETAILS_DIR.glob("*.md"): if p.name.startswith("_"): continue try: - out[p.stem] = {"details_md": p.read_text(encoding="utf-8")} + _put(p.stem, "details_md", p.read_text(encoding="utf-8")) + except Exception: + traceback.print_exc() + + # .html brief/details + for p in _DETAILS_DIR.glob("*.html"): + if p.name.startswith("_"): + continue + name = p.stem + try: + txt = p.read_text(encoding="utf-8") except Exception: traceback.print_exc() continue + + if name.endswith(".brief"): + key = name[:-6] + _put(key, "brief_html", txt) + elif name.endswith(".details"): + key = name[:-8] + _put(key, "details_html", txt) + else: + _put(name, "details_html", txt) + return out def _load_master_json() -> Dict[str, Dict[str, Any]]: @@ -454,13 +485,14 @@ def _load_external_docs() -> Dict[str, Dict[str, Any]]: return _DOCS_CACHE["map"] master = _load_master_json() - md_map = _scan_md_files() + doc_map = _scan_doc_files() merged: Dict[str, Dict[str, Any]] = {k: dict(v) for k, v in master.items()} - for k, v in md_map.items(): + for k, v in doc_map.items(): if k not in merged: merged[k] = {} - merged[k]["details_md"] = v.get("details_md", "") + for kk, vv in v.items(): + merged[k][kk] = vv _write_master_json(merged) _DOCS_CACHE["map"] = merged @@ -474,16 +506,21 @@ def _augment_with_external_docs(rows: List[Dict[str, Any]]) -> None: dm = r["extras"].get("details_md") if isinstance(dm, str) and dm.strip(): r["details_md"] = dm - if not r.get("details_md"): + if not r.get("details_md") or not r.get("brief_html") or not r.get("details_html"): for key in _row_key_candidates(r): if key in mapping and isinstance(mapping[key], dict): - dm = mapping[key].get("details_md") - if isinstance(dm, str) and dm.strip(): - r["details_md"] = dm - meta = {kk: vv for kk, vv in mapping[key].items() if kk != "details_md"} + m = mapping[key] + if not r.get("details_md") and isinstance(m.get("details_md"), str): + r["details_md"] = m["details_md"] + if not r.get("brief_html") and isinstance(m.get("brief_html"), str): + r["brief_html"] = m["brief_html"] + if not r.get("details_html") and isinstance(m.get("details_html"), str): + r["details_html"] = m["details_html"] + if not r.get("doc_meta"): + meta = {kk: vv for kk, vv in m.items() if kk not in {"details_md","brief_html","details_html"}} if meta: r["doc_meta"] = meta - break + break # ============================= @@ -521,6 +558,10 @@ def _merge_hybrid_slash(rows: List[Dict[str, Any]]) -> None: h["extras"] = r["extras"] if r.get("details_md") and not h.get("details_md"): h["details_md"] = r["details_md"] + if r.get("brief_html") and not h.get("brief_html"): + h["brief_html"] = r["brief_html"] + if r.get("details_html") and not h.get("details_html"): + h["details_html"] = r["details_html"] to_remove.append(i) for i in sorted(to_remove, reverse=True): @@ -536,7 +577,7 @@ def build_command_schema(bot: commands.Bot) -> Dict[str, Any]: sl = _gather_slash(bot) all_rows = px + sl - # Additional moderator checks & sanitization + # Mark mod-only via hints/perms/extras for row in all_rows: try: helptext = f"{row.get('help') or ''} {row.get('brief') or ''}" @@ -556,20 +597,24 @@ def build_command_schema(bot: commands.Bot) -> Dict[str, Any]: _augment_with_external_docs(all_rows) _merge_hybrid_slash(all_rows) + # ✅ NEW: sort alphabetically by *command name* (display_name/name), not by cog + def _sort_key(r: Dict[str, Any]) -> str: + s = (r.get("display_name") or r.get("name") or "") + # strip leading slash and normalize + return s.lstrip("/").strip().lower() + + all_rows.sort(key=_sort_key) + mods = [r for r in all_rows if r.get("moderator_only")] users = [r for r in all_rows if not r.get("moderator_only")] return { "title": "ShaiWatcher Commands", "count": len(all_rows), - "sections": { - "user": users, - "moderator": mods, - }, + "sections": {"user": users, "moderator": mods}, "all": all_rows, } - # ============================= # Static asset serving # ============================= @@ -591,6 +636,8 @@ def _guess_mime(p: Path) -> str: ".json": "application/json; charset=utf-8", ".md": "text/markdown; charset=utf-8", ".txt": "text/plain; charset=utf-8", + ".ico": "image/x-icon", + ".html": "text/html; charset=utf-8", }.get(ext, "application/octet-stream") @@ -598,508 +645,12 @@ def _guess_mime(p: Path) -> str: # HTTP + UI # ============================= -_HTML = """ - - - -__TITLE__ - - - -

__TITLE__

-
- -
- -
- Sections: User · Moderator · All - - -
-
- - -
- -
- -
-
-
- - -
-
-
-
- - - - - - -
-
- - - -""" - class _DocsHandler(BaseHTTPRequestHandler): bot: commands.Bot = None title: str = "ShaiWatcher Commands" force_ready: bool = False + support_url: Optional[str] = None + support_label: Optional[str] = None def _set(self, code=200, content_type="text/html; charset=utf-8"): self.send_response(code) @@ -1110,6 +661,15 @@ class _DocsHandler(BaseHTTPRequestHandler): def log_message(self, fmt, *args): return + def _serve_file(self, fs_path: Path, cache: str = "public, max-age=86400, immutable"): + mime = _guess_mime(fs_path) + self.send_response(200) + self.send_header("Content-Type", mime) + self.send_header("Cache-Control", cache) + self.end_headers() + with fs_path.open("rb") as f: + self.wfile.write(f.read()) + def do_GET(self): try: ready = bool(self.force_ready) or (getattr(self, "bot", None) and self.bot.is_ready()) @@ -1120,7 +680,25 @@ class _DocsHandler(BaseHTTPRequestHandler): path = urlparse(self.path).path - # Serve static assets from /assets/docs/* + # Root favicon / PWA convenience routes + if path in ("/favicon.ico", "/favicon-16x16.png", "/favicon-32x32.png", + "/apple-touch-icon.png", "/site.webmanifest"): + root = _static_root().resolve() + name = path.lstrip("/") + fs_path = (root / name).resolve() + try: + if not fs_path.is_relative_to(root): + raise ValueError("outside root") + except AttributeError: + if str(root) not in str(fs_path): + raise ValueError("outside root") + if fs_path.is_file(): + self._serve_file(fs_path) + else: + self._set(404, "text/plain; charset=utf-8"); self.wfile.write(b"not found") + return + + # Static assets under /assets/docs/... if path.startswith("/assets/docs/"): try: root = _static_root().resolve() @@ -1133,55 +711,44 @@ class _DocsHandler(BaseHTTPRequestHandler): if str(root) not in str(fs_path): raise ValueError("outside root") if fs_path.is_file(): - mime = _guess_mime(fs_path) - self.send_response(200) - self.send_header("Content-Type", mime) - self.send_header("Cache-Control", "public, max-age=86400, immutable") - self.end_headers() - with fs_path.open("rb") as f: - self.wfile.write(f.read()) + self._serve_file(fs_path) return - self._set(404, "text/plain; charset=utf-8") - self.wfile.write(b"not found") + self._set(404, "text/plain; charset=utf-8"); self.wfile.write(b"not found") return + except Exception: + traceback.print_exc() + self._set(500, "text/plain; charset=utf-8"); self.wfile.write(b"internal error") + return + + if path == "/": + # Load template file + tpl_path = _static_root() / "cmd.html" + if tpl_path.is_file(): + html = tpl_path.read_text(encoding="utf-8") + else: + # Fallback minimal page if template missing + html = "Docs
Missing assets/docs/cmd.html
" + + # Inject data + support placeholders + try: + schema = build_command_schema(self.bot) + inline = json.dumps(_to_primitive(schema), ensure_ascii=False, separators=(",", ":")) + # Insert __DATA__ just before + inj = f"" + html = html.replace("", inj + "") + support_url = getattr(_DocsHandler, "support_url", "") or "" + support_label = getattr(_DocsHandler, "support_label", "Buy me a ☕") + vis = "block" if support_url else "none" + html = (html.replace("__TITLE__", self.title) + .replace("__SUPPORT_URL__", support_url) + .replace("__SUPPORT_LABEL__", support_label) + .replace("__SUPPORT_VIS__", vis)) + self._set() + self.wfile.write(html.encode("utf-8")) except Exception: traceback.print_exc() self._set(500, "text/plain; charset=utf-8") self.wfile.write(b"internal error") - return - - if path == "/": - self._set() - html = _HTML.replace("__TITLE__", self.title) - try: - schema = build_command_schema(self.bot) - inline = json.dumps(_to_primitive(schema), ensure_ascii=False, separators=(",", ":")) - html = html.replace("", f"") - - # Inject support link + visibility - support_url = getattr(_DocsHandler, "support_url", "") or "" - support_label = getattr(_DocsHandler, "support_label", "Buy me a ☕") - vis = "block" if support_url else "none" - - html = (html - .replace("__SUPPORT_URL__", support_url) - .replace("__SUPPORT_LABEL__", support_label) - .replace("__SUPPORT_VIS__", vis) - ) - - # Fallback to values stored on the cog (we’ll wire them below in _start_server) - support_url = support_url or getattr(_DocsHandler, "support_url", "") - support_label = support_label or getattr(_DocsHandler, "support_label", "Buy me a ☕") - vis = "block" if support_url else "none" - - html = (html - .replace("__SUPPORT_URL__", support_url) - .replace("__SUPPORT_LABEL__", support_label) - .replace("__SUPPORT_VIS__", vis) - ) - except Exception: - traceback.print_exc() - self.wfile.write(html.encode("utf-8")) return if path == "/api/status": @@ -1191,9 +758,7 @@ class _DocsHandler(BaseHTTPRequestHandler): return if path == "/healthz": - self._set(200, "text/plain; charset=utf-8") - self.wfile.write(b"ok") - return + self._set(200, "text/plain; charset=utf-8"); self.wfile.write(b"ok"); return if path == "/api/commands": schema = build_command_schema(self.bot) @@ -1202,13 +767,11 @@ class _DocsHandler(BaseHTTPRequestHandler): self.wfile.write(payload) return - self._set(404, "text/plain; charset=utf-8") - self.wfile.write(b"not found") + self._set(404, "text/plain; charset=utf-8"); self.wfile.write(b"not found") except Exception: traceback.print_exc() try: - self._set(500, "text/plain; charset=utf-8") - self.wfile.write(b"internal error") + self._set(500, "text/plain; charset=utf-8"); self.wfile.write(b"internal error") except Exception: pass @@ -1245,7 +808,7 @@ class DocsSite(commands.Cog): self.support_url = r.get("docs_support_url", "https://throne.com/ookamikuntv/item/39590391-c582-4c5d-8795-fe6f1925eaae") self.support_label = r.get("docs_support_label", "Buy me a ☕") - # Expose to handler via bot (read in _start_server) + # Expose to handler self.bot.docs_support_url = self.support_url self.bot.docs_support_label = self.support_label