Commit Graph
64 Commits
Author SHA1 Message Date
ancsemi c8c8fbcf3c Release 4.25.0 2026-10-16 12:50:58 -04:00
ancsemi 085563b37c Release 4.24.0 2026-10-15 15:22:01 -04:00
ancsemi 2b93553420 Update better-sqlite3 to 12.11.1, which stops random crashes at exit on Node 26 2026-10-15 12:38:49 -04:00
ancsemi bb5b699dec Release 4.23.0 2026-10-14 01:24:30 -04:00
ancsemi aac3a50849 Release 4.22.0 2026-10-12 14:04:59 -04:00
ancsemi b69bea7fe4 Release 4.21.0 2026-10-10 18:35:37 -04:00
ancsemi d3e8e0184e Remove the unused Inno Setup installer scripts 2026-10-10 18:08:54 -04:00
ancsemi 44a51b2c6e Update music-metadata to 11.16 for two audio file parsing fixes (an MP4 infinite loop and unbounded EBML lengths) 2026-10-09 13:28:21 -04:00
ancsemi 041a21abe6 Release 4.20.0 2026-10-09 13:17:02 -04:00
ancsemi 72fbcfbff3 Update proxy-addr to 2.0.8 for an IP spoofing fix (GHSA-jqcg-44mw-7w3h) 2026-10-06 21:05:03 -04:00
ancsemi 4bd2be4f11 Release 4.19.0 2026-10-06 02:15:30 -04:00
ancsemi 3643487f2c Release 4.18.0 2026-10-04 11:53:34 -04:00
Josh Stevenson-WoodsandJosh Stevenson-Woods e84e562b53 Add manual plugin and theme updates (#5588)
* docs: propose extension release and blocklist contracts

* feat: add manual plugin and theme updates

---------

Co-authored-by: Josh Stevenson-Woods <5860234+jkswoods@users.noreply.github.com>
2026-10-03 15:35:49 -04:00
ancsemi 9001af5425 Release 4.17.0 2026-10-02 11:42:05 -04:00
ancsemi c3777bc779 Security: engine.io 6.6.11 and brace-expansion 2.1.7
engine.io (under socket.io) had a denial of service through a protocol
revision mismatch, reachable by anyone who can open a connection to the
server. brace-expansion (pulled in by archiver's glob matching) had
denial-of-service bugs on crafted patterns. Both updates stay inside the
version ranges Haven already allows; nothing else in the lockfile changed.
2026-10-02 11:40:57 -04:00
ancsemi 5bfcde9b7b Release 4.16.0 2026-10-01 00:20:45 -04:00
ancsemi 768b7930ab Update multer to 2.4.0: an upload aborted partway no longer leaves its partial file behind on disk (Dependabot alert 1) 2026-09-29 18:02:49 -04:00
ancsemi 0713a61eba Release 4.15.0 2026-09-27 21:15:39 -04:00
ancsemi 2da9c91c0c The voice relay installs on demand: an Install the relay button in Large Server Setup downloads it (about 10 MB) into the data folder with plain progress steps, so normal installs and updates never fetch it, Haven updates leave it in place, and it starts without a restart 2026-09-27 13:10:14 -04:00
ancsemi 61a13b1d7f Voice relay (off by default): with the built-in relay on, each person in a call sends once to the server, which passes it on, so calls are no longer capped by everyone's upload. Mic, camera and screen go through it; bots and apps without relay support still connect directly in the same call; calls recover on their own after a server restart and move to direct connections if the relay is turned off. mediasoup is an optional dependency, so an install without it keeps working peer to peer 2026-09-27 03:44:53 -04:00
ancsemi 54601dcd06 Release 4.14.0 2026-09-26 16:41:40 -04:00
ancsemi 0a5d36dd16 Release 4.13.0 2026-09-24 17:54:08 -04:00
ancsemi d6173eb210 Flash games load the Ruffle player from Haven itself: pinned as a dependency instead of the newest build from unpkg, and no page's CSP lists unpkg any more 2026-09-23 23:38:50 -04:00
ancsemi 35cff8914a Release 4.12.0 2026-09-23 23:15:57 -04:00
ancsemi 465733c2af Dependencies: adm-zip 0.6.1, express 4.22.3, body-parser 1.20.8 and qs 6.16.0 for their published security fixes 2026-09-23 20:38:27 -04:00
ancsemi dda4421c68 Release 4.11.0 2026-09-21 22:12:13 -04:00
ancsemi 089039503f Lockfile carries the current version 2026-09-21 21:12:23 -04:00
AmnibroandCursor daefc658f2 Group DM E2E: crypto core, key distribution, and tests (#5499)
* Group DM E2E: crypto core, key distribution, and tests

Implements the design in docs/group-dm-e2e-plan.md far enough to prove the
properties hold. No UI yet — this is the layer everything else sits on.

public/js/e2e-group.js holds the primitives, deliberately free of storage,
sockets and DOM so the same file runs in the browser and under node --test.
Confidentiality comes from an AES-256 epoch key wrapped per member over the
pairwise ECDH secret that already exists; authenticity from an ECDSA P-256
signature over a digest binding channel, epoch, sender and the previous
message. Verification happens before decryption and a rejected message
returns null plaintext, so a forgery cannot be rendered by mistake.

The signature is what makes one shared group key safe: every member can
decrypt, only a key holder can author.

src/socketHandlers/groupE2E.js distributes wrapped blobs the server cannot
open, while enforcing what no client can check for itself — only a member
may publish an epoch, epochs are strictly sequential, a member reads only
their own blob, and an epoch must cover exactly the current membership.
That last rule stops a member publishing an epoch that silently omits
someone, locking them out of a conversation the UI still shows them in.

Two suites, 28 tests, runnable with npm test. The crypto suite is written
as attacks rather than happy paths: forging as another member, re-attributing
a genuine message, tampering, stripping the signature, replaying into another
channel or epoch, reading an epoch you were not given, and unwrapping a key
not wrapped for you. It also asserts scrollback still decrypts across several
rotations, which is the regression a per-message ratchet would have caused,
and that concurrent sends sharing a prev do not false-alarm.

Confirmed the suite is load-bearing by disabling the signature check: exactly
the five attack tests fail and nothing else.

* Group DMs invite instead of auto-joining, and rewraps require a request.

Addresses review on #5499: drop leftover setup.js, consent plus roster
dedup on start-group-dm, and pin rewraps to a prior request plus the
recipient's TOFU public key.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 22:00:38 -04:00
ancsemi 91fb5a8e0b Clear 12 dependency advisories, 7 of them high
npm audit was reporting 12, and two of the high ones sit directly under the
chat transport rather than off in tooling: ws had an uninitialised memory
disclosure and a memory-exhaustion denial of service, and socket.io-parser
could be made to exhaust memory through binary attachments. Every Haven message
travels through both.

ws 8.18.3 to 8.21.3, socket.io-parser 4.2.5 to 4.2.7, axios 1.13.5 to 1.20.0,
qs 6.14.2 to 6.15.3, body-parser 1.20.4 to 1.20.6. Audit reports none left.

package.json is untouched. Every fix landed inside the semver ranges already
declared, so this is a lockfile change and nothing was upgraded across a major.
Ran with --ignore-scripts so no native rebuild was attempted; better-sqlite3's
compiled binary is the same one as before and still loads.

Verified after: express and socket.io both load in a fresh process, no unmet
dependencies, the suite passes 91 of 91, and a server boots and answers a
socket.io handshake.
2026-08-30 14:17:28 -04:00
ancsemi 6f080487bc Release 4.1.0 2026-08-30 00:43:14 -04:00
Bernardo Costa 3b14588bae Add dynamic bot voice audio (#5540)
* Add dynamic bot voice audio

* Close bot audio review gaps
2026-08-29 23:43:44 -04:00
ancsemi 65bb4bac3c Sync the lockfile version to 4.0.0
package-lock.json still carried 3.50.0 in both of its version fields. The
release steps bump package.json and the lockfile was left behind, so npm
rewrites those two lines on the next install and leaves the tree looking dirty
for no reason.
2026-08-29 21:44:09 -04:00
ancsemi 7b7c47f669 Add Ferry, a two-way Haven and Discord message bridge (server side)
Each Haven server brings its own Discord bot, since Discord caps unverified
applications at 100 guilds. Reading uses a gateway WebSocket, because Discord
has no outgoing webhook for new messages. Writing uses a per-pairing Discord
channel webhook rather than the bot user, which is the only way a relayed
message can carry the Haven author's own name and avatar.

Pairings live in ferry_links and act as the allowlist, not just a routing
table: a member can only reach Discord channels an admin paired with the Haven
channel they are standing in. Each pairing picks its own direction (two way, or
either one way) and its own outgoing mode (mirror everything, or only messages
addressed with the "=>" prefix). Sending needs the new use_ferry permission.

The trigger is "=>" and not ">>" because a leading ">" is Haven's blockquote
marker, so an unresolved target would render as a quote instead of visibly
failing. Resolved targets are stripped from the stored message and recorded in
messages.ferry_target so history shows a destination badge instead of raw
routing syntax.

Loop prevention drops the gateway echo of our own webhook ids and, by default,
every other bot. Mentions are blocked by default so one member cannot ping a
whole Discord server through the bridge. DMs are off by default, outbound only,
and stamped with the author's name, since a bot cannot impersonate in a DM.

ws becomes a direct dependency; it was only reaching us through socket.io.
2026-08-27 15:17:17 -04:00
Amnibro e735ac80e5 Braid Layout: restore Join / Create a Channel, and fix the dead Encryption entry (#5497)
Reported by birdycrazy: the layout "seems to completely remove the ability
to join or create channels", and the Encryption menu entry does nothing.

Both are real. Join and Create were not merely collapsed — they could not
be reopened at all.

_collapseJoinCreate() added its own `braid-collapsed` class to the section
alongside the stock `.collapsed` class on the body, and the stylesheet hid
`.braid-collapsed .collapsible-section-body` with !important. Nothing ever
removed that class, so the stock toggle went on flipping `.collapsed`
underneath while the body stayed hidden. Traced live: clicking the header
moves the stock class exactly as it should, computed display stays `none`,
and dropping `braid-collapsed` by hand returns it to `block`. That left an
admin unable to create a channel and everyone unable to join one for as
long as the layout was on.

Visibility now comes only from the stock class, which the collapse helper
still sets, so the sections keep opening collapsed and the header reopens
them.

The Encryption entry proxied a click to #e2e-menu-btn, whose handler only
toggles #e2e-dropdown. That dropdown lives inside #e2e-menu-wrapper, which
the app sets to display:none outside a DM — so in a normal channel the
entry appeared and did nothing whatsoever. Inside a DM it fared no better:
this layout hides the header chrome the dropdown anchors to, and the click
that opened it kept bubbling to the document handler that closes it again.

Proxy the three actions instead — Verify encryption, Recover keys, Reset
keys — each of which opens its own modal and does not depend on the
dropdown. The menu is built once, so they are marked and resolved when it
opens: shown where encryption applies, hidden everywhere else.
2026-08-15 09:51:09 -04:00
ancsemi de30620bfe 3.41.0 2026-08-05 01:10:23 -04:00
Anupam Mediratta 4b51a69b51 Upgrade adm-zip to 0.6.0 (CVE-2026-39244) (#5473)
Automated dependency upgrade by OrbisAI Security
2026-08-05 00:22:37 -04:00
ancsemi 358b7314b8 Raise the Node ceiling to 26, the version this repo actually runs on (#5471)
#5471 correctly widened the range from <24 to <25, but the source machine and
the Haven servers here run Node 26.4, so the launcher would still print a
"very new Node" warning and package.json would still declare the running
runtime unsupported.

The functional better-sqlite3 load check that #5471 added is the real gate, so
the version number only needs to stop being wrong. Ceiling is now <27 and the
launcher's guidance points at Node 26.
2026-08-02 20:56:46 -04:00
Amnibro f233849c08 Fix silent crash on launch (swallowed bind errors), allow Node 24, and add opt-in Braid themes + layout plugin (#5471)
* Fail loudly when the server port cannot be bound, and stop refusing Node 24

A failed bind (EADDRINUSE/EACCES/EADDRNOTAVAIL) surfaces as an async
'error' event on the HTTP server. With no listener there, it fell
through to the global uncaughtException keep-alive, which logged it to
crash.log and kept a process alive that never got its socket: no
banner, no exit code, and Start Haven.bat's netstat readiness probe
was satisfied by the stale port-holder — so launches died silently.
Reproduced on 3.39.0 by double-launching; %APPDATA%\Haven\crash.log
shows the same swallowed EADDRINUSE in the wild.

- server.js: bind failures on the main listener are now fatal and
  explain themselves (which port, why, what to do) before exit 1.
  Other server errors keep the existing keep-alive behaviour.
- server.js: the HTTP->HTTPS redirect listener (port+1) warns and
  continues instead of feeding the same silent uncaught path.
- Start Haven.bat: the hard Node >=24 refusal is gone — Node 24 is
  current LTS and better-sqlite3 12.10.0 ships prebuilds for it
  (verified working). The honest gate is a functional
  require('better-sqlite3') check after npm install; >=25 warns.
- package.json/lock: engines widened to >=18 <25 (lock version also
  synced from stale 3.31.2).

* Add Braid themes (dark/light) and the Braid Layout plugin

Ports the Braid look from the Amni-Haven mobile app to the web and
desktop clients, as pure opt-ins riding the existing rails — no core
file changes:

- themes/braid.theme.css / braid-light.theme.css: the mobile BRAID /
  BRAID_LIGHT palettes (mint on slate / deep green on white) with the
  1.6x-rounder shape language. Unpublished by default; admins opt in
  via Settings -> Custom Themes, exactly like any file theme.
- plugins/BraidLayout.plugin.js: the 'vastly simplified' two-edge
  layout — folds the server rail (including manage/sync) into a strip
  atop the sidebar, tucks header extras into a kebab menu, and uses
  the full width for messages. Every stock feature stays reachable:
  the kebab proxies search/pins/gallery/channel code/encryption/
  settings and adds People, Soundboard, Status bar, and app-download
  entries for the chrome it hides. A separate variable-driven shape
  layer applies the Braid format (radii, blur, focus rings) on top of
  ANY active theme, so all built-in themes reskin the Braid layout
  with their own palette. start()/stop() fully applies and fully
  restores (DOM, styles, listeners, localStorage); the
  MutationObserver is childList-only and only mutates
  styles/attributes, so it cannot observe its own writes.

All sizes are rem so the interface Zoom slider scales Braid chrome
together with the rest of the UI. prefers-reduced-motion disables the
motion layer outright.

* Braid Layout: mobile-parity chat cards and condensed chrome

Feedback round: the flat message list didn't carry the mobile Braid
shape language, and leftover strips still read as four-edge chrome.

- Merged run cards (desktop twin of Haven-Mobile's braidForm): a run of
  same-author posts becomes one bordered card on .message-body, dashed
  seams between posts, avatar and hover-timestamp in the gutter, own
  messages tinted with the accent. Run position comes from O(n)
  data-braid-run marking in the existing observer batch — NOT
  :has(+ .message-compact), which makes Chromium re-run invalidation
  per sibling insert and sent a 600-message channel load quadratic.
  Channel list runs are carded the same way.
- Condensed chrome: the topic bar folds into the header band as a
  quiet subtitle (no second border strip), header drops to 3rem,
  sidebar footer and composer tighten, media buttons shrink to 2rem.
- All gutter math is rem (.625 pad + 2.25 avatar + .75 gap = 3.625rem)
  so cards and compacts stay aligned under interface zoom.

* Braid Layout v1.2: Mod Mode interop, themed icons, modern settings, subtler seams

- Mod Mode (the layout editor) is now fully functional with Braid on:
  the layer suspends itself while body.mod-mode-on is set — gates come
  off and the server rail unfolds so every draggable section and panel
  handle is real — and re-applies when editing ends. A saved custom
  section order wins over Braid's default join/create-at-bottom
  ordering (the order rules load only when no haven-layout is stored).
  'Edit layout' joins the kebab menu.
- The bottom-left emoji buttons (theme/activities/members/settings/
  donate/mute/deafen) become currentColor line icons so they follow the
  palette like the rest of the chrome; originals restore on disable.
- Modern settings: nav rail becomes grouped pills with an accent-tinted
  active state, each settings section becomes a rounded card (the
  inline border-top hairlines between sections retire), header gets the
  blur band + pill tabs treatment.
- The seam between same-author posts shrinks to a centered dotted
  hairline (min(7rem,45%)) instead of a full-width dashed rule.

* Braid Layout v1.3: true author-run merging, hamburger menu, text sliders, hex loader, logo glow

- Message merging now chains on AUTHOR: consecutive posts by the same
  user merge into one card even when the app rendered them as separate
  full .message elements (persona changes, break_chain, and system
  notices still split). Continuing messages hide their avatar/header
  and surface their own timestamp in the gutter on hover
  (data-time-short). The visible full-width divider is gone — bodies
  inside a run no longer draw internal horizontal borders, so the only
  separator is the small centered dotted seam.
- One animated hamburger (morphs to an X) replaces both the header
  kebab and the bottom-left icon bar: grouped Channel / View / App menu
  with staggered item animation. The menu mounts on <body> — the
  blurred header is a containing block with overflow:hidden that clips
  even position:fixed descendants. Every absorbed feature keeps its
  door (themes, activities, members, settings, donate, apps, Mod Mode).
- Braid text size: two continuous sliders in Settings (chat text
  80-160%, interface text 85-140%) layered over rem zoom, live-applied
  via CSS vars and persisted per user.
- The brand hexagon gets an accent glow that breathes (and a slow spin
  on hover); a hexagonal loading indicator appears on channel switches
  and reskins the setup wizard's circular spinner. All animations
  respect prefers-reduced-motion.
- Braid / Braid Light themes: type & color enforcement pass — the font
  stack and palette land over style.css's per-element declarations
  (fonts on all controls, selection/placeholder/link/timestamp colors),
  and the own-message tint calms from 12% to 8% accent.

* Braid Layout v1.4: hamburger moves home to the bottom-left, settings nav gets real pills and themed icons

- The hamburger relocates to the bottom-left corner where the icon bar
  it absorbed used to live — the popups it opens (themes, activities)
  anchor down there, so the menu and its children now share a corner
  instead of spawning across the screen from the button. It is styled
  as a Haven tile (bordered, bg-tertiary, accent hover) with a Menu
  label, and the grouped menu rises from the bottom-left with the same
  morph-to-X and stagger.
- Settings nav highlights now encompass their rows: stock uses a
  hanging-indent hack (padding-left 1.625rem + text-indent -16px) so
  wrapped labels clear the leading emoji, which pulled the first line
  left out of the highlight pill. Items are now full-width flex pills
  with text-indent reset — and every leading emoji (nav rows and the
  User/Admin tabs) becomes a currentColor line glyph keyed by
  data-target, same treatment as the old bottom-left buttons, with a
  Haven hexagon fallback for unmapped rows. Originals restore on
  disable.
2026-08-02 20:56:15 -04:00
ancsemi a5b11c03ad Release v3.31.2 2026-07-10 23:38:27 -04:00
ancsemi e3f3d91425 Release v3.31.1 2026-07-10 23:32:31 -04:00
ancsemi b92ddedf0b Stream backup restore + lift upload caps so large restores work (#5436)
Restoring a full backup that included files failed at ~30GB, and even a
paired-down ~15GB backup died after about 30 seconds with "failed to fetch".
Three separate limits were in the way:

- server.requestTimeout was 30s, so any upload that took longer than that was
  aborted mid-transfer (this is the ~30s "failed to fetch"). Raised to 1h; the
  15s headersTimeout still guards against slowloris, and the restore handler
  now also clears its own socket inactivity timeout for the long upload +
  staging phase.
- The restore upload was capped at 4GB by multer, so a 15GB+ body was rejected
  part-way and reset the connection. Raised well past realistic backup sizes.
- Restore read the whole uploaded zip into memory via adm-zip (getData() per
  entry), which OOM'd on a large archive. It now streams every entry to disk
  with yauzl, so memory stays flat regardless of backup size.

Verified with an archiver -> yauzl round-trip: a full backup restores
byte-for-byte, a partial backup is rejected (400), and a crafted ../ entry
cannot write outside the staging directory. adm-zip stays as a dep for the
Discord importer.
2026-07-10 23:31:30 -04:00
ancsemi 73cadff537 Release v3.31.0 2026-07-10 16:50:38 -04:00
ancsemi 14baa20ea5 Stream large backups to disk instead of buffering in memory (#5434)
Backups that include files failed and crashed the server once the uploads
directory got large (reporter hit it around 30GB). buildBackupBuffer read
every file into an adm-zip object and then called zip.toBuffer(), holding
the entire archive in RAM at once. That both exceeded Node's Buffer size
limit (RangeError) and exhausted the heap (OOM), so the process died.
Structure-only backups (~300KB) were unaffected, matching the report.

Replace the in-memory build with a streaming archiver(zip) pipeline that
writes each entry straight to a file on disk, keeping memory bounded no
matter how big the uploads are:

- buildBackupBuffer -> buildBackupFile(include, outPath): streams to disk,
  returns a Promise. The temp VACUUM'd DB clone is unlinked only after the
  archive stream closes.
- Download endpoint builds to a temp file then res.download()s it (no more
  res.send of a multi-GB buffer), cleaning the temp file afterward.
- Auto-backup streams to a .partial file then renames it into place, so a
  crash mid-write can't leave a truncated .zip. Prune/list now skip dotfiles.

Output is a standard zip; the existing restore path reads it back
byte-for-byte (verified via SHA-256 round-trip through the adm-zip reader).
2026-07-09 23:47:21 -04:00
ancsemi 9198c8c3ce chore: upgrade better-sqlite3 to v12.10.0, support Node.js 22-26
v12.10.0 ships prebuilts for Node.js 26 (current) and drops EOL v20/v23.
Updated version guard from 18-22 to 22-26 to match.
2026-06-13 13:00:37 -04:00
ancsemi 45ddb27d84 v2.9.5: License changed to AGPL-3.0
- Replaced custom MIT-NC license with GNU Affero General Public License v3
- Haven is now truly open source under an OSI-approved license
- AGPL copyleft ensures forks deployed as network services must release source
- Updated all license references across README, package.json, docs, website, and TOS
- Fixed bot/webhook pinned messages showing raw username instead of [BOT] prefix
2026-04-06 22:21:47 -04:00
ancsemi b1fa8c5aac Follow-up: make voice panel hiding and sidebar controls opt-in
- Voice panel visible on desktop by default (add 'Hide Voice Panel' setting)
- Mute/deafen buttons in voice panel header by default (add 'Sidebar Voice Controls' setting)
- Add header mute/deafen buttons back alongside sidebar buttons
- Sync both button sets via _syncMuteDeafenButtons helper
- Restore _renderVoiceUsers call in voice-users-update (respects setting)
- Restore voice-user-item.talking CSS + talking indicators for voice panel
- Deduplicate muted/deafened strikethrough CSS into shared rules
- Remove broken tlsClientError redirect (localAddress != hostname)
- Keep all PR #188 features: mute/deafen sync, graceful shutdown, deafen-implies-mute
2026-03-21 20:07:59 -04:00
anmire 544824d9a7 feat: One-click installer, FCM mobile push notifications, push relay (#109)
Contributed by @anmire.

- One-Click Installer: Windows (.bat bootstrap + PowerShell WPF wizard), Linux/macOS (bash with package-manager auto-detect), web-based setup UI (installer/server.js + installer/index.html), Inno Setup script for Windows .exe
- FCM Mobile Push Notifications: src/fcm.js module supporting direct mode (Firebase service account), custom relay mode, or automatic Haven Global Relay fallback. No new runtime dependencies (uses existing jsonwebtoken). Hooks into sendPushNotifications alongside existing web-push.
- Push Relay: haven-push-relay/ — standalone Express relay server + Firebase Cloud Function for self-hosted relay deployments
2026-03-01 21:27:58 -05:00
ancsemi 884fbb58fd Add TOTP two-factor authentication (Issue #58)
- New dependencies: otpauth, qrcode
- Database migrations: totp_secret, totp_enabled columns + totp_backup_codes table
- Server endpoints: /totp/setup, /totp/verify-setup, /totp/validate, /totp/disable, /totp/status, /totp/regenerate-backup
- Modified login flow: returns challengeToken when TOTP enabled, validated in second step
- Auth page: TOTP code input form with backup code toggle
- App settings: Two-Factor section with enable/disable, QR setup, backup code management
- Backup codes: 8 one-time-use codes (XXXX-XXXX format), SHA-256 hashed in DB
- Admin recovery intentionally bypasses TOTP as last-resort access
2026-03-01 01:43:05 -05:00
ancsemi 674fc26671 v2.3.4 — Fix duplicate effect sliders, hover popup close, channel roles in profile, donor tier styling, voice right-click 2026-02-26 14:36:46 -05:00
ancsemi 417700d3e9 release: v2.3.2 sound manager, kick revoke, role auto-assign fix, font size fixes 2026-02-25 15:52:30 -05:00