Skip to content

Commit ebe03ac

Browse files
ParadauxIOclaude
andcommitted
Migrate legacy b:<FirmName> chestshops to native B:<accountId>
Legacy PlayerBusinesses chestshops addressed firms by name (b:<FirmName>); the native format is B:<base36 Treasury account id>. Migration is lazy, on-interaction, and self-healing: - TreasuryListener.onAccountQuery resolves a legacy firm-name token via BusinessApi.firms().getFirm(...) -> firm default account, alongside the existing native base-36 path, so old shops keep working immediately. - onTransactionMigrateSign rewrites the sign owner line to B:<base36> on the first completed trade (no-op once already native). - SignParseListener skips the player-name regex for B:/b: business tokens so legacy names containing spaces or - . & pass validation and reach the resolver. Firms whose names were altered by the ingest sanitizer won't resolve and are left for owners to recreate (accepted breakage). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ab545a9 commit ebe03ac

2 files changed

Lines changed: 74 additions & 10 deletions

File tree

plugin/src/main/java/com/Acrobot/ChestShop/Listeners/Economy/Plugins/TreasuryListener.java

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.democracycraft.treasury.utils.Idempotency;
3030
import org.bukkit.Bukkit;
3131
import org.bukkit.ChatColor;
32+
import org.bukkit.block.Sign;
3233
import org.bukkit.entity.Player;
3334
import org.bukkit.event.EventHandler;
3435
import org.bukkit.event.EventPriority;
@@ -42,7 +43,6 @@
4243
import java.util.Locale;
4344
import java.util.UUID;
4445
import java.util.logging.Level;
45-
import java.util.regex.Pattern;
4646

4747
/**
4848
* Treasury economy adapter for ChestShop.
@@ -53,8 +53,6 @@ public class TreasuryListener extends EconomyAdapter {
5353
static final long BUSINESS_UUID_MSB = 0xC5B0000000000000L;
5454
static final UUID CHESTSHOP_SYSTEM_UUID = new UUID(0xC5B0FFFFFFFFFFFEL, 0xFFFFFFFFFFFFFFFEL);
5555

56-
private static final Pattern BUSINESS_NAME_PATTERN = Pattern.compile("(?i)^B:[0-9A-Z]+$");
57-
5856
private final TreasuryApi treasury;
5957
private final TaxApi taxApi;
6058
private final int systemAccountId;
@@ -478,27 +476,86 @@ public void onAccountQuery(AccountQueryEvent event) {
478476
}
479477

480478
String name = event.getName();
481-
if (!BUSINESS_NAME_PATTERN.matcher(name).matches()) {
479+
// A business token is anything starting with "B:" — the native, uppercase form
480+
// written by ChestShopSign.businessAccountSignName, or the legacy lowercase "b:"
481+
// form written by the old PlayerBusinesses/PlayerTreasury chestshops. We accept
482+
// both prefixes (and any suffix, incl. firm names with spaces) here; the suffix is
483+
// disambiguated below. Player names can never contain ':' so this never collides.
484+
if (name == null || name.length() < 3 || !name.regionMatches(true, 0, "B:", 0, 2)) {
482485
return;
483486
}
484487

485488
try {
486-
int accountId = Integer.parseInt(name.substring(2), 36);
487-
net.democracycraft.treasury.model.economy.Account treasuryAccount = treasury.getAccountById(accountId);
489+
String token = name.substring(2);
490+
int accountId = -1;
491+
net.democracycraft.treasury.model.economy.Account treasuryAccount = null;
492+
493+
// Native form: the suffix is a base-36 Treasury account id (e.g. B:1A).
494+
try {
495+
accountId = Integer.parseInt(token, 36);
496+
treasuryAccount = treasury.getAccountById(accountId);
497+
} catch (NumberFormatException notBase36) {
498+
// e.g. a legacy firm name containing spaces — fall through to the name lookup.
499+
}
500+
501+
// Legacy migration form: the suffix is an old PlayerBusinesses firm *name*
502+
// (e.g. b:My Shop). Resolve it to the firm's default BUSINESS account so the
503+
// shop keeps working; the physical sign is rewritten to the native form on
504+
// first use (see onTransactionMigrateSign). This is also the fallback when a
505+
// firm name happens to be valid base-36 but doesn't decode to a real account.
506+
if (treasuryAccount == null && businessApi != null) {
507+
net.democracycraft.business.model.Firm firm = businessApi.firms().getFirm(token);
508+
if (firm != null && firm.getDefaultAccountId() != null) {
509+
accountId = firm.getDefaultAccountId();
510+
treasuryAccount = treasury.getAccountById(accountId);
511+
}
512+
}
513+
488514
if (treasuryAccount != null) {
489515
String displayName = treasuryAccount.getDisplayName();
490516
String shortName = ChestShopSign.businessAccountSignName(accountId);
491517
UUID syntheticUuid = toBusinessUuid(accountId);
492518
Account csAccount = new Account(displayName, shortName, syntheticUuid);
493519
event.setAccount(csAccount);
494520
}
495-
} catch (NumberFormatException e) {
496-
// Invalid base-36 number, ignore
497521
} catch (Exception e) {
498522
ChestShop.getBukkitLogger().log(Level.WARNING, "Treasury: Could not resolve business account for " + name, e);
499523
}
500524
}
501525

526+
/**
527+
* Lazily migrates legacy business shop signs to the native account-id format.
528+
*
529+
* <p>The old PlayerBusinesses chestshops addressed a firm by name
530+
* ({@code b:<FirmName>}); the native format is {@code B:<base36 account id>}
531+
* ({@link ChestShopSign#businessAccountSignName(int)}). By the time a shop
532+
* trades, {@link #onAccountQuery} has already resolved the owner account, and
533+
* its short name is the canonical native token. So if the physical sign still
534+
* shows the legacy text, we rewrite the owner line in place. This runs only on
535+
* a completed (non-cancelled) transaction and is a no-op for shops already in
536+
* the native form. Firms whose names were altered during the data migration
537+
* (stripped special characters) won't resolve and are intentionally left for
538+
* their owners to recreate.</p>
539+
*/
540+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
541+
public void onTransactionMigrateSign(TransactionEvent event) {
542+
Sign sign = event.getSign();
543+
Account owner = event.getOwnerAccount();
544+
if (sign == null || owner == null || owner.getUuid() == null || !isBusinessUuid(owner.getUuid())) {
545+
return;
546+
}
547+
548+
String canonical = owner.getShortName();
549+
if (canonical == null || canonical.equals(ChestShopSign.getOwner(sign))) {
550+
return;
551+
}
552+
553+
sign.setLine(ChestShopSign.NAME_LINE, canonical);
554+
sign.update(true);
555+
ChestShop.getBukkitLogger().info("Migrated legacy business shop sign to " + canonical
556+
+ " at " + sign.getLocation());
557+
}
558+
502559
@EventHandler(priority = EventPriority.LOW)
503560
public void onAccountAccess(AccountAccessEvent event) {
504561
if (event.canAccess()) {

plugin/src/main/java/com/Acrobot/ChestShop/Listeners/SignParseListener.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,15 @@ public void onSignValidation(SignValidationEvent event) {
3535
String ownerName = event.getOwner();
3636
String[] lines = event.getLines();
3737

38-
// If the shop owner is not blank (auto-filled) or the admin shop string, we need to validate it
39-
if ((!ChestShopSign.isAdminShop(ownerName)) && (!ownerName.isEmpty())) {
38+
// If the shop owner is not blank (auto-filled), the admin-shop string, or a
39+
// business token, we need to validate it as a player name. Business shops —
40+
// native B:<base36 account id> or legacy b:<FirmName> — are not player names;
41+
// their owner line is resolved via AccountQueryEvent (see TreasuryListener), so
42+
// skip the player-name regex for them. (Native B:<id> previously slipped through
43+
// by masquerading as "player B + id 1A", but a legacy firm name containing a
44+
// space or a - . & character fails that and would never reach the resolver.)
45+
if ((!ChestShopSign.isAdminShop(ownerName)) && (!ownerName.isEmpty())
46+
&& !ownerName.regionMatches(true, 0, "B:", 0, 2)) {
4047

4148
// Prepare regexp patterns
4249
Pattern playernamePattern = Pattern.compile(Properties.VALID_PLAYERNAME_REGEXP); // regexp from config file

0 commit comments

Comments
 (0)