refactor update messageId system / Discord.Interaction refactors
This commit is contained in:
parent
be84fa2fb6
commit
666986afb1
11 changed files with 336 additions and 120 deletions
|
|
@ -1,23 +1,46 @@
|
|||
{
|
||||
"version": "v0.1",
|
||||
"date": "2026-05-01",
|
||||
"date": "2026-05-28",
|
||||
"title": "Core Poll System",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "Poll creation with `/tg poll start` — opens voting for the upcoming TG", "emojiKey":null },
|
||||
{ "text": "Poll scheduling with cronjobs — opens voting for the upcoming TGs at specific times", "emojiKey":null },
|
||||
{ "text": "Yes/No voting with character display — class emoji, level and name", "emojiKey": "wi" },
|
||||
{ "text": "Nation-separated fields — Capella and Procyon listed independently", "emojiKey": "capella" },
|
||||
{ "text": "Public messages per player — leave a note when voting", "emojiKey": null },
|
||||
{ "text": "Poll lock and confirm system — lock at TG start, confirm yes/no after", "emojiKey": null },
|
||||
{ "text": "W.Rank display — rank number with gold variant for 7 TGs done", "emojiKey": "wrank_1_gold" },
|
||||
{ "text": "Bringer display — Storm Bringer and Luminous Bringer indicators", "emojiKey": "storm_bringer" }
|
||||
{
|
||||
"text": "Poll creation with `/tg poll start` — opens voting for the upcoming TG",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Poll scheduling with cronjobs — opens voting for the upcoming TGs at specific times",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Yes/No voting with character display — class emoji, level and name",
|
||||
"emojiKey": "wi"
|
||||
},
|
||||
{
|
||||
"text": "Nation-separated fields — Capella and Procyon listed independently",
|
||||
"emojiKey": "capella"
|
||||
},
|
||||
{
|
||||
"text": "Public messages per player — leave a note when voting",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Poll lock and confirm system — lock at TG start, confirm yes/no after",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "W.Rank display — rank number with gold variant for 7 TGs done",
|
||||
"emojiKey": "wrank_1_gold"
|
||||
},
|
||||
{
|
||||
"text": "Bringer display — Storm Bringer and Luminous Bringer indicators",
|
||||
"emojiKey": "storm_bringer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,22 +1,42 @@
|
|||
{
|
||||
"version": "v0.2",
|
||||
"date": "2026-05-10",
|
||||
"date": "2026-05-30",
|
||||
"title": "Character System & Impersonation",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "Character management — `/tg char add/remove/set-active/set-nation`", "emojiKey": "active_char" },
|
||||
{ "text": "Character sharing — lend your character to another player with `/tg char share`", "emojiKey": "borrowed" },
|
||||
{ "text": "Character borrowing — request to play someone else's character", "emojiKey": "borrowed" },
|
||||
{ "text": "Session borrows and persistent preferences across restarts", "emojiKey": "active_char" },
|
||||
{ "text": "`/tg switch` — change your active character at any time", "emojiKey": "active_char" },
|
||||
{ "text": "Impersonation system — officers can vote on behalf of players", "emojiKey": null },
|
||||
{ "text": "Autocomplete for character names with nation emoji and shared indicator 🔗", "emojiKey": null }
|
||||
{
|
||||
"text": "Character management — `/tg char add/remove/set-active/set-nation`",
|
||||
"emojiKey": "active_char"
|
||||
},
|
||||
{
|
||||
"text": "Character sharing — lend your character to another player with `/tg char share`",
|
||||
"emojiKey": "borrowed"
|
||||
},
|
||||
{
|
||||
"text": "Character borrowing — request to play someone else's character",
|
||||
"emojiKey": "borrowed"
|
||||
},
|
||||
{
|
||||
"text": "Session borrows and persistent preferences across restarts",
|
||||
"emojiKey": "active_char"
|
||||
},
|
||||
{
|
||||
"text": "`/tg switch` — change your active character at any time",
|
||||
"emojiKey": "active_char"
|
||||
},
|
||||
{
|
||||
"text": "Impersonation system — officers can vote on behalf of players",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Autocomplete for character names with nation emoji and shared indicator 🔗",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -24,9 +44,18 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "Character data stored per user in `characters.json`", "emojiKey": null },
|
||||
{ "text": "Borrow requests tracked with expiry timestamps", "emojiKey": null },
|
||||
{ "text": "ID-first usermap lookup — survives Discord username changes", "emojiKey": null }
|
||||
{
|
||||
"text": "Character data stored per user in `characters.json`",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Borrow requests tracked with expiry timestamps",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "ID-first usermap lookup — survives Discord username changes",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,19 +1,30 @@
|
|||
{
|
||||
"version": "v0.3",
|
||||
"date": "2026-05-18",
|
||||
"date": "2026-06-01",
|
||||
"title": "Conflict Resolution",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "Character conflict detection — warns when two players want the same character", "emojiKey": null },
|
||||
{ "text": "Conflict embed with Reclaim and Switch buttons", "emojiKey": null },
|
||||
{ "text": "Reclaim notifies the borrower via DM with a character selection prompt", "emojiKey": null },
|
||||
{ "text": "Auto-vote on conflict switch — voting Yes automatically after switching", "emojiKey": "active_char" }
|
||||
{
|
||||
"text": "Character conflict detection — warns when two players want the same character",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Conflict embed with Reclaim and Switch buttons",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Reclaim notifies the borrower via DM with a character selection prompt",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Auto-vote on conflict switch — voting Yes automatically after switching",
|
||||
"emojiKey": "active_char"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -21,8 +32,14 @@
|
|||
"label": "Bug Fixes",
|
||||
"emoji": "🔧",
|
||||
"items": [
|
||||
{ "text": "Score overwrite fixed for shared characters", "emojiKey": null },
|
||||
{ "text": "Bringer display corrected after weekly reset", "emojiKey": null }
|
||||
{
|
||||
"text": "Score overwrite fixed for shared characters",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Bringer display corrected after weekly reset",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -30,8 +47,14 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "Conflict resolution state machine with proper cleanup", "emojiKey": null },
|
||||
{ "text": "Reclaim flow uses interaction tokens for ephemeral editing", "emojiKey": null }
|
||||
{
|
||||
"text": "Conflict resolution state machine with proper cleanup",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Reclaim flow uses interaction tokens for ephemeral editing",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
{
|
||||
"version": "v0.4",
|
||||
"date": "2026-05-25",
|
||||
"date": "2026-06-03",
|
||||
"title": "Companion System",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "Companion ephemeral — after voting Yes, shows your active character with switch buttons", "emojiKey": "active_char" },
|
||||
{ "text": "Character switch buttons update the poll embed in real time", "emojiKey": "active_char" },
|
||||
{ "text": "Switching to a taken character triggers the conflict resolution flow", "emojiKey": null }
|
||||
{
|
||||
"text": "Companion ephemeral — after voting Yes, shows your active character with switch buttons",
|
||||
"emojiKey": "active_char"
|
||||
},
|
||||
{
|
||||
"text": "Character switch buttons update the poll embed in real time",
|
||||
"emojiKey": "active_char"
|
||||
},
|
||||
{
|
||||
"text": "Switching to a taken character triggers the conflict resolution flow",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -20,8 +28,14 @@
|
|||
"label": "Improvements",
|
||||
"emoji": "⚡",
|
||||
"items": [
|
||||
{ "text": "Interaction lock — prevents double-click issues on all buttons", "emojiKey": null },
|
||||
{ "text": "Companion ephemeral updates in place instead of spawning a new message", "emojiKey": null }
|
||||
{
|
||||
"text": "Interaction lock — prevents double-click issues on all buttons",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Companion ephemeral updates in place instead of spawning a new message",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -29,8 +43,14 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "`EphemeralRegistry` — edit ephemeral messages via interaction token within 15 minute window", "emojiKey": null },
|
||||
{ "text": "`InteractionLock` — prevents duplicate interaction processing", "emojiKey": null }
|
||||
{
|
||||
"text": "`EphemeralRegistry` — edit ephemeral messages via interaction token within 15 minute window",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`InteractionLock` — prevents duplicate interaction processing",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,19 +1,30 @@
|
|||
{
|
||||
"version": "v0.5",
|
||||
"date": "2026-06-01",
|
||||
"date": "2026-06-05",
|
||||
"title": "W.Rank Improvements",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "W.Rank delta system — tracks rank movement with <:wrank_up:1512114414474756132><:wrank_down:1511906547104616643> and grey placeholder for unchanged", "emojiKey": "wrank_up" },
|
||||
{ "text": "Midnight snapshot — after 24 hours of no rank change, delta resets to ( - 0 )", "emojiKey": "wrank_1" },
|
||||
{ "text": "Weekly reset carries Bringer forward — W.Rank 1 with goal TGs becomes next week's Bringer", "emojiKey": "storm_bringer" },
|
||||
{ "text": "No-rank placeholder alignment — players without W.Rank align with those who have it", "emojiKey": "wrank_no_dash" }
|
||||
{
|
||||
"text": "W.Rank delta system — tracks rank movement with <:wrank_up:1512114414474756132><:wrank_down:1511906547104616643> and grey placeholder for unchanged",
|
||||
"emojiKey": "wrank_up"
|
||||
},
|
||||
{
|
||||
"text": "Midnight snapshot — after 24 hours of no rank change, delta resets to ( - 0 )",
|
||||
"emojiKey": "wrank_1"
|
||||
},
|
||||
{
|
||||
"text": "Weekly reset carries Bringer forward — W.Rank 1 with goal TGs becomes next week's Bringer",
|
||||
"emojiKey": "storm_bringer"
|
||||
},
|
||||
{
|
||||
"text": "No-rank placeholder alignment — players without W.Rank align with those who have it",
|
||||
"emojiKey": "wrank_no_dash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -21,8 +32,14 @@
|
|||
"label": "Improvements",
|
||||
"emoji": "⚡",
|
||||
"items": [
|
||||
{ "text": "W.Rank now tracked per character, not per player — borrowing a character preserves its rank", "emojiKey": "wrank_2_gold" },
|
||||
{ "text": "Bringer validation — must be W.Rank 1 AND have 7 TGs, otherwise no Bringer this week", "emojiKey": "luminous_bringer" }
|
||||
{
|
||||
"text": "W.Rank now tracked per character, not per player — borrowing a character preserves its rank",
|
||||
"emojiKey": "wrank_2_gold"
|
||||
},
|
||||
{
|
||||
"text": "Bringer validation — must be W.Rank 1 AND have 7 TGs, otherwise no Bringer this week",
|
||||
"emojiKey": "luminous_bringer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -30,9 +47,18 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "`lastRankChangeAt` timestamp on W.Rank entries — drives the 24h snapshot window", "emojiKey": null },
|
||||
{ "text": "W.Rank keys migrated from lowercase `capella/procyon` to `Nation` enum values", "emojiKey": null },
|
||||
{ "text": "`WRankEntry` hydration — runtime entries carry full `Character` object, not just flat fields", "emojiKey": null }
|
||||
{
|
||||
"text": "`lastRankChangeAt` timestamp on W.Rank entries — drives the 24h snapshot window",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "W.Rank keys migrated from lowercase `capella/procyon` to `Nation` enum values",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`WRankEntry` hydration — runtime entries carry full `Character` object, not just flat fields",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,19 +1,30 @@
|
|||
{
|
||||
"version": "v0.6",
|
||||
"date": "2026-06-05",
|
||||
"date": "2026-06-07",
|
||||
"title": "Administration & Score Submission",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "`/tg-admin user map/unmap/list` — register Discord accounts to player profiles", "emojiKey": null },
|
||||
{ "text": "`/tg-admin poll fix-voter` — correct stale poll entries after a restart", "emojiKey": null },
|
||||
{ "text": "Score submission via Submit Score button after TG ends", "emojiKey": null },
|
||||
{ "text": "`/tg score get` — retrieve your score for a specific TG slot", "emojiKey": null }
|
||||
{
|
||||
"text": "`/tg-admin user map/unmap/list` — register Discord accounts to player profiles",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`/tg-admin poll fix-voter` — correct stale poll entries after a restart",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Score submission via Submit Score button after TG ends",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`/tg score get` — retrieve your score for a specific TG slot",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -21,8 +32,14 @@
|
|||
"label": "Improvements",
|
||||
"emoji": "⚡",
|
||||
"items": [
|
||||
{ "text": "ID-first usermap lookup — account links survive Discord username changes", "emojiKey": null },
|
||||
{ "text": "`UserRegistry` — centralized user identity with caching", "emojiKey": null }
|
||||
{
|
||||
"text": "ID-first usermap lookup — account links survive Discord username changes",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`UserRegistry` — centralized user identity with caching",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -30,9 +47,18 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "`CharacterRegistry` — cached character lookups across all users", "emojiKey": null },
|
||||
{ "text": "`Attendance` system — snapshots who attended each TG at lock time", "emojiKey": null },
|
||||
{ "text": "`Score` namespace — centralized score submission and retrieval", "emojiKey": null }
|
||||
{
|
||||
"text": "`CharacterRegistry` — cached character lookups across all users",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`Attendance` system — snapshots who attended each TG at lock time",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`Score` namespace — centralized score submission and retrieval",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,19 +1,30 @@
|
|||
{
|
||||
"version": "v0.7",
|
||||
"date": "2026-06-08",
|
||||
"date": "2026-06-09",
|
||||
"title": "UI Layout System",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
"label": "New Features",
|
||||
"emoji": "✨",
|
||||
"items": [
|
||||
{ "text": "Poll layout system — multiple display styles, switchable via `/tg-config poll set-layout`", "emojiKey": null },
|
||||
{ "text": "`default` layout — standard vertical nation fields", "emojiKey": null },
|
||||
{ "text": "`side-by-side` layout — nations displayed inline, auto-stacks when >5 players per nation", "emojiKey": null },
|
||||
{ "text": "Layout persists across bot restarts", "emojiKey": null }
|
||||
{
|
||||
"text": "Poll layout system — multiple display styles, switchable via `/tg-config poll set-layout`",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`default` layout — standard vertical nation fields",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`side-by-side` layout — nations displayed inline, auto-stacks when >5 players per nation",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Layout persists across bot restarts",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -21,9 +32,18 @@
|
|||
"label": "Improvements",
|
||||
"emoji": "⚡",
|
||||
"items": [
|
||||
{ "text": "Voting Yes and No now run in parallel — faster poll embed updates", "emojiKey": null },
|
||||
{ "text": "Autocomplete filtered by nation for Bringer set command", "emojiKey": null },
|
||||
{ "text": "Character class now carries full name and emoji — `Force Blader`, `Wizard`, etc.", "emojiKey": null }
|
||||
{
|
||||
"text": "Voting Yes and No now run in parallel — faster poll embed updates",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Autocomplete filtered by nation for Bringer set command",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Character class now carries full name and emoji — `Force Blader`, `Wizard`, etc.",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -31,11 +51,26 @@
|
|||
"label": "Under the Hood",
|
||||
"emoji": "🔩",
|
||||
"items": [
|
||||
{ "text": "`BaseLayout` — shared functions inherited by all layouts, override only what differs", "emojiKey": null },
|
||||
{ "text": "Layout auto-discovery — drop a file in `layouts/` and it registers automatically", "emojiKey": null },
|
||||
{ "text": "`Character` type now carries `ownerKey` and full `CharacterClass` object", "emojiKey": null },
|
||||
{ "text": "`SerializableCharacter` — clean JSON serialization boundary, runtime uses rich types", "emojiKey": null },
|
||||
{ "text": "`Nation` converted to string enum — serializes cleanly, exhaustiveness checked by TypeScript", "emojiKey": null }
|
||||
{
|
||||
"text": "`BaseLayout` — shared functions inherited by all layouts, override only what differs",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "Layout auto-discovery — drop a file in `layouts/` and it registers automatically",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`Character` type now carries `ownerKey` and full `CharacterClass` object",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`SerializableCharacter` — clean JSON serialization boundary, runtime uses rich types",
|
||||
"emojiKey": null
|
||||
},
|
||||
{
|
||||
"text": "`Nation` converted to string enum — serializes cleanly, exhaustiveness checked by TypeScript",
|
||||
"emojiKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
"date": "2026-06-11",
|
||||
"title": "Framework & Architecture",
|
||||
"layout": "default",
|
||||
"messageId": null,
|
||||
"sections": [
|
||||
{
|
||||
"type": "new",
|
||||
|
|
@ -12,7 +11,7 @@
|
|||
"items": [
|
||||
{ "text": "`/tg poll mark-left` — mark a character as having left TG mid-game with 🪲 counter", "emojiKey": "cockroach" },
|
||||
{ "text": "Leave counter displays total times a character has left TG", "emojiKey": null },
|
||||
{ "text": "W.Rank no-rank placeholder alignment — players without rank align correctly with ranked players", "emojiKey": "wrank_no_dash" }
|
||||
{ "text": "W.Rank no-rank placeholder alignment — players without rank now align correctly with ranked players", "emojiKey": null }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -20,11 +19,8 @@
|
|||
"label": "Improvements",
|
||||
"emoji": "⚡",
|
||||
"items": [
|
||||
{ "text": "Config system moved from `.env` to `config.json` — hot-reloadable, no restart needed", "emojiKey": null },
|
||||
{ "text": "All config organized into sections — `poll`, `wrank`, `channels`, `roles`, etc.", "emojiKey": null },
|
||||
{ "text": "Scheduler plugin system — cron jobs are self-contained files, drop one in to add a job", "emojiKey": null },
|
||||
{ "text": "Logger with levels and context icons — structured, filterable output", "emojiKey": null },
|
||||
{ "text": "Benchmark profiling — vote path performance measured, optimized with `Promise.all`", "emojiKey": null }
|
||||
{ "text": "Voting is faster — poll embed and companion ephemeral now update in parallel", "emojiKey": null },
|
||||
{ "text": "Config changes no longer require a bot restart — hot-reloadable from `config.json`", "emojiKey": null }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -34,9 +30,12 @@
|
|||
"items": [
|
||||
{ "text": "`Runtime` lifecycle system — phased startup: load → restore → connect → schedule → ready", "emojiKey": null },
|
||||
{ "text": "`Config` namespace — `Config.get({ section, key })` with full type safety per section", "emojiKey": null },
|
||||
{ "text": "All config organized into sections — `poll`, `wrank`, `channels`, `roles`, etc.", "emojiKey": null },
|
||||
{ "text": "`Store` abstraction — centralized JSON file I/O with error handling", "emojiKey": null },
|
||||
{ "text": "`Paths` helper — no more `path.join(__dirname, ...)` scattered across the codebase", "emojiKey": null },
|
||||
{ "text": "`Discord` abstraction layer — `Discord.Interaction`, `Discord.Guild`, `Discord.Channel`", "emojiKey": null },
|
||||
{ "text": "`Scheduler` plugin system — drop a file in `scheduler/` to add a cron job", "emojiKey": null },
|
||||
{ "text": "`Logger` with levels and context icons — structured, filterable output", "emojiKey": null },
|
||||
{ "text": "`WRankEntry` hydration — runtime entries carry full `Character` object", "emojiKey": null },
|
||||
{ "text": "`Leaves` system — character leave tracking keyed by character name and history key", "emojiKey": null }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,11 +11,18 @@ import {
|
|||
MessageFlags,
|
||||
} from "discord.js";
|
||||
|
||||
import { Logger } from "@systems/logger";
|
||||
import { Benchmark } from "@systems/benchmark";
|
||||
|
||||
type AnyInteraction =
|
||||
| ChatInputCommandInteraction
|
||||
| ButtonInteraction
|
||||
| ModalSubmitInteraction;
|
||||
|
||||
// ─── Logger ─────────────────────────────────────────────────────────
|
||||
|
||||
const log = Logger.for("Discord.Interaction");
|
||||
|
||||
// ─── Options resolver ─────────────────────────────────────────────────────────
|
||||
|
||||
export interface OptionParams {
|
||||
|
|
@ -80,10 +87,35 @@ async function followUp(interaction: AnyInteraction, params: ReplyParams): Promi
|
|||
});
|
||||
}
|
||||
|
||||
async function deferReply(interaction: AnyInteraction, { ephemeral = false } = {}): Promise<void> {
|
||||
const bench = Benchmark.start("deferReply");
|
||||
try {
|
||||
await interaction.deferReply({
|
||||
flags: ephemeral ? MessageFlags.Ephemeral : undefined
|
||||
});
|
||||
bench.end();
|
||||
} catch (err: any) {
|
||||
log.warn(`deferReply failed (interaction likely expired): ${err.message}`);
|
||||
bench.end();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function editReply(interaction: AnyInteraction, params: ReplyParams | string): Promise<void> {
|
||||
const opts = typeof params === "string" ? { content: params } : params;
|
||||
await interaction.editReply({
|
||||
content: opts.content,
|
||||
embeds: opts.embeds,
|
||||
components: opts.components,
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Namespace ────────────────────────────────────────────────────────────────
|
||||
|
||||
export const Interaction = {
|
||||
options,
|
||||
reply,
|
||||
followUp,
|
||||
deferReply,
|
||||
editReply
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
import { ChatInputCommandInteraction } from "discord.js";
|
||||
import { Updates } from "@systems/updates";
|
||||
import { replyAndDelete } from "@utils";
|
||||
import { Discord } from "@discord";
|
||||
|
||||
export async function handleUpdatesPost(interaction: ChatInputCommandInteraction): Promise<void> {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
await Discord.Interaction.deferReply(interaction, { ephemeral: true });
|
||||
|
||||
const options = interaction.options as any;
|
||||
const version = options.getString("version") ?? Updates.latest();
|
||||
const opts = Discord.Interaction.options(interaction);
|
||||
const version = opts.string({ key: "version" }) ?? Updates.latest();
|
||||
|
||||
if (!version) {
|
||||
await interaction.editReply("❌ No versions found.");
|
||||
await Discord.Interaction.editReply(interaction, "❌ No versions found.");
|
||||
return;
|
||||
}
|
||||
|
||||
await Updates.post({ version, client: interaction.client });
|
||||
await interaction.editReply(`✅ Update \`${version}\` posted.`);
|
||||
try {
|
||||
await Updates.post({ version, client: interaction.client });
|
||||
await Discord.Interaction.editReply(interaction, `✅ Update \`${version}\` posted.`);
|
||||
} catch (err: any) {
|
||||
await Discord.Interaction.editReply(interaction, `❌ Failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpdatesPreview(interaction: ChatInputCommandInteraction): Promise<void> {
|
||||
const options = interaction.options as any;
|
||||
const version = options.getString("version") ?? Updates.latest();
|
||||
await Discord.Interaction.deferReply(interaction, { ephemeral: true });
|
||||
|
||||
const opts = Discord.Interaction.options(interaction);
|
||||
const version = opts.string({ key: "version" }) ?? Updates.latest();
|
||||
|
||||
if (!version) {
|
||||
await interaction.reply({ content: "❌ No versions found.", ephemeral: true });
|
||||
await Discord.Interaction.editReply(interaction, "❌ No versions found.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -34,21 +40,20 @@ export async function handleUpdatesList(interaction: ChatInputCommandInteraction
|
|||
const latest = Updates.latest();
|
||||
const lines = versions.map((v) => {
|
||||
const entry = Updates.get({ version: v });
|
||||
const posted = entry?.messageId ? "✅" : "⬜";
|
||||
const tag = v === latest ? " ← latest" : "";
|
||||
return `${posted} \`${v}\` — ${entry?.title ?? ""}${tag}`;
|
||||
return `⬜ \`${v}\` — ${entry?.title ?? ""}${tag}`;
|
||||
});
|
||||
|
||||
await interaction.reply({
|
||||
await Discord.Interaction.reply(interaction, {
|
||||
content: lines.length > 0 ? lines.join("\n") : "No versions found.",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function autocompleteVersion(interaction: any): Promise<void> {
|
||||
const focused = interaction.options.getFocused().toLowerCase();
|
||||
const versions = Updates.list();
|
||||
const choices = versions
|
||||
const focused = interaction.options.getFocused().toLowerCase();
|
||||
const versions = Updates.list();
|
||||
const choices = versions
|
||||
.filter((v) => v.toLowerCase().includes(focused))
|
||||
.map((v) => {
|
||||
const entry = Updates.get({ version: v });
|
||||
|
|
@ -57,10 +62,3 @@ export async function autocompleteVersion(interaction: any): Promise<void> {
|
|||
.slice(0, 25);
|
||||
await interaction.respond(choices);
|
||||
}
|
||||
|
||||
export const UpdatesCommands = {
|
||||
post: handleUpdatesPost,
|
||||
preview: handleUpdatesPreview,
|
||||
list: handleUpdatesList,
|
||||
autocomplete: autocompleteVersion,
|
||||
};
|
||||
|
|
@ -83,6 +83,8 @@
|
|||
}[];
|
||||
}
|
||||
|
||||
let _versionsCache: string[] | null = null;
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function updatesDir(): string {
|
||||
|
|
@ -195,10 +197,13 @@
|
|||
// ─── Updates namespace ────────────────────────────────────────────────────────
|
||||
|
||||
export const Updates = {
|
||||
list(): string[] {
|
||||
const index = Store.read<VersionsIndex>(path.join(updatesDir(), "versions.json"));
|
||||
return index?.versions ?? [];
|
||||
},
|
||||
list(): string[] {
|
||||
if (!_versionsCache) {
|
||||
const index = Store.read<VersionsIndex>(path.join(updatesDir(), "versions.json"));
|
||||
_versionsCache = index?.versions ?? [];
|
||||
}
|
||||
return _versionsCache;
|
||||
},
|
||||
|
||||
latest(): string | null {
|
||||
const index = Store.read<VersionsIndex>(path.join(updatesDir(), "versions.json"));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue