fix(guilds): do not offer linking where linking cannot reach
Found on the live rig, with the shard's guild feature gated to staff: the refusal still read "link your account — this shard shows guild information to linked players". Signing in reaches `logged_in` and linking a game account reaches `player`; `staff` and `admin` are roles an operator grants, and no amount of linking earns them. Inviting someone to do something that changes nothing is worse than plainly saying no. Also drops the host name from the list embed's title. `ctx.site` carries a base URL and no brand name, so naming the deployment there could only ever mean printing its hostname into a title on the shard's own Discord server. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -42,13 +42,22 @@ async function levelFor(actor) {
|
|||||||
return visibility.viewerLevel({ user: { id: actor.userId, role: actor.role } })
|
return visibility.viewerLevel({ user: { id: actor.userId, role: actor.role } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// The nudge §9 answer 5 asks for, and only when it is TRUE. An unlinked caller
|
// The nudge §9 answer 5 asks for, and only when it is TRUE.
|
||||||
// who was told nothing because the shard publishes nothing is not helped by
|
//
|
||||||
// being invited to link; the prompt appears when linking is what would actually
|
// **Linking reaches exactly two rungs and no further.** Signing in gets a caller
|
||||||
// change the answer.
|
// to `logged_in` and linking a game account to `player`; `staff` and `admin` are
|
||||||
|
// roles an operator grants and no amount of linking will earn. So a shard that
|
||||||
|
// gates guilds to staff refuses an unlinked caller WITHOUT the invitation —
|
||||||
|
// telling them to link would be telling them to do something that changes
|
||||||
|
// nothing, which is worse than saying no.
|
||||||
|
//
|
||||||
|
// The live walk found this: gated to `staff`, the refusal still read "this shard
|
||||||
|
// shows guild information to linked players".
|
||||||
|
const LINKING_REACHES = new Set(['logged_in', 'player'])
|
||||||
|
|
||||||
function linkPrompt(actor, audience) {
|
function linkPrompt(actor, audience) {
|
||||||
if (actor.isLinked) return null
|
if (actor.isLinked) return null
|
||||||
if (audience === 'anonymous') return null
|
if (!LINKING_REACHES.has(audience)) return null
|
||||||
return 'Link your account on the site to see more — this shard shows guild information to linked players.'
|
return 'Link your account on the site to see more — this shard shows guild information to linked players.'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +163,10 @@ async function handler({ options, actor }) {
|
|||||||
if (!wanted) {
|
if (!wanted) {
|
||||||
const top = [...rows].sort((a, b) => (b.members || 0) - (a.members || 0)).slice(0, LIST_LIMIT)
|
const top = [...rows].sort((a, b) => (b.members || 0) - (a.members || 0)).slice(0, LIST_LIMIT)
|
||||||
return {
|
return {
|
||||||
title: `Guilds on ${core.baseUrl.replace(/^https?:\/\//, '')}`,
|
// Not "Guilds on <host>": `ctx.site` carries a base URL and no brand name,
|
||||||
|
// so naming the deployment here can only mean printing its hostname into
|
||||||
|
// an embed title, which is noise on a shard's own Discord server.
|
||||||
|
title: 'Guilds on this shard',
|
||||||
fields: top.map((g) => ({
|
fields: top.map((g) => ({
|
||||||
name: g.abbr ? `${g.name} [${g.abbr}]` : g.name,
|
name: g.abbr ? `${g.name} [${g.abbr}]` : g.name,
|
||||||
value: `${g.members || 0} members · ${g.online || 0} online`,
|
value: `${g.members || 0} members · ${g.online || 0} online`,
|
||||||
|
|||||||
@@ -88,6 +88,14 @@ test('an unlinked caller is invited to link — but only when linking would chan
|
|||||||
stub({ audience: 'anonymous', level: 'anonymous' })
|
stub({ audience: 'anonymous', level: 'anonymous' })
|
||||||
const open = await command.handler({ options: {}, actor: anonymous })
|
const open = await command.handler({ options: {}, actor: anonymous })
|
||||||
assert.equal(open.notice, null)
|
assert.equal(open.notice, null)
|
||||||
|
|
||||||
|
// Gated to staff: linking reaches `player` and stops there, so the invitation
|
||||||
|
// would be an instruction to do something that changes nothing. Found on the
|
||||||
|
// live rig, where a staff-gated shard still offered it.
|
||||||
|
stub({ audience: 'staff', level: 'anonymous' })
|
||||||
|
const unreachable = await command.handler({ options: {}, actor: anonymous })
|
||||||
|
assert.match(unreachable.text, /not shown to your account/)
|
||||||
|
assert.equal(unreachable.notice, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('a stale board answers offline rather than reporting what it still holds', async () => {
|
test('a stale board answers offline rather than reporting what it still holds', async () => {
|
||||||
@@ -99,6 +107,7 @@ test('a stale board answers offline rather than reporting what it still holds',
|
|||||||
test('no argument lists the largest guilds', async () => {
|
test('no argument lists the largest guilds', async () => {
|
||||||
stub()
|
stub()
|
||||||
const res = await command.handler({ options: {}, actor: anonymous })
|
const res = await command.handler({ options: {}, actor: anonymous })
|
||||||
|
assert.equal(res.title, 'Guilds on this shard')
|
||||||
assert.equal(res.fields.length, 2)
|
assert.equal(res.fields.length, 2)
|
||||||
assert.match(res.fields[0].name, /Knights of the Codex/)
|
assert.match(res.fields[0].name, /Knights of the Codex/)
|
||||||
assert.match(res.fields[0].value, /12 members · 3 online/)
|
assert.match(res.fields[0].value, /12 members · 3 online/)
|
||||||
|
|||||||
Reference in New Issue
Block a user