Skip to main content

Cross-User Data

Leaderboards, ledgers, and any data shared between users must live in shared.*, because user.* is private to one person. This page covers the canonical patterns.

Why shared.*

ScopeVisible to
user.*Only the current user
shared.*Every user of the bot

A leaderboard, a karma ledger, a product catalog, a list of paid members — all of these are read and written by different users, so they belong in shared.*.

shared leaderboard: Array<{ uid: Number, name: String, score: Number }> = []

The upsert pattern

You can't mutate one element of a shared array in place (see the warning below). Instead, rebuild the array: drop the existing entry with array.reject, then append the fresh one with <<.

set {
// Drop any existing entry for this user, then add the updated one.
shared.leaderboard = array.reject(shared.leaderboard, "uid", user.id)
shared.leaderboard << [{ uid: user.id, name: user.first_name, score: user.score }]
}

To preserve existing fields when updating, read the current entry first:

set { flow.me = array.find(shared.ledger, "uid", user.id) }
set { flow.rep = (types.isNil(flow.me) ? 0 : flow.me.rep) + 1 }
set {
shared.ledger = array.reject(shared.ledger, "uid", user.id)
shared.ledger << [{ uid: user.id, name: user.first_name, rep: flow.rep }]
}
warning

No nested in-place array element mutation. You cannot write shared.leaderboard[0].score = 10. Always rebuild the array with array.reject + <<, or rebuild it entirely with a foreach (see below). Whole-array reassignment is fine.

Leaderboards (sort + slice)

Sort a shared array and take the top N:

set { flow.top10 = array.slice(array.sortBy(shared.leaderboard, "score", true), 0, 10) }
set { flow.body = "🏆 Leaderboard\n\n" }
foreach e, i in flow.top10 {
set { flow.pos = i + 1 }
set { flow.body = flow.body + `${flow.pos}. ${e.name} — ${e.score}\n` }
}
send.text(flow.body)

array.sortBy(arr, "score", true) sorts descending (pass true for desc).

Computing a rank

To find where the current user sits, count how many entries beat them:

set { flow.rank = 1 }
foreach e in shared.leaderboard {
when e.score > user.score {
set { flow.rank += 1 }
}
}
send.text(`You're ranked #${flow.rank}`)

Resetting a board (rebuild with foreach)

To zero a field across every entry, build a fresh array (you can't mutate in place):

set { flow.fresh = [] }
foreach e in shared.leaderboard {
set { flow.fresh << [{ uid: e.uid, name: e.name, score: 0 }] }
}
set { shared.leaderboard = flow.fresh }

This is exactly how the karma and quiz blueprints reset weekly/seasonal counters.

Empty states

Always handle the empty board:

when util.len(shared.leaderboard) == 0 {
send.text("🏆 No scores yet — be the first to play!")
} else {
/* render the board */
}

See also