Compare commits

...

1 commit

Author SHA1 Message Date
Claude
f505a580d2
Filter several service accounts, not just Playerbots
Some checks failed
Build / build (push) Has been cancelled
Lint / eslint (push) Has been cancelled
The auction-house bot account (ahouse) owns 9 mule characters that were
showing up in the armory alongside real players. One LIKE pattern could not
cover both it and RNDBOT%.

Replaces botAccountPattern with botAccountPatterns, a comma-separated list
of LIKE patterns, and builds the clause with Utils.botAccountFilter. An empty
list now disables the filter outright rather than emitting a clause that
matches nothing, so misconfiguration fails open instead of hiding everyone.

The stack default becomes "RNDBOT%,ahouse"; config.default.json keeps the
generic "RNDBOT%" so the fork stays upstreamable.

Verified against the live realm: 29 listed before, 20 after. The 9 excluded
are the ahouse mules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NW2ooBP2KPqQVzdZZDMvZd
2026-09-02 21:28:18 +00:00
8 changed files with 48 additions and 13 deletions

View file

@ -10,7 +10,7 @@
"loadDbcs": true, "loadDbcs": true,
"hideGameMasters": true, "hideGameMasters": true,
"hideBotAccounts": true, "hideBotAccounts": true,
"botAccountPattern": "RNDBOT%", "botAccountPatterns": "RNDBOT%",
"transmogModule": false, "transmogModule": false,
"useZamCdn": false, "useZamCdn": false,
"realms": [ "realms": [

View file

@ -48,8 +48,11 @@ services:
# Playerbots are ordinary accounts, not game masters, so the GM # Playerbots are ordinary accounts, not game masters, so the GM
# filter does not touch them. Without this the armory lists ~1350 # filter does not touch them. Without this the armory lists ~1350
# bots against ~29 real characters. # bots against ~29 real characters.
# Comma-separated LIKE patterns. RNDBOT% covers the Playerbots
# accounts; ahouse is the auction-house bot, which owns 9 mule
# characters. Add further service accounts here as they appear.
ACORE_ARMORY_HIDE_BOT_ACCOUNTS: ${ARMORY_HIDE_BOTS:-1} ACORE_ARMORY_HIDE_BOT_ACCOUNTS: ${ARMORY_HIDE_BOTS:-1}
ACORE_ARMORY_BOT_ACCOUNT_PATTERN: ${ARMORY_BOT_PATTERN:-RNDBOT%} ACORE_ARMORY_BOT_ACCOUNT_PATTERNS: ${ARMORY_BOT_PATTERNS:-RNDBOT%,ahouse}
ACORE_ARMORY_TRANSMOG_MODULE: ${ARMORY_TRANSMOG:-0} ACORE_ARMORY_TRANSMOG_MODULE: ${ARMORY_TRANSMOG:-0}
# 0 = serve 3D model assets from the local data directory. # 0 = serve 3D model assets from the local data directory.

View file

@ -133,6 +133,8 @@ FLUSH PRIVILEGES;
this realm belong to real players — `Spinnaker` among them — so hiding them this realm belong to real players — `Spinnaker` among them — so hiding them
would hide people who should be listed. Set `ARMORY_HIDE_GMS=1` to restore would hide people who should be listed. Set `ARMORY_HIDE_GMS=1` to restore
upstream behaviour. upstream behaviour.
- **Playerbots are hidden** via `hideBotAccounts` / `botAccountPattern` - **Service accounts are hidden** via `hideBotAccounts` / `botAccountPatterns`,
(`RNDBOT%`). They are ordinary accounts rather than game masters, so the GM a comma-separated list of LIKE patterns defaulting to `RNDBOT%,ahouse` — the
filter never touched them. `ARMORY_HIDE_BOTS=0` shows them again. Playerbots accounts and the auction-house bot, which owns 9 mule characters.
None of them are game masters, so the GM filter never touched them. Add more
with `ARMORY_BOT_PATTERNS`, or set `ARMORY_HIDE_BOTS=0` to show them all.

View file

@ -32,7 +32,7 @@ export class Config {
public loadDbcs: boolean; public loadDbcs: boolean;
public hideGameMasters: boolean; public hideGameMasters: boolean;
public hideBotAccounts: boolean; public hideBotAccounts: boolean;
public botAccountPattern: string; public botAccountPatterns: string;
public transmogModule: boolean; public transmogModule: boolean;
public useZamCdn: boolean; public useZamCdn: boolean;
public realms: IRealmConfig[]; public realms: IRealmConfig[];

View file

@ -73,4 +73,24 @@ export class Utils {
public static quoteSqlString(value: string): string { public static quoteSqlString(value: string): string {
return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
} }
/**
* Build the LIKE test that identifies non-player accounts.
*
* `patterns` is a comma-separated list, so a realm can name several service
* accounts at once Playerbots plus an auction-house bot, say. Returns an
* empty string when nothing is configured, which callers treat as "do not
* filter" rather than emitting an always-false clause.
*/
public static botAccountFilter(column: string, patterns: string): string {
const list = String(patterns ?? "")
.split(",")
.map((p) => p.trim())
.filter((p) => p.length > 0);
if (list.length === 0) {
return "";
}
return `(${list.map((p) => `${column} LIKE ${Utils.quoteSqlString(p)}`).join(" OR ")})`;
}
} }

View file

@ -347,10 +347,14 @@ export class CharacterController {
// rows we do not want, then require the join to have missed. Keeping the // rows we do not want, then require the join to have missed. Keeping the
// pattern inline rather than bound avoids disturbing the positional // pattern inline rather than bound avoids disturbing the positional
// values below. // values below.
const botJoin = this.armory.config.hideBotAccounts const botFilter = this.armory.config.hideBotAccounts
? `LEFT JOIN \`${realm.authDatabase}\`.\`account\` AS \`bot_account\` ON \`bot_account\`.\`id\` = \`characters\`.\`account\` AND \`bot_account\`.\`username\` LIKE ${Utils.quoteSqlString(this.armory.config.botAccountPattern)}` ? Utils.botAccountFilter("`bot_account`.`username`", this.armory.config.botAccountPatterns)
: ""; : "";
const botWhere = this.armory.config.hideBotAccounts ? "AND `bot_account`.`id` IS NULL" : ""; const botJoin =
botFilter === ""
? ""
: `LEFT JOIN \`${realm.authDatabase}\`.\`account\` AS \`bot_account\` ON \`bot_account\`.\`id\` = \`characters\`.\`account\` AND ${botFilter}`;
const botWhere = botFilter === "" ? "" : "AND `bot_account`.`id` IS NULL";
const [rows] = await this.armory.getCharactersDb(realm.name).query({ const [rows] = await this.armory.getCharactersDb(realm.name).query({
sql: ` sql: `

View file

@ -93,7 +93,10 @@ export class GuildController {
ssp = ssp.where("`account_access`.`id` IS NULL"); ssp = ssp.where("`account_access`.`id` IS NULL");
} }
if (this.armory.config.hideBotAccounts) { const botFilter = this.armory.config.hideBotAccounts
? Utils.botAccountFilter(`\`${realm.authDatabase}\`.\`account\`.\`username\``, this.armory.config.botAccountPatterns)
: "";
if (botFilter !== "") {
ssp.joins.push({ ssp.joins.push({
table1: "characters", table1: "characters",
column1: "account", column1: "account",
@ -101,7 +104,7 @@ export class GuildController {
column2: "id", column2: "id",
database2: realm.authDatabase, database2: realm.authDatabase,
kind: "LEFT", kind: "LEFT",
where: `AND \`${realm.authDatabase}\`.\`account\`.\`username\` LIKE ${Utils.quoteSqlString(this.armory.config.botAccountPattern)}`, where: `AND ${botFilter}`,
}); });
ssp = ssp.where(`\`${realm.authDatabase}\`.\`account\`.\`id\` IS NULL`); ssp = ssp.where(`\`${realm.authDatabase}\`.\`account\`.\`id\` IS NULL`);
} }

View file

@ -55,7 +55,10 @@ export class IndexController {
ssp = ssp.where("`account_access`.`id` IS NULL"); ssp = ssp.where("`account_access`.`id` IS NULL");
} }
if (this.armory.config.hideBotAccounts) { const botFilter = this.armory.config.hideBotAccounts
? Utils.botAccountFilter(`\`${realm.authDatabase}\`.\`account\`.\`username\``, this.armory.config.botAccountPatterns)
: "";
if (botFilter !== "") {
ssp.joins.push({ ssp.joins.push({
table1: "characters", table1: "characters",
column1: "account", column1: "account",
@ -63,7 +66,7 @@ export class IndexController {
column2: "id", column2: "id",
database2: realm.authDatabase, database2: realm.authDatabase,
kind: "LEFT", kind: "LEFT",
where: `AND \`${realm.authDatabase}\`.\`account\`.\`username\` LIKE ${Utils.quoteSqlString(this.armory.config.botAccountPattern)}`, where: `AND ${botFilter}`,
}); });
ssp = ssp.where(`\`${realm.authDatabase}\`.\`account\`.\`id\` IS NULL`); ssp = ssp.where(`\`${realm.authDatabase}\`.\`account\`.\`id\` IS NULL`);
} }