# Modern Relaxy! Changelog

## [1.1.1_2] - 20:45 CEST 17-08-2026

### Fixed

- Warnings no longer should display above the limit f.e 13/10 when being batched, f.e when someone's account is compromised and they spam scam messages

## [1.1.1_1] - 02:00 CEST 17-08-2026

### Most important stuff

- `=ban @everyone` asks three times, and for an administrator the answers are remembered for 24 hours.
- The image scam memory survives a restart, and remembers a confirmed scam for a year instead of a day.

### Moderation

- `=ban @everyone` no longer empties a server on one click, needs a 3 time confirmation .

### Quality of life

- Every date Relaxy prints is now a live Discord timestamp.

## [1.1.1] - 21:45 CEST 16-08-2026

### Moderation

- `=exempt` is per capability now, not one switch.
  Being on the exempt list used to mean exempt from EVERYTHING, so a member trusted enough to have their links survive
  the censor was equally trusted to run `=remake` and empty a channel. Two very different levels of trust under one
  name, with no way to grant the smaller one. There are eight: `censorwarn`, `censor`, `mute`, `kick`, `ban`, `staff`,
  `immunity`, `total`.
  `=exempt @Trusted censor` leaves their messages alone. `=exempt @Mod staff` hands over mute, kick and ban *and*
  protects them from being moderated back. `=exempt categories` explains each one, `=exempt list` shows what is set, and
  `=exempt remove @them ban` takes a single one away.
  Everyone who was exempt before this becomes `total`, which is exactly what they already had.
- `=mute (stuff here) dm` tells the member why they were muted.
- The modlog can leave things out. `=modlog exempt <@user|@role|#channel> [event… | all]`, with `unexempt` and
  `exemptions` beside it. 

### Quality of life

- `=status server` opens the health page directly

## [1.1.0] - 16:30 CEST 16-08-2026

### Most important stuff

- `=quarantine` Compromised high-permission accounts can no longer wreck a server in seconds.
- Members without `Mention Everyone` can no longer use Relaxy! to bypass that.
- `=ban <user> scam` - for an account that was stolen rather than malicious.

### Moderation

- The censor stopped deleting documents.
- URL shorteners are recognized. `bit.ly`, `t.co`, `goo.gl` etc.
- Russian scam screenshots are read. OCR runs `-l eng+rus`
- `=custompermissions <category>`
- Ban announcements say who was banned, no longer use just the @ of the user.
- Member update modlogs now show the diff of roles.
- fixed the fact a role swap didn't show up correctly.
- Appeal threads now notify when the user leaves the server.

### Raid protection

- Added alerts and greatly improved internal functionality.
- New: `=raidprotection anythreshold <n>`. Trigger on a burst of joins whatever the accounts look like.

### Music

- Fixed the voice-connection crash. A `/play` into a channel that would not connect produced an `AbortError`, now fixed.
- A shard will not recluster mid-song anymore

### Fun and quality of life

- Heartboarded posts with several pictures are composed into one image. An embed has a single image slot, so the
  other pictures used to become blue links. Two images split down the middle, three split across the middle with the
  bottom half split again, four as quarters.
- Relaxy suggests the command you meant. Make a channel, then run a command that wants one and forget to paste the
  id, and the answer is now the exact line to run rather than "No arguments provided!". Remembered per member for ten
  minutes.
- `=prefix` was rewritten. `add`, `remove`, `set`, `reset` and a numbered list, and prefixes may contain spaces.
  `=prefix add "hey relaxy "` or `=prefix add hey~_relaxy~_`, so `hey relaxy play something` works. Longest prefix wins,
  so a short one cannot shadow a long one, and removing the last remaining prefix is refused.
- `=status` now has 2 pages. This server's systems. Every Relaxy system configured here, whether it actually
  works, and the exact missing permission it has including the failures that look fine
  from Discord's settings screen, like a warning-ladder rule naming a role above Relaxy's own, or a quarantine role that
  cannot be handed out.
- `=whois` now reads `Display name - username` in its author line.
- `=profile` counters work correctly now, didn't use to work for / commands.

### Fixed

- `=pfp` and every other file-only answer came back empty when its interaction reply had expired. The fallback carried
  the text and the embeds and dropped the files, so Discord was asked to send nothing at all.
- `client.edit` sent `attachments: []` on every edit, silently stripping attachments from any message it touched. Scoped
  by argument count, so all existing callers keep their exact behaviour.
- Removed deprecated lib methods.
- Library deprecation warnings print once and no longer count toward shard health.

### New commands

| Date       | Command                                                |
|------------|--------------------------------------------------------|
| 16-08-2026 | `=quarantine` (aliases `=antinuke`, `=lockdownmember`) |

## [1.0.41] - 21:35 CEST 11-08-2026

## Updated
- `=censor` had it's description and annotations updated.

## [1.0.4] - 21:30 CEST 11-08-2026

## Updated
- `=censor` now performs 256 bit perceptual hashing and applies an entropy matching algorithm on top of it in order to match images based not on text but on look-alike probablity.  
Currently gathering data on confirmed scam images, the entropy based detection will kick in within a week or two once enough hash data has been gathered.  
Censoring will also ignore keyword starting with `@` within OCR-based recognition.  
The command description has also been updated.  

## [1.0.3] - 22:00 CEST 10-08-2026

### Fixed & Updated
- `=level`, `=leaderboard` and internal rank mechanism for servers  
They were fetching whole servers causing long load times even when leveling was disabled.

## [1.0.2] - 20:35 CEST 10-08-2026

### Updated
- `=invite` now also displays link buttons
- `=help` the buttons are now displayed in 2 rows, sorted

### Fixed
- **ModuleLoader** was broken because of the internal file path reordering
- **Private Channels** were broken giving people the ability to edit any vc lol, fixed the guarding clause

## [1.0.1] - 18:45 CEST 10-08-2026

### Updated
- `=invite` now displays properly

## [1.0.0 Public Release] - 17:30 CEST 10-08-2026

Everything since the GA3.0 line began on 23-10-2023, and the release that made the source public.
`changelog-old.md` covers 7.4.1 (05-09-2021) through 2.4.4 (20-09-2023).

### Most important stuff

- **A music player kept in the source.** `src/Client/Modules/MusicPlayer/` is a custom Relaxy!fied version of
  `discord-player` 7.2.0.
- **The database was redesigned.** The guild is two documents, a small one read on every message
  and a cold config read only when something needs it. Short keys throughout, because on a
  database this size the key strings alone cost megabytes. Defaults are stripped on write and
  merged back on read, so a document only stores what differs, which also means adding a field
  needs no migration. Every write is queued, batched and executed on a worker thread, and a
  registry stores each repeated Discord id once instead of over and over.
- **Self-repair.** A watchdog restarts a shard that is drowning in errors, the manager hot-reloads
  modules without downtime, and a file watcher decides whether a change needs a reload or a
  recluster.
- **Configuration out of the source.** Secrets and host paths live in `.env`, operator settings
  live in the database and are changed from the manager console, and every asset resolves through
  one CDN layer with an on-disk fallback.
- **Cross-server families**, sharing roles, ban appeals and, where each server opts in,
  moderation.

### Cross-server links

- **`=syncservers` / `=syncrole`** (14-07-2026, 25-07-2026). Any number of servers form a pool and
  share roles. A role granted in any of them is granted in all of them; the newest change stands
  everywhere. There is no hub server and no owning server.
- **The handshake.** One side runs the bare command, which records a proposal on that user's
  profile for thirty minutes; the same person runs the mirror command in the other server, which
  accepts. Each command carries its own permission gate, so a link can only exist between servers
  where one person holds that permission on both. No ids, tokens or invites are involved, and
  nothing can be linked unilaterally.
- **Role adoption.** Existing roles are adopted rather than duplicated. A family of servers
  sharing `Trusted` almost always already has a `Trusted` in most of them. Everyone already
  holding the adopted role is in the shared group immediately, and the rollback on a failed link
  may only delete roles that the call itself created. Deleting an adopted one would strip a role
  that predates the command from everybody who had it.
- **Role groups.** Each group names the servers it covers, so `Trusted` can span the main server
  and the wiki while `Modder` spans the main server and the modding server, and the two never
  touch. `=syncrole attach <role>` opens a group from a role that is called something different in
  each server; only membership is shared, so names, colours and permissions stay local.
- **`=appealchannel`** (14-07-2026). A banned member cannot post in the server that banned them,
  so the channel they appeal in lives in the partner server. Relaxy opens one private thread per
  ban, pings the configured appeal roles (or, when none of them still exists, picks up to three
  members by moderation permission and states that the pick was automatic), and posts the paged
  dossier into the thread, readable by staff and not by the appellant.
- **One thread per ban.** One appeal server can host any number of guest servers, so somebody
  banned in three of them has three appeals to make. The thread name carries the guest server, and
  four separate guards stop a rejoin, a re-ban or a redelivered `guildMemberAdd` from minting a
  second thread, including a rescan after the create that settles two processes racing each other
  by letting the lowest thread id win.
- **Synced moderation**, off by default and decided per server (`=syncservers moderation on`).
  `=sban`, `=skick`, `=smute`, `=sunban`, `=sunmute` and `=swarning` (25-07-2026 and 02-08-2026)
  carry an action to every consenting partner. Each one runs through that server's own moderation
  module, so it lands with that server's case number, modlog entry, mute role and saved role list
  rather than a foreign ban placed over the top. The local action always goes first. `=tempban` is
  not syncable, because its expiry sweep is per guild and a synced copy would not carry the
  expiry.
- A synced warning carries its tier but not its autoban. The ladder is evaluated locally only.

### Moderation

- **A shared action layer** (20-07-2026). `=ban`, `=kick`, `=mute`, `=unmute` and `=warning` run
  through one module, so an action placed from a command, from the dashboard or by a synced
  partner lands with the same case, modlog entry and notice.
- **Moderation cases** (`=case`, 11-07-2026). Numbered cases with their moderator, reason and
  deletion window. They cover actions taken by hand in the Discord client as well as Relaxy's own,
  because Discord raises no "a moderator kicked someone" event: a kick is indistinguishable from
  leaving and a timeout is a member update, so the events read the audit log to find out who
  acted.
- **Lifts are recorded as outcomes.** An unban is recorded on the ban it lifts and an unmute on
  the mute, whether a person or an expiry did it, so `=case` reads "banned, since then: unbanned"
  as one story. A timeout lift stands alone, because Discord raises the same event for applying
  and removing one.
- **`=dossier`** (18-07-2026, aliases `modprofile`, `record`, `rap`). One member's whole file on
  one screen: standing (banned, muted, timed out, exempt, elevated, granted commands), the live
  ban beside the case it was opened under, the mute row, every name Relaxy has seen them use,
  every case from both sides (paged, not truncated), warnings with tiers, and activity. One server
  per page, and the page switcher only reaches servers this one is linked to. An unlinked server
  id is refused rather than quietly answered.
- **The warning ladder** (`=warntiers`, 02-08-2026). `=autoban` sets one number for a whole
  server; this is the rest of it. Per tier, a list of rules that mute, kick, ban, or add or remove
  a role at a given count. The ladder fires on the crossing rather than on the count, applies the
  most severe rule once, remembers the crossing rather than re-deriving it, and covers the
  warnings the censor issues itself as well as the ones a moderator typed. A ladder ban or kick
  ends the warning, so `=autoban 5` beside a ban rule at 5 no longer bans twice with two case
  numbers.
- Warnings gained tiers and edits. Mutes are persistent, survive restarts and lift themselves.
- **One reply shape across moderation commands.** Subject, reason, moderator, case, then the
  fields that action alone has, then anything synced. There were five shapes before this: `=mute`
  recorded a reason on the case without printing it, `=unmute` showed neither reason nor case, and
  `=unban` printed the reason the person was banned for rather than the one being entered.
- **Raid protection made public** (`=raidprotection`, moved out of `administrator` 12-07-2026; the
  command dates to April 2023). Watches for bursts of joins from accounts under a configurable
  age, then bans or kicks the whole burst and keeps removing suspicious joiners, and any bots,
  until the raid subsides. Configurable threshold, detection window and account age.
- **Image scam detection** (18-07-2026). The scam accounts post a screenshot with an empty message
  body, so the words that would trip the filter live in the pixels. Relaxy reads them with
  `tesseract-ocr` out of process and feeds the text through the same censor. Distinct images are
  read once, keyed on a hash of their bytes rather than on the URL, because a Discord attachment
  gets a fresh id and a fresh signed link on every upload. Ten channels of four screenshots costs
  four OCR runs and thirty-six lookups. The memory is shared across the whole fleet, since a
  digest identifies a picture and nothing about a picture is per shard.
- Censoring gained per-word severity, actor and channel allow and deny lists, exemption words, and
  link, invite, scam and spam lists, with the scam domain list pulled from the CDN. Exceptions
  became their own module (13-07-2026).
- **Modlogs are per event**, drained on a timer with a burst window so one incident is one entry
  rather than thirty. Anything an owner did is not logged: owners administer the bot across
  servers they do not staff, and their work is not that server's moderation history. The case is
  still recorded.
- **The blacklist moved into the database** (settings collection) from
  `assets/config/blacklist.json`, which was read off local disk by every cluster and rewritten
  whole by every cluster, so it never crossed hosts and two owners editing at once lost one of the
  edits. The two tiers are not new, they date to the file version in February 2024: a grace period
  that silences a user everywhere Relaxy reaches, and enforcement, which additionally bans them in
  every server they are in and blocks every future join. What is new is that an enforced server is
  stripped, left and purged at once, that a failed read is not treated as an empty list, that
  reads fail open until the first refresh lands, and that an owner is never enforced whatever the
  list says.
- **Temporary bans** (`=tempban`, 29-11-2025). Bans that lift themselves.
- **`=ban message <message|no>`** (16-07-2026) builds the embed a member is DM'd as they are
  banned, with placeholders for the user, the server, the reason and the case number. Where the
  server has an appeal channel, the invite to it follows the message. It replaces the standalone
  `=banmessage`.
- `=purge`, `=warnings`, `=restrain`, `=restrict` and `=exempt` reworked.

### Levelling and rewards

- **`=leveltier`** (02-08-2026). Per level, a list of rewards: grant a role, remove a role, pay
  money, or grant a Relaxy command. Four things differ from the warning ladder deliberately. The
  level in a rule is the level members see (stored levels are zero-based and every user-facing
  surface prints one more). Every rule crossed applies, not just the most severe. Roles are
  settled as a state rather than a list of events, so a "highest badge only" ladder works at every
  level in between and a role a moderator granted early is never stripped. Money is the one reward
  paid on the crossing and never re-run, since state rewards are re-asserted on every level-up.
- **`=leveltier sync`** back-fills a server that has been levelling for years or a rule added
  after the fact. It is driven off member documents, is resumable (a capped run records where it
  stopped), and pays no money, which the reply states.
- **Ranking.** The leaderboard and the server rank share one ordering path; the global rank
  applies the same rule as a database aggregation across every profile. The previous
  implementation built a `level.userid` string and sorted it with a hand-written radix that
  compared entries by coercing each string to a float, which truncates a nineteen-digit id to
  nothing, so the tie-break silently did not exist. It also pushed every remaining element into
  its result while leaving it in the working array, so 400 members came back as 690 entries with
  duplicates and 110 people ranked above the member count.

### Fun, economy and games

- **Counting game** (`=counting`, `=countingleaderboard`, 12-07-2026). A dedicated counting
  channel with a no-counting-twice-in-a-row rule, an optional hard mode that resets the count on a
  wrong number, a high score, payouts and per-member tallies.
- **`=daily`** (18-07-2026), with a streak that survives a 48 hour gap.
- **`=tictactoe`** (18-07-2026). Nine buttons, xp and money to the winner.
- **`=tradeout`** (18-07-2026). Opt out of trading in both directions.
- Voice activity, reaction activity and daily statistics became their own modules (18-07-2026)
  rather than inline code in the message path.

### Server features

- **Ticket system** (`=ticketsystem`, first landed 19-06-2024 and rebuilt through early 2025).
  Button-driven support tickets opened as private threads, with configurable forms, buttons and
  per-system designs.
- **Join-to-create voice channels** (`=jointocreate`, 08-02-2025). A lobby channel that spawns a
  private voice channel per user and cleans it up when they leave.
- **Card rendering in-house** (27-06-2026). `canvacord` v6 removed the rank, welcome and leave
  builders, so Relaxy draws them itself with node-canvas, keeping the same chained setters so call
  sites only swap the constructor. From 17-07-2026 they are drawn on a pool of worker threads: a
  rank card is about 27 milliseconds of canvas work and is drawn on every level-up and every join
  rather than on request, so on a busy guild the shard blocked for longer than an Opus frame's 20
  millisecond deadline several times a minute.
- **Cross-hosting bridge** (`src/Bridge/`). A bridge server that hands each machine the shards it
  should run, so the system can outgrow a single box. Prepared, not yet wired into the fleet.

### Music

- `src/Client/Modules/MusicPlayer/` holds the queue, history, player node,
  audio filters, stream dispatcher, extractor context and query resolver. The npm `discord-player`
  package is still declared, and the scoped `@discord-player/*` packages (equalizer, extractor,
  ffmpeg, opus, utils) are still real runtime dependencies. Nothing imports `discord-player`
  itself.
- **A yt-dlp extractor** (11-07-2026), with a shared cookie the operator rotates fleet-wide from
  the console rather than by redeploying. It replaced a youtubei extractor kept in the source,
  with a SABR core and PO token generation that ran from 11-10-2025 to 12-07-2026.
- **SoundCloud** resolution through `discord-player-soundcloud`, registered ahead of the default
  bundle so the bundled copy is skipped. **Spotify**, Apple Music, Vimeo, Reverbnation and
  attachments through the default extractor set, and **LrcLib** lyrics kept in the source.
- Extractor loading is started without being awaited, so the network work it does never sits on
  the path to logging in, and one failing extractor cannot bring down the rest of the music stack.
- **A track that resolves but produces no stream is retried** before anything is said to the
  channel. A throttled player-config request or an expired signature cipher looks exactly like an
  unplayable video. Attempts are counted per track id, so a queue of ten dead tracks does not
  spend the budget on the first two.
- A prefetcher (13-07-2026) and a per-guild options module (16-07-2026).
- `=play playlist <link>`. A link carrying both a video and a playlist plays just the video by
  default; putting `playlist` in front of it queues the whole list with the linked video first.
- Playlists queue as their tracks rather than as one opaque object. An extractor built from the
  npm package's classes produced playlists of a foreign class identity, and those were being
  queued as a single unplayable track.
- `=bitrate` works again after being disabled.
- Filters, `=jump`, `=swap`, `=repeat`, `=goto`, `=clear`, `=reverse`, `=shuffle` and `=pskip`
  reworked.
- The volume stage stays in the pipeline permanently. Removing it to get Opus passthrough would
  make `=volume` unsettable.

### Infrastructure and operations

- **The cluster manager was rebuilt** on `discord-hybrid-sharding` (20-09-2024), with heartbeats,
  re-clustering, self-repair, a spawn processor and a console for operating the fleet: `show`,
  `toggle`, `update`, `restart`, `halt`, `recluster`, `sync`, `eval`, plus `cookie`, `opmode`,
  `watchdog`, `self_repair`, `low_performance`, `heartbeat`, `pids`, `queues`, `slash`, `ping`,
  `uptime` and `dependencies`. The console grammar dates to 14-09-2024, when flat verbs like
  `show_help` became verb plus subcommand.
- **The ADSR watchdog** counts a shard's errors and warnings and restarts it before it takes
  anything down with it.
- **An internet watchdog** on the manager (14-05-2025) recognises an outage across the whole
  fleet, kills every cluster and holds off respawning until it is over. The cooldown is armed when
  the recovery ends rather than when it is triggered, and the wait for the respawn is capped,
  because a spawn that never resolves would hold the lock open forever. There is now one network
  error classifier (02-08-2026) where there had been three copies that had drifted apart. Every
  occurrence is counted, because that is the watchdog's only input and throttling it would raise
  the trigger threshold by however much noise happened to be deduplicated, while the console line
  is rolled up per cluster per 15 seconds.
- **A custom rate limiter** queues and paces outbound API calls per route, sized by guild, with a
  handler (13-07-2026), REST retry (16-07-2026) and a load monitor (16-07-2026) alongside it.
- **Hot reloading.** Commands, events and whole modules reload live, and the manager can reload a
  module across every cluster without a restart.
- **A dashboard write API** (13-07-2026 onward). The
  external dashboard posts mutations to the manager, which executes each one on the cluster that
  owns the guild. It may never write to the database directly: the bot keeps documents in memory
  and writes them back whole, so a direct write would be overwritten at the next save. Optimistic
  concurrency is handled by a content revision on both sides, appeal and role-sync roots are
  protected (a plain config write to either would be a cross-server privilege escalation), and a
  third version adds an action layer for making Discord objects exist rather than editing
  documents that already do.
- **A garbage collector** reconciles the database against Discord on every deletion event:
  channel, thread, message, bulk message, role, emoji, member leave and guild removal. It
  deliberately does not guess at lists that mix user and role ids.
- **Eight background sweeps**, because events are only as reliable as the process receiving them.
  Stale stored channel and role ids re-checked against a fetched list every 30 minutes, marked
  guilds purged and orphaned guilds diffed against a fleet snapshot every 6 hours, member presence
  every 15 minutes, departed users and deleted Discord accounts hourly, the levelling backlog
  every 30 minutes, and the in-memory document caches every minute.
- **The grace periods.** A departed guild's data is purged after 180 days and only when no cluster
  in the fleet can see the guild; a departed member's after 30 days. Warnings, mutes and cases are
  never touched, because those are the guild's moderation history rather than the user's data. A
  profile is only judged out of scope on positive evidence, and one with no member documents at
  all is kept forever, since a member document only exists for somebody who has been active and a
  lurker in a live guild has none. Somebody who deleted their Discord account is the one case that
  skips the roster gate: Discord is being asked about the account directly rather than the answer
  being inferred from stale member documents.
- **The levelling backlog sweep** converts experience already earned into the levels it bought, in
  two halves on the same tick: profiles on the sweeper cluster only, since a profile belongs to no
  guild and every cluster running it would race the same documents, and members per guild on the
  cluster that owns them. It is the only caller besides a live level-up allowed to pay a level
  reward, because it knows both ends of the crossing.
- **`=scopeaudit`** (18-07-2026) is a dry run of all of it, same snapshot and same gates, deleting
  nothing.
- **Runtime statistics** (18-07-2026). Per-cluster event loop delay as a histogram rather than a
  timed interval, CPU from process deltas rather than machine load average, and every rate
  measured against a window with the window reported alongside it. `show stats` aggregates the
  fleet and drills into events, commands, database and counters. A cluster that does not answer is
  reported silent, never as zero.
- **Configuration in three tiers**, by how a value changes. Constants in code (the `=` prefix, the
  CDN origin, the Node flags used to fork clusters). Secrets and host-specific paths in `.env`,
  which is where paths live because they leak the account layout of the box. Operator settings
  (version, owners, status, cookie, blacklist) in the MongoDB `settings` collection, changed from
  the console and consistent across every host. `assets/configuration/key.ini`, previously
  required on each machine before startup, is gone.
- **A CDN layer.** Every asset resolves through one place, with anything missing from the local
  mirror downloaded once and cached on disk, so a host without the CDN mounted still runs.
- **Low-performance mode**, detected at boot and toggleable by hand from the console. Introduced
  in February 2024 as a platform check, it now reads the hardware model reported by the system to
  recognise a Raspberry Pi, and treats distribution and hardware as independent. The flag is
  propagated to every cluster and reported by `=status`.
- **Per-feature logging.** Every category is a named channel with one definition, from which both
  the manager's and the client's flag sets are built so the two cannot drift. Toggle one from the
  console, or all of them at once.
- Owners are a list, not a single id.
- Discord.js v14 throughout.
- **Tooling and documentation.** A music vendoring toolchain (`music:check`, `music:plan`,
  `music:validate`, `music:bump`) that compares the  code against a pinned upstream
  commit, database migration and repair scripts, a maintenance updater (`maint:check`,
  `maint:apply`, `maint:dry`), and thirteen documents under `docs/` behind an index.

### Fixed

- **`guild.me` was removed in Discord.js v14.** Seventeen music commands guarded on it, so the
  same-voice-channel check never fired and anyone could skip, stop or reorder another channel's
  queue. The audit-log lookup read the same removed property, and voice cleanup never ran.
- **`=dossier` by name loaded every member record in the guild**, up to about 118,000, and passed
  every one of them between threads. Names are now matched by the database itself, in one query,
  anchored so a search for `bob` cannot return `bobby`. The same path sat behind `=ban`, `=case`
  and `=unban` by name.
- **The censor re-read every warning in the guild once per censored message** to derive one
  number, hardest during a raid. That derivation is cached for five minutes and shared with
  `=warning` and `=warnings`.
- **The autoban acted per message rather than per incident.** A burst of scam images posted across
  several channels each read the warning total after all of them had landed, producing one ban per
  message. A claim taken before the first await now settles the incident once, and the remaining
  messages of the burst are silenced rather than flushed.
- **Messages with an empty `content`.** A poll, a forward, a sticker post, a Components-v2 layout
  and every system message carry an empty `content`, so every surface that printed it went blank
  at once: heartboard reposts, delete logs, purge transcripts, and the censor, which threw before
  reaching its warn or ban decision. All of them now render through one description helper.
- **Heartboards, six bugs.** Posts threw on pre-migration field names and an undefined colour. The
  refetch gate treated any message without text as unloaded, so polls paid a REST fetch per vote.
  Reactor pagination stopped at the first 100, so a post already past that could never be
  corrected. The board document was cached as a divergent second copy, so changing the requirement
  had no effect. Edits are now throttled per board message, and drift against Discord's own count
  is reconciled at most once a minute.
- **Cache presence was treated as cluster ownership.** A single REST fetch of a foreign guild left
  it cached permanently, after which `broadcastEval` bodies ran on more than one cluster: two
  appeal threads, two invites, two bans with two case numbers. Ownership is now tested against the
  shards the cluster actually runs.
- **A cold guild setting read as its default everywhere.** The cold config blueprint did not pin
  its collection name, so writes and reads went to different collections. `scripts/repair-db.mjs`
  merges them.
- **A missing permission reached nobody who could grant it.** The channel Relaxy would report it
  in is the one it cannot post in. It now DMs the owner, falling through to the highest-permission
  members, at most once a day per guild.
- **The track queue length check** read `length` rather than `size`, so the guards in `=swap` and
  `=clear` never fired.
- **`=remindme` did not run.** An early return sat in front of the prefix path, and the slash path
  passed a string where an array was expected.
- **Permissions were checked against the guild rather than the channel**, so a channel-level
  denial passed the gate and failed at the API instead.
- **`=redirect` rejected every forum channel**, and `=redirect welcomes` and `=redirect leaves`
  failed outright.
- **A modlog write could stop about 25 event handlers**, when a guild's modlog array had not been
  initialised (03-02-2024).
- **A leaderboard page past the end** threw inside an async collector, so any server with fewer
  than fifteen member documents failed on the first button press with no reply.
- **Snowflake-keyed paths could not be written.** The path setter reads a numeric segment as an
  array index, so a user id as a key built a sparse array. The appeal thread record is written as
  a whole map, with a schema version bump.
- **A stored reason is no longer printed inside a bare code span**, where a mention inside it
  rendered as a raw id.
- **Relaxy is no longer pinged as the moderator on its own autobans**, which added nobody to the
  appeal thread.
- **Embeds could exceed Discord's field limits and be rejected whole** (`=whois` on a member with
  many roles, `=roleinfo` on an administrator role). One module now enforces the limits
  (03-08-2026).
- Saving a file to server storage was not permission-checked.
- `#relaxy-log` reports raised on any cluster other than the one owning the support server were
  discarded, so most join and leave reports never arrived.
- A Discord outage was treated as a guild removal.
- `=repeat`'s "off" turned on autoplay.
- `=goto` and `=jump` failed instead of reporting that nothing is playing.

### Changed

- The counting game, raid protection and appeal channels moved out of the `administrator` category
  (12-07-2026 and 14-07-2026). That category is excluded from slash-command registration and
  hidden from non-owners in `=help`, so a finished user-facing command left there has no slash
  command and is invisible.
- `client.stats` and the periodic usage reports are gone. It was ten lifetime counters plus a
  per-command tally, incremented from a dozen hot paths and read by nothing once the dashboard
  replaced the reports. Everything it tracked is now a statistics counter, which unlike a lifetime
  total can be read as a rate over a named window.
- `toggle debug` no longer touches the operating mode. It used to write the debug operating mode
  into the persisted setting, so turning logging on also announced to users that Relaxy was under
  maintenance.

### Removed

- **`=banmessage`** (16-07-2026), now `=ban message <message|no>`.
- **`=timestamp`** (03-08-2026), now a tool under `=toolkit`.
- **`=visusage`** and `scripts/visualise_usage.py` (03-08-2026 and 25-07-2026).
- **`=reddit`** (12-07-2026), **`=warningtier`** (18-05-2025) and the seven prototype
  `n_`-prefixed music commands, `=clearq` and `=leave` (30-10-2023).

### Internal

- The source was split into `src/Client` and `src/Manager` with a module loader (20-09-2024), then
  into `src/Config`, `src/Core`, `src/Assets`, `src/Utils`, `src/Client`, `src/Manager` and
  `src/Bridge` (12-07-2026).
- `src/Client/Modules/` was regrouped (08-08-2026). Now:
  `ADSR/`, `Cards/`, `Core/`, `Dashboard/`, `Database/`, `Guilds/`, `Handlers/`, `Moderation/`,
  `Music/`, `MusicPlayer/`, `Progression/`, `VoiceRecorder/`, `Workers/`. The vendored player
  stays at `MusicPlayer/` deliberately, because it is diffed against upstream.
- TypeScript declarations and a `tsconfig.json` (12-02-2026), so an editor can see through the
  dynamic module loading.

### New and newly public commands

| Date       | Command                                                                                  |
|------------|------------------------------------------------------------------------------------------|
| 30-10-2023 | `=clear` (music)                                                                         |
| 17-01-2024 | `=pickrandomreacter`, moved to miscellaneous 04-07-2024                                  |
| 18-02-2024 | `=induce_death`                                                                          |
| 19-06-2024 | `=ticketsystem`                                                                          |
| 04-07-2024 | `=sakify`                                                                                |
| 08-02-2025 | `=jointocreate`                                                                          |
| 23-02-2025 | `=move`                                                                                  |
| 15-05-2025 | `=blacklist`                                                                             |
| 20-06-2025 | `=infiltrate`                                                                            |
| 26-09-2025 | `=elevate`                                                                               |
| 29-11-2025 | `=tempban`                                                                               |
| 26-06-2026 | `=exportcommands`                                                                        |
| 11-07-2026 | `=case`                                                                                  |
| 12-07-2026 | `=countingleaderboard`; `=counting` and `=raidprotection` made public                    |
| 14-07-2026 | `=syncrole`; `=appealchannel` made public and rebuilt for cross-server use               |
| 18-07-2026 | `=dossier`, `=scopeaudit`, `=daily`, `=tictactoe`, `=tradeout`                           |
| 25-07-2026 | `=sban` (added as `=bansync`), `=skick`, `=smute`, `=sunban`, `=sunmute`, `=syncservers` |
| 02-08-2026 | `=warntiers`, `=swarning`, `=leveltier`, `=respond`                                      |

### New events

| Date       | Events                                                                                                                                                                                                                                                                                            |
|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 23-10-2023 | `playerStart`, `emptyChannel`, `disconnect`, `queueCreate`, `audioTrackAdd`                                                                                                                                                                                                                       |
| 30-10-2023 | `audioTracksAdd`, `emptyQueue`                                                                                                                                                                                                                                                                    |
| 18-05-2025 | `queueDelete`                                                                                                                                                                                                                                                                                     |
| 11-10-2025 | `playerError`                                                                                                                                                                                                                                                                                     |
| 26-06-2026 | `guildUpdate`                                                                                                                                                                                                                                                                                     |
| 28-06-2026 | `autoModerationActionExecution`, `autoModerationRuleCreate`, `autoModerationRuleDelete`, `autoModerationRuleUpdate`, `guildScheduledEventCreate`, `guildScheduledEventDelete`, `guildScheduledEventUpdate`, `stageInstanceCreate`, `stageInstanceDelete`, `stageInstanceUpdate`, `webhooksUpdate` |

## The GA3.0 version

### 01-07-2026 to 12-07-2026: the database redesign and the public source scrub

- **The schema engine** (08-07-2026). Default stripping on write and reconciliation on read, the
  hot and cold guild split into two collections, the id registry, and blueprints for cases, the
  cold guild config, mutes and reaction roles. Schema versioning with explicit upgrade steps for
  the two cases that need one: a field changing type, and a field going away. Adding a field needs
  neither.
- **`scripts/migrate-db.mjs`** (08-07-2026) and **`scripts/repair-db.mjs`** (10-07-2026, dry run
  by default). The repair script merges collections, restores voice configuration, removes
  blank-id and duplicate documents and creates missing unique indexes.
- **The configuration rework** (10-07-2026). `Env.js`, `Config.js`, `Settings.js` and
  `Storage.js`, replacing the single key file with `.env` plus a settings collection.
  `.env.example` is now what a new host copies.
- **The CDN layer** (10-07-2026) and its documentation.
- **Cookie rotation** (11-07-2026) and the yt-dlp extractor.
- **The music vendoring toolchain** (11-07-2026): a lock file pinning the upstream commit, a
  fetcher, an upgrade planner, a validator and a lock bumper, so the vendored code can be brought
  forward without losing the modifications. Documented the same day.
- **Moderation cases** (11-07-2026), the counting game and raid protection promoted out of the
  administrator category, and `=reddit` removed (12-07-2026).
- **The top-level source split** into `Config`, `Core`, `Assets`, `Utils`, `Client`, `Manager`,
  `Bridge` (12-07-2026), with the cross-hosting bridge written against the new configuration
  object.

### June 2026: configuration, hot patching and event coverage

- Four commits of several hundred files each on 20-06, 26-06 and 28-06 covering descriptions,
  formatting and comment coverage across the whole codebase.
- **Hot patching** (27-06-2026) and Relaxy's own card renderer, replacing the builders canvacord
  removed in v6.
- **`=exportcommands`** (26-06-2026), which regenerates the website's command catalogue from the
  live command set rather than from a separately maintained list.
- **`=timestamp`** (28-06-2026), later folded into `=toolkit`.
- **Eleven new events** (28-06-2026): automod and its rules, scheduled events, stage instances and
  webhooks, plus `guildUpdate` two days earlier.
- **A log worker** on the manager (29-06-2026).

### January 2026 to April 2026

- **TypeScript declarations** (12-02-2026). `Relaxy.d.ts` (316 lines) declares the shapes the
  dynamic module loader attaches at runtime: `client.imports`, `client.collections`,
  `client.class`, `client.module` and the module lookup table. `Manager.d.ts` (129 lines) does the
  same for the manager. Neither is imported by anything; both exist so an editor can see the
  shapes. The same commit removed a 1737-line lock file, a stray history file and a 1.8 MB image
  from the repository.
- **`=forum` rebuilt over thirteen commits** (08-04-2026 to 11-04-2026). About thirty
  `markModified()` and `save()` pairs, one per setting, became queued writes through the normal
  writer. Argument handling was rewritten so the channel is cut out of the arguments before the
  option and its value are read. Emoji validation moved to the shared validator, and the rejection
  marker is refused as a forum emoji.
- **A 183-file documentation pass** (18-04-2026), replacing 177 generic Mongoose JSDoc tags with a
  guild-document type backed by a new `types/Server.d.ts`.
- **The Windows launchers were deleted** (18-04-2026): five batch files, the workspace file and
  the dependency snapshot script from May 2025.
- The Steam catalogue fetch was switched off (08-04-2026). The manager had been pulling the whole
  app list shortly after spawn and broadcasting it to every cluster.
- **`=banmessage`** (18-04-2026), replaced by `=ban message` on 16-07-2026.

### July 2025 to November 2025

- **The youtubei SABR extractor** (11-10-2025), four files. YouTube stopped handing back a media
  URL to download: the player response carries a streaming endpoint and a config blob, and the
  client posts to that endpoint to be fed segments, with the server choosing what it sends. The
  extractor deciphers that endpoint and runs an audio-only stream against it. Each request carries
  a proof-of-origin token, minted by downloading YouTube's attestation script and running it in a
  headless DOM with a canvas implementation grafted in, because the script requires one. Two
  tokens are minted per stream, one bound to the video and one to the session, and the stream
  renegotiates them when the server signals that a token has gone stale or been rejected. The
  previous extractor stayed registered behind it until July 2026.
- **A `playerError` event** (11-10-2025), which binds failures attached to a track. Only failures
  attached to the queue had a handler before it. `ready` was renamed to `clientReady` the same
  day.
- **The censor's exemption guard was restored** (26-09-2025), covering censoring being disabled
  for the guild, an exempt author, Relaxy itself, and an author holding Ban Members.
- **A warning's severity is read from its own tier** (09-07-2025), rather than from the member's
  running total, which had made every later warning of any tier read as serious once somebody was
  near the limit.
- **`=rep+` writes to the correct paths** (09-07-2025). It had been saving inventory into
  experience and money into reputation.
- **Re-muting on rejoin anchors to the right field** (21-11-2025). Stored mute records are
  underscore-joined strings carrying the moderator id, the expiry, the duration and the channel,
  and the rejoin check had matched the joiner's id as a substring anywhere in them.
- **The temporary voice channel reaper only strikes a channel** when the fetch failed, errored,
  and nothing in the guild cache still holds it (29-11-2025).
- **`=unban` reports a successful unban correctly** (05-07-2025). The API call resolves to the
  unbanned user, and the result had been tested for falsiness.
- **`=tempban`** (29-11-2025). The expiry rides on the same records mutes use, with a marker
  distinguishing a ban, and the existing mute expiry sweep lifts it.
- **`=elevate`** (26-09-2025), which reads Relaxy's own permissions, creates a role carrying
  exactly those permissions and grants it to the invoker.
- **`=purge` gained content filters** (`embeds`, `links`, `images`) and the ability to target one
  person (09-07-2025 and 26-09-2025), so link spam can be cleared without touching conversation.
- **`messageDelete` names who deleted the message**, read from the audit log (09-07-2025).
- Autorole reports a role it cannot grant because the role outranks Relaxy (29-11-2025). Messages
  in threads are read (27-10-2025). Levelling and heartboards cover stage channels, the censor
  gained a near-duplicate message rule, and heartboard settings moved onto the queued writer
  (26-09-2025).

### May 2025 to June 2025: the second music player and the worker threads

- **Music Player V2**, built beside the running one and swapped in one commit. A `v2/`
  subdirectory appeared inside `src/Client/Modules/MusicPlayer/` on 15-05-2025 holding a fresh
  vendoring, grew for three days, and on 18-05-2025 (223 files) was promoted to the root while the
  2023 layout was removed. The equalizer returned at the new root the next day. At no point was
  the bot without a player.
- **The V2 layout**, a re-vendor against a newer upstream. V1 was the v6-era layout
  (`player-core/`, a worker-based audio node, its own downloader and voice connection). V2 is the
  v7-era one (`fabric/`, `hooks/`, `queue/`, `stream/`, `compat/` adapters,
  `PlayerStreamInterceptor`), which is the layout still in use and the reason
  `scripts/music/music.lock.json` can pin it to upstream 7.2.0. Relaxy's own additions are
  `lrclib/` for lyrics and, later, the yt-dlp extractor.
- **Extractors.** The vendored code carries none of its own. Under `extractors/` V2 kept
  `BaseExtractor` and `ExtractorExecutionContext`, the infrastructure. Resolution comes from npm
  packages, so the extractor set can change without touching the vendored source.
- A `queueDelete` event arrived with it (18-05-2025).
- **The database worker** (18-05-2025). Every write moved off the main thread onto a worker thread
  with a model map.
- **The file handler and its worker** (20-05-2025).
- **A dependency snapshot tool** (18-05-2025), which reads `package.json` and packs the installed
  modules into `assets/blob/relaxy.blob`, which is how dependencies moved between hosts while the
  manifest was untracked.
- **The watchdog split** (17-05-2025): error counting on the client, repair on the manager, which
  is the arrangement still in use.
- **`=blacklist`** (15-05-2025), fleet-wide, replacing the per-tier warning command (18-05-2025).
- **`=infiltrate`** (20-06-2025).
- **`=visusage`** and its Python plotter (10-05-2025), both since removed.
- Sixty-three commits on the utilities module, fifty-five on the client, forty-four on the
  watchdog and thirty-one on the manager console.

### February 2025 to mid-May 2025: the ticket system

- **The ticket system.** The moderator panel went from three buttons to five across three
  state-dependent layouts, dropping `Claim` once a ticket is claimed and collapsing to one row
  once a private chat is open. `Private Chat` opens a private thread named after the ticket with
  the opener and the moderator in it and links back to the staff copy; `Completed` archives that
  thread. `Close with Answer` shows a modal with a free-text answer and an anonymity choice, and
  DMs the reply to the opener credited either to the moderator or to "Anonymous moderator". The
  opener has their own panel with a status button that force-fetches the staff copy past the
  message cache, reports whether anything moved, and shows when the staff copy has been deleted.
  It became public on 22-03-2025.
- **More than one ticket system per guild.** The routing state carried in the embed gained a
  fourth field naming the system's own channel, so the edit panel resolves which system it belongs
  to rather than assuming it sits in that system's channel.
- **Answers longer than one embed are split** on the last newline into continuation fields, and a
  long run starts a second embed.
- **Custom buttons, the edit modal, button placement and named-system lookup** were all rebuilt in
  the same pass (23-02-2025), with the edit panel handling designs that render a single embed
  (22-04-2025).
- **`=jointocreate`** (08-02-2025). A marker channel that spawns `Private <n>` in its category and
  moves the joiner in, swept every 20 seconds by a routine that skips guilds this cluster does not
  own and needs three consecutive failed fetches before deleting anything.
- **`=privatechannel` gained bounds**: bitrate clamped to what the guild's boost tier allows, user
  limit clamped to 0 to 99, a quoted rename that runs through the censor first, and a region from
  a fixed table (01-05-2025).
- **The scam list** (18-04-2025), a newline-delimited file of domains, phrases and bare words
  scored against a suspicion threshold derived from message length, URL count, mentions, emoji and
  embeds. It is the ancestor of today's CDN-hosted list.
- **The InternetConn watchdog** (14-05-2025): a substring classifier over about thirty DNS, TCP,
  TLS and MongoDB failure strings, one IPC message per occurrence, counted per cluster, killing
  the fleet and holding off for 30 minutes (90 if the last attempt was recent) before respawning.
- **The periodic routines stopped querying Mongo every tick** (10-05-2025). Channel clearing,
  reminders, the mute sweep, modlog flushing and welcome messages walk the document caches, which
  are warmed at boot behind a flag the loops check.
- **Event hot reload unregisters the previous listener** (10-05-2025) instead of leaving it
  attached.
- **The error watchdog moved off the command path** (01-05-2025) onto a 10 second interval, so a
  shard with no command traffic is still watched. It became its own module sixteen days later.
- **The elevated-members gate is awaited** (10-05-2025). Its check had been declared `async` and
  compared as a Promise.
- **The manager's hot reloader can be revived** from both the heartbeat timeout and the console
  (07-05-2025), through one method on the manager.
- **Hot reloading survives a recluster** (10-05-2025). The suppression flag is cleared on
  completion.
- **`=disable <category>` saves** (19-04-2025). The loop no longer falls through to resolving the
  category name as a command.
- **`=privatechannel` and `voiceStateUpdate`** were reordered so the private channel handling is
  reached, with an RTC region restore when a channel empties (08-02-2025).

### September 2024 to November 2024: the cluster manager and the module system

- **The source was split into a client and a manager** (20-09-2024, 368 files). The old monolithic
  `src/Core.js` became the client's common routines, `src/Manager.js` became a manager with a
  spawn processor and a console input processor, the rate limiter and the file watcher moved under
  new homes, and every database blueprint moved under the client's database module.
- **The module loader** (20-09-2024), allowing a module to be replaced without restarting the
  process. Before it, every module was a hardcoded construction in the client and manager
  constructors. It re-imports with a random query string appended to the specifier so Node's
  module cache is bypassed, constructs the result, and places it where callers expect it through a
  dotted `altPath`. A sentinel argument attaches a class without constructing it, which is how the
  blueprints that commands instantiate themselves are loaded. Hot reload was wired in both
  directions the same day and became cross-platform in November.
- **The manager console changed grammar** (14-09-2024). Flat underscore verbs (`show_help`,
  `toggle_debug`, `update_status`) became the verb-plus-subcommand form still used today. Day one
  covered `ping`, `recluster`, `restart`, `show`, `delete`, `create`, `toggle` and `update`.
  `eval` arrived at the end of the month; `halt`, `sync`, `show stats`, `show watchdog` and the
  generated per-log-channel toggles are all later.
- **The logging service was removed** (14-09-2024). Four separate sets of log files became one
  write stream six days later, gated to Windows only (01-10-2024). The named log-channel table
  dates to 2026.
- **The three per-entity write queues became one indexed table** (14-09-2024), with the writer
  routed by a numeric argument.
- **The error watchdog respawns a cluster** (30-09-2024) rather than starving its heartbeat, and
  its announcement carries the context it needs to reach the support server.
- **Network failures were split out of the error count** (18-11-2024, three passes in nine hours).
  Connect timeouts, Mongo server selection failures and one TLS error were given their own
  counter, so an outage cannot spend the watchdog's restart budget. This is the ancestor of
  today's single network error classifier.
- **`=ban` and `=kick` expand a token in the reason** into the date plus the moderator's mention
  (30-09-2024). That is where stored reasons of the form `racism 2026/07/29 - <@!...>` come from,
  and therefore why a reason cannot be printed inside a bare code span.
- **Guild documents are serialised in a try/catch** (17-11-2024), so one unserialisable document
  is skipped and logged rather than stopping the whole flush.
- **The boot order was fixed** (04-11-2024). Command loading had been a floating promise fired at
  import time, so the gateway could connect before the handlers were registered. It is now awaited
  between the database connection and login.
- Moderation commands refuse to act on the primary owner (03-11-2024).
- Raspberry Pi support fixes (01-10-2024).
- The two watchdog names were exchanged in May 2025. Until then the manager's `ActiveDiagnosis`
  was the file watcher and the error counting lived in the client's command path; afterwards the
  client held `ActiveDiagnosis` and the manager's file watcher took the `SelfRepair` name.

### January 2024 to August 2024

- **The Linux port** (19-02-2024), and the origin of low-performance mode. Relaxy had been a
  Windows program and the second host was a Raspberry Pi. Low-performance mode arrived as a
  platform check, and off Windows it refused the music category and `=caption`. The heartbeat
  delay warning went from 300 milliseconds on the desktop to 1.5 seconds on the Pi, and `=caption`
  moved `sharp`, `gif-frames` and `gifencoder` to lazy imports.
- **A heartbeat manager copied into the source** (03-02-2024), a copy of the hybrid sharding
  plugin brought into the source so it could be modified: 20 second intervals, a delay warning,
  and a respawn once five acknowledgements are outstanding.
- **`=induce_death`** (18-02-2024), which tests that respawn path on a live cluster by making the
  heartbeat acknowledgement return early.
- **The blacklist**, with the two-tier design it still has (16-02-2024). A file holding `grace`
  and `total`, each with users and servers, returning 0, 1 or 2. Grace warned by DM that a ban was
  coming; enforcement deleted messages and forum posts. What changed in 2026 is where the list
  lives.
- **A logging service** (24-02-2024), three processes deep, communicating over HTTP. A supervisor
  spawned a logger as its own Node process, which forked five subprocesses (main, console, errors,
  guild messages, DMs). Two days later the HTTP transport was switched off in favour of forking
  the subprocesses in-process and forwarding over IPC. Removed in September.
- **`package.json` was cut from about 580 entries to about 60** (26-02-2024), the flattened full
  dependency list having been pinned in `dependencies`, and the version set to 3.0.0.
- **The rate limiter was rebuilt twice in five days** (29-01-2024 and 02-02-2024), gaining a an
  always-increasing queue index, a list of failures in place of a counter, and a wait that scales
  with the live queue length rather than a fixed multiple.
- **`client.delete()` and `client.respond()`** replaced direct `.delete()` calls and five
  hand-written interaction replies (02-02-2024, and 28-10-2023 for the responder).
- **`=purge` gained regex filtering** (29-01-2024), with a choice of delimiters.
- **Modlog writes became optional-chained across about 25 event files** (03-02-2024), so a guild
  whose modlog array had not been initialised no longer stops the rest of the event.
- **The ticket system landed as a prototype** (19-06-2024), 568 lines and owner-only. A modal
  submit rendered its answers into an embed and fanned it out to the configured recipients with a
  close button. It gained permission gates on 24-08-2024 and was rebuilt through early 2025.
- **`=resume` returned** (19-06-2024), rewritten against the hook API.
- **YouTube playback was switched off at the source** (24-08-2024), with `=play` reporting the
  change. `discord-player-youtubei` was added the same day. In the same commit, a track that
  resolves but produces no stream started being skipped, which is the ancestor of today's retry
  path.
- **`=pickrandomreacter`** (17-01-2024, moved to miscellaneous 04-07-2024), **`=sakify`**
  (04-07-2024).
- Heartboards gained a name display mode (08-03-2024).

### October 2023: GA3.0 begins

- **"Prep for 3.0"** (23-10-2023). The first vendored music player lands as `src/MusicV4`: player
  core, queue, history, player node, equalizer with biquad filters and a channel processor,
  extractors for YouTube, SoundCloud, Spotify, Apple Music, Vimeo, Reverbnation, attachments and
  lyrics, an FFmpeg wrapper, an Opus layer, a voice connection and adapter, and a worker-based
  audio node. `client.player` dates from this commit. The music events were renamed to the new
  player's vocabulary at the same time: `trackStart` became `playerStart`, `botDisconnect` became
  `disconnect`, `channelEmpty` became `emptyChannel`.
- The music category was owner-gated for the duration of the rewrite.
- **`client.respond()` and the quick response builder** (28-10-2023), which tests whether the
  destination channel allows embeds and falls back to plain text. Also the synthetic message
  object that lets a button press re-enter its own command's `run`, which is still how component
  handling works.
- **Renamed to `src/MusicPlayer`** (30-10-2023). The vendored code kept the player core,
  equalizer, FFmpeg, Opus, voice and utility layers, and resolution moved to npm packages. The
  seven prototype `n_`-prefixed music commands were removed at the same time, along with
  `=clearq`, `=leave` and `=resume`.
