676 Commits

Author SHA1 Message Date
github-actions[bot] 969f177108 🆙 Bump version to 4.2.13 [skip ci] v4.2.13 2026-05-21 07:02:08 +00:00
DuckieTM e485c2747c Merge pull request #116 from duckietm/dev
Dev
2026-05-21 09:01:07 +02:00
DuckieTM d99a51899b Merge pull request #115 from simoleo89/fix/modtool-counter-bumps
fix(modtool): bump users_settings counters on every sanction
2026-05-21 07:40:49 +02:00
DuckieTM 29677a19be Merge pull request #114 from simoleo89/feat/modtool-user-info-real-data
feat(modtool): populate lastPurchase / tradeLockExpiry / identityBans
2026-05-21 07:40:34 +02:00
DuckieTM 21ee36e089 Merge pull request #113 from simoleo89/fix/acc-supporttool-rank-pattern
fix(permissions): acc_supporttool incorrectly granted to VIP, denied to Super Mod
2026-05-21 07:40:19 +02:00
simoleo89 4e47dbee16 fix(modtool): bump users_settings counters on every sanction
The User Info panel reads its CFH / Cautions / Bans / Trade locks
counters from `users_settings.cfh_send` / `cfh_warnings` / `cfh_bans`
(via totalBans) / `tradelock_amount`. Historically only `cfh_send`
was ever incremented (by `InsertModToolIssue` on CFH submit), so a
user could accumulate any number of Alert / Mute / Ban / TradeLock
sanctions without the stats reflecting it — every panel showed all
zeros even on accounts with a long sanction history visible in the
modern `sanctions` table.

The two systems aren't going away — `ModToolSanctions` (the modern
one) tracks individual sanction events with probation timestamps,
while the legacy `users_settings.cfh_*` columns are flat counters
the ModTool UI displays. Both need to stay in sync.

Wire them up:

`ModToolManager.bumpUserSettingCounter(userId, column)`
  Static helper, column-whitelisted (`cfh_warnings` / `cfh_bans` /
  `cfh_abusive` / `tradelock_amount`) to keep the dynamic SQL safe.
  Single UPDATE per call; SQL exceptions logged, never thrown.

`ModToolSanctionAlertEvent`, `ModToolSanctionMuteEvent` → bump
  `cfh_warnings`. Mute is a punitive but non-banning action; both it
  and Alert are recorded as a warning on the legacy counter, matching
  what the Cautions stat card represents in the new UI.

`ModToolSanctionBanEvent` → bump `cfh_bans`. The `totalBans` field
  the composer sends ALREADY counts entries in the `bans` table, so
  the wire field reflects reality immediately — this column bump is
  a defensive duplicate so any code that reads `users_settings.cfh_bans`
  directly (e.g. plugin scripts, CMS dashboards) stays in sync.

`ModToolSanctionTradeLockEvent` → bump `tradelock_amount`. Mirrors
  what `AllowTradingCommand` already does for the command-line path.

`ModToolManager.closeTicketAsAbusive` → bump `cfh_abusive` for the
  REPORTER (issue.senderId), not the reported user. The Abusive
  counter measures false reports filed by the user, so it belongs on
  whoever opened the CFH that got closed as abusive.

No client-side changes — counter columns are unchanged, only the
write paths are.
2026-05-20 21:54:07 +02:00
simoleo89 e7ba4d0926 feat(modtool): populate lastPurchase / tradeLockExpiry / identityBans
ModToolUserInfoComposer used to send three trailing fields hardcoded
to empty/zero — the client rendered placeholders for every user, on
every panel open:

  appendString("");  // Trading lock expiry timestamp
  appendString("");  // Last Purchase Timestamp
  appendInt(0);      // Number of account bans

These are useful moderation signals and the data already exists in
the live tables. Wire them up.

Last Purchase
  Query MAX(timestamp) FROM logs_shop_purchases WHERE user_id = ?.
  Returns the most recent purchase epoch. Rendered as yyyy-MM-dd HH:mm.
  Empty when the user has never bought anything (the query returns
  NULL → getInt returns 0 → formatUnixTimestamp emits "").

Trading lock expiry
  Query MAX(trade_locked_until) FROM sanctions WHERE habbo_id = ? AND
  trade_locked_until > <now>. Latest ACTIVE lock only — past entries
  don't count. Same yyyy-MM-dd HH:mm format. Empty when no active
  lock.

Identity related bans
  Count of DISTINCT other user accounts that have a ban entry against
  the same machine_id as the target. Self is excluded since the target's
  own bans already show up in banCount. An empty machine_id (default
  '') short-circuits to 0 so we never match accounts whose machine
  fingerprint was never recorded.

The existing totalBans counter is extracted into a helper alongside
the three new ones — cleaner than the inline try-catch tower it used
to live in, same behaviour.

Format choice yyyy-MM-dd HH:mm matches the timestamp shown elsewhere
in moderation UI; both string fields go through the same formatter so
the empty case stays consistent (empty string, not "1970-01-01...").

No client-side changes needed — ModeratorUserInfoData already parses
both strings and the int, and the React ModToolsUserView already
renders them. They were just always empty before.
2026-05-20 21:32:10 +02:00
simoleo89 67d2f52f64 fix(permissions): acc_supporttool incorrectly granted to VIP, denied to Super Mod
The default permission_definitions seed for acc_supporttool used the
pattern (0, 1, 1, 1, 1, 0, 1) across rank_1..rank_7 — apparently
shifted by two columns:

  * rank_2 (VIP) and rank_3 (X) had ALLOWED. With acc_supporttool=1
    the SecureLoginEvent path sends ModeratorInitMessageEvent on
    login, which makes the React client surface the ModTools toolbar
    button and let the user open room/user info windows. The actual
    sanction endpoints (ModToolSanctionBanEvent, ModToolWarnEvent,
    …) still gate on ACC_SUPPORTTOOL so a VIP cannot actually take
    moderator action — but they can request user info, room info
    and chatlogs they have no business reading.
  * rank_6 (Super Mod) was DISALLOWED, which is obviously not what
    the name says.

Corrected pattern: (0, 0, 0, 1, 1, 1, 1) — Support (4), Moderator
(5), Super Mod (6), Administrator (7). Matches the convention used
by the other staff-only acc_modtool_* keys.

Two changes:
  - Default Database/FullDatabase.sql: fix the seed for fresh
    installs.
  - Database Updates/004_fix_acc_supporttool_rank.sql: idempotent
    UPDATE to realign existing deployments.

Found by user report: a rank-2 (VIP) account on the live retro had
the ModTools button visible in the toolbar after login.
2026-05-20 20:34:37 +02:00
github-actions[bot] 69d770b65e 🆙 Bump version to 4.2.12 [skip ci] v4.2.12 2026-05-20 09:36:00 +00:00
DuckieTM 2492569e16 Merge pull request #112 from duckietm/dev
🆙 Added the missing pet package for the borderID
2026-05-20 11:34:57 +02:00
duckietm 9c215bea6b 🆙 Added the missing pet package for the borderID 2026-05-20 11:34:33 +02:00
github-actions[bot] 7dc3581f8f 🆙 Bump version to 4.2.11 [skip ci] v4.2.11 2026-05-20 06:25:19 +00:00
DuckieTM f38eb32eee Merge pull request #111 from duckietm/dev
Dev
2026-05-20 08:24:20 +02:00
duckietm 222e356ff0 Merge branch 'dev' of https://github.com/duckietm/Arcturus-Morningstar-Extended into dev 2026-05-20 08:23:31 +02:00
duckietm c8022ccc45 Small update 2026-05-20 08:23:22 +02:00
DuckieTM 9579833775 Merge branch 'main' into dev 2026-05-20 08:20:56 +02:00
DuckieTM 87ad289a54 Merge pull request #110 from simoleo89/pr/update-permissions-broadcast
feat(commands): :update_permissions broadcasts refreshed permissions to every online client
2026-05-20 08:15:19 +02:00
DuckieTM fd28af5f69 Merge pull request #109 from simoleo89/pr/user-permissions-composer-extension
feat(messages): extend UserPermissionsComposer with rank metadata + resolved permission map
2026-05-20 08:15:03 +02:00
DuckieTM 99c938b98f Merge pull request #108 from Lorenzune/merge-duckie-main-2026-05-06
Add badge leaderboard API endpoint
2026-05-20 08:00:20 +02:00
simoleo89 82d90418cd feat(commands): :update_permissions broadcasts refreshed UserPermissionsComposer to every online client
`PermissionsManager.reload()` rebuilds the rank table from
`permission_ranks` + `permission_definitions`, but every Habbo
currently online still holds a reference to the OLD `Rank` object
on `HabboInfo.rank`. Server-side `hasPermission()` therefore keeps
returning stale results, and any Nitro client that reads permission
state from the wire keeps gating UI on the map shipped at login
— until a relogin or `:give_rank` forces a per-user refresh.

Extend the existing `UpdatePermissionsCommand` so after `reload()`
it:

1. Iterates the online Habbos via `HabboManager.getOnlineHabbos()`.
2. Re-binds each one's `HabboInfo.rank` to the FRESH `Rank` object
   returned by `PermissionsManager.getRank(currentRankId)`. Falls
   back to rank 1 if the admin deleted the rank from
   `permission_ranks` between sessions, so the user is never left
   with a null `Rank` reference.
3. Sends a fresh `UserPermissionsComposer` to each client.

With the companion composer extension PR also merged, this
broadcasts the rank metadata + resolved permission map runtime —
the Nitro React-side `useHasPermission(key)` / `useUserRank()`
consumers re-render against the freshly-loaded tables without
requiring an F5.

The whisper feedback now reports how many connected users were
refreshed, useful for ops feedback after a large `permission_ranks`
edit.

Defensive null guards on habbo / habboInfo / client survive
transient state during the broadcast (e.g. a user disconnecting
mid-iteration).
2026-05-19 20:20:08 +02:00
simoleo89 8b51be4940 feat(messages): extend UserPermissionsComposer with rank metadata + resolved permission map
Backward-compatible wire extension of `UserPermissionsComposer`
(header 411) that lets Nitro clients display per-deployment rank
info and drive UI gates against the actual `permission_definitions`
table instead of hardcoded SecurityLevel constants.

Wire layout after this change (each trailing block is guarded by
`bytesAvailable` on the client side so older Nitro builds keep
parsing the prefix and stop):

    int     clubLevel
    int     rank.level                          // mapped to securityLevel on the client
    bool    isAmbassador                        // existing ACC_AMBASSADOR flag

    --- new: rank metadata ---
    int     rank.id
    string  rank.name                           // permission_ranks.rank_name
    string  rank.badge
    string  rank.prefix
    string  rank.prefixColor

    --- new: resolved permission map ---
    int     count
    loop:   string permission_key + int value   // 1 = ALLOWED, 2 = ROOM_OWNER

The permission map is the union of:

  * Rank entries whose `PermissionSetting != DISALLOWED` (value 1
    for ALLOWED, 2 for ROOM_OWNER).
  * For every rank-DISALLOWED key, each installed
    `HabboPlugin.hasPermission(habbo, key)` is consulted; if any
    plugin grants the permission, the key lands on the wire with
    value 1 (plugins do not have a ROOM_OWNER concept).

Iterating `rank.getPermissions().keySet()` covers every key in
`permission_definitions` because `PermissionsManager.loadPermissionsNormalized()`
calls `rank.setPermission(key, ...)` for every row of the table —
including DISALLOWED ones. Custom keys a plugin invents that are
not in `permission_definitions` stay invisible (there is no
enumeration API on `HabboPlugin` to discover them); this is a rare
case documented in the class-level Javadoc.

The result is a client-side permission map whose semantics match
exactly what `PermissionsManager.hasPermission(habbo, key)` would
return server-side — including plugin-granted permissions, which
were invisible to the client before.

Performance: at login the loop is O(N keys × P plugins), with
N ≈ 200 (size of permission_definitions) and P typically 1-5.
`HabboPlugin.hasPermission` is O(1) hashset lookups in
real-world implementations. Sub-millisecond at login, and the
composer is only sent at login + `HabboManager.setRank` +
`:update_permissions` broadcast.

Backward compatibility: all new fields are appended in tail
position with `bytesAvailable` guards on the parser side, so:
  * existing Nitro clients keep parsing only the prefix and ignore
    the trailing bytes (no error, no behavior change);
  * new Nitro clients with the matching parser extension expose the
    extra data via `IUserDataSnapshot` snapshot getters and the
    React-side `useUserRank()` / `useHasPermission(key)` /
    `useUserPermissions()` hooks (see companion PRs on
    `duckietm/Nitro_Render_V3` and `duckietm/Nitro-V3`).
2026-05-19 20:18:31 +02:00
duckietm 54259f89bd 🆕 Infostand Borders 2026-05-19 16:57:34 +02:00
Lorenzune 272a9b9f42 Add badge leaderboard API and live schema update 2026-05-19 15:30:47 +02:00
duckietm 9c94402f78 🆙 Small update to the SQL 2026-05-19 11:48:33 +02:00
github-actions[bot] 7271506262 🆙 Bump version to 4.2.10 [skip ci] v4.2.10 2026-05-19 09:42:32 +00:00
DuckieTM 09710fc5d6 Merge pull request #107 from duckietm/dev
SMall fix for CORS
2026-05-19 11:41:32 +02:00
duckietm d958fbc0ab SMall fix for CORS 2026-05-19 11:41:17 +02:00
github-actions[bot] dca405ffb5 🆙 Bump version to 4.2.9 [skip ci] v4.2.9 2026-05-19 08:07:32 +00:00
DuckieTM 4190fa96d4 Merge pull request #106 from duckietm/dev
Dev
2026-05-19 10:06:40 +02:00
duckietm 033faaeab6 🆙 Update Database 2026-05-19 10:04:59 +02:00
DuckieTM 98326e11af Merge pull request #104 from duckietm/main
Main to DEV
2026-05-19 10:03:12 +02:00
github-actions[bot] 0f2666916f 🆙 Bump version to 4.2.8 [skip ci] v4.2.8 2026-05-19 07:58:26 +00:00
DuckieTM 46041eedfe Merge pull request #103 from medievalshell/Dev
feat(furnieditor): split-aware FurniDataManager + JSON5 tolerance
2026-05-19 09:57:34 +02:00
medievalshell e334a3e0ac feat(auth): backward-compatible TTL check on SSO auth_ticket
Pairs with the CMS-side change introducing auth_ticket_expires_at (60s
expiry written on every ticket issuance). Without an emulator-side
verification the column was advisory only — this commit gates every
SELECT that resolves a user by auth_ticket on

    auth_ticket = ?
    AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW())

The NULL branch preserves backward-compatibility: CMS deployments that
do not yet populate the column keep working exactly like before
(every ticket passes the WHERE clause as soon as auth_ticket matches),
and the TTL takes effect automatically the moment a CMS starts writing
the expiry value.

Five SELECTs touched:
- SessionEndpoints.java (cms-issued SSO + remember-token flow)
- HabboManager.loadHabbo (game client login by ticket)
- SecureLoginEvent (legacy handshake path)

DB schema delivered both ways:
- Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql:
  idempotent ALTER, skips if column already present (information_schema
  guard so re-running the bundle is safe).
- Default Database/FullDatabase.sql: column added to the `users` table
  definition for fresh installs.

Bumps the emulator version to 4.2.7.
2026-05-19 00:46:58 +02:00
medievalshell 53b7dba185 feat(furnieditor): split-aware FurniDataManager + JSON5 tolerance
Aligns the :furnidata in-game admin command with the split-aware gamedata
layout shipped by the Nitro V3 client. FurniDataManager now resolves the
furnidata source through three accepted shapes:

- legacy single-file path (filesystem or http URL ending in .json/.json5)
- split-mode directory (URL ending with '/') — walks core/custom/seasonal
  tiers via manifest.json5 files and merges by item id, with later tiers
  overriding earlier ones (same semantics as the client-side loader)
- fallback to furni.editor.asset.base.path when the renderer config is
  missing or contains an unresolved placeholder

Adds a small JSON5 sanitiser (stripJson5) that removes line and block
comments and trailing commas before handing the content to Gson, so both
the renderer config and the split-mode files can be JSON or JSON5
without pulling in a JSON5 dependency. String contents are preserved
verbatim — comment-looking substrings inside strings (e.g. URLs) are
not touched.

Bumps the emulator version to 4.2.6.
2026-05-18 22:00:16 +02:00
github-actions[bot] efb4997bdb 🆙 Bump version to 4.1.16 [skip ci] v4.1.16 2026-05-18 10:57:52 +00:00
DuckieTM 7617f8483e Merge pull request #102 from duckietm/dev
Dev
2026-05-18 12:56:50 +02:00
duckietm 4f9fa9fc93 🆙 Database updated to TuT instalation 2026-05-18 12:56:28 +02:00
DuckieTM d1d8d14bec 🆙 Update AboutCommand 2026-05-16 10:47:06 +02:00
duckietm 1909f6d3c1 🆙 Update DB Updates 2026-05-13 11:39:47 +02:00
github-actions[bot] 8709a72b6e 🆙 Bump version to 4.1.15 [skip ci] v4.1.15 2026-05-12 08:55:48 +00:00
DuckieTM c331da9fbe Merge pull request #101 from duckietm/dev
Dev
2026-05-12 10:54:51 +02:00
duckietm f9a079da02 🆙 comibe SQLs 2026-05-12 09:18:22 +02:00
duckietm 89eb989c26 🆙 Refactor AuthHttpHandler for the API and Websocket 2026-05-12 09:11:43 +02:00
duckietm 47be392d8e 🆕 Added Reset password / Email and chenge username in user settings 2026-05-11 18:06:34 +02:00
duckietm d9465a0a65 🆙 Update Some security updates for guilds 2026-05-08 15:38:14 +02:00
duckietm 90314d00fe 🆙 Fix Guilds removal 2026-05-08 15:19:00 +02:00
duckietm 56c73b9d98 🆙 Small fix for the websocket, some CF users have problems with the max frame size 2026-05-08 08:03:51 +02:00
github-actions[bot] e6093f959f 🆙 Bump version to 4.1.14 [skip ci] v4.1.14 2026-05-06 10:51:57 +00:00
DuckieTM c854770561 Merge pull request #100 from duckietm/dev
Dev
2026-05-06 12:51:02 +02:00