-
Notifications
You must be signed in to change notification settings - Fork 0
Social
Since v0.5.0. steam.social is the curated layer over ISteamFriends: the
local persona, the friend list, avatars, and rich presence. It is called
social because the generated ISteamFriends class already owns
steam.friends.
Nothing here is async. Every read answers from the Steam client's local
cache, so the whole layer is synchronous, and the two things that do take time
(persona data for a stranger, avatar pixels) are handled by asking Steam to
fetch and reading again from a callback. The client only caches persona data
for users it has a reason to know about: friends, people in the same lobby, and
anybody you asked for with
requestUserInformation. For everyone else the reads
answer with an empty string or a zero, not with an error.
The overlay half of the same interface lives on Overlay; clans, chat
and coplay stay on the raw steam.friends. The Social instance is created
lazily and cached on the Steam object. Steam ids are bigint.
personaName(): string
personaState(): numberThe local user's display name, the one other players see, and their
EPersonaState: 0 offline, 1 online, 2 busy, 3 away, 4 snooze, 7 invisible.
listFriends(flags?: number): Friend[]The friend list with names, states and relationships, in Steam's order.
flags is an EFriendFlags bit field and defaults to k_EFriendFlagAll,
which is everything the client knows about, blocked users and clan members
included. Pass k_EFriendFlagImmediate for the plain friend list.
import { init, flat } from 'steamwand.js';
const steam = init({ appId: 480 });
const online = steam.social
.listFriends(flat.EFriendFlags.k_EFriendFlagImmediate)
.filter((f) => f.state !== flat.EPersonaState.k_EPersonaStateOffline);
console.log(online.map((f) => f.name));
steam.close();friendName(steamId: bigint): string
friendState(steamId: bigint): numberOne user's persona name and EPersonaState. An uncached user reads back as an
empty name and offline (0), and offline is indistinguishable from actually
being offline, so call requestUserInformation first
when that matters.
Since v0.6.0.
friendGame(steamId: bigint): FriendGame | nullWhat a user is playing right now, or null when they are in no game or the
client has no data for them.
Steam answers with a CGameID, which packs the app id into its low 24 bits;
appId is that app id, already unpacked. A user on a game server also reports
the server address as a dotted quad and its two ports, and a user in a lobby
reports a lobbyId you can pass straight to lobbies.join. A
single player session reports an empty ip, zero ports and a null lobby.
for (const friend of steam.social.listFriends()) {
const game = steam.social.friendGame(friend.steamId);
if (game) console.log(friend.name, 'is in', game.appId);
}Since v0.6.0.
listFriendsInGame(appId?: number): (Friend & { game: FriendGame })[]The immediate friends who are playing one app right now, in Steam's friend list
order. appId defaults to the running app, which makes this the "who of my
friends is in this game" list an invite UI needs.
Each entry is a Friend with its FriendGame attached, so an invite needs no second call:
for (const friend of steam.social.listFriendsInGame()) {
if (friend.game.lobbyId) await steam.lobbies.join(friend.game.lobbyId);
}Only k_EFriendFlagImmediate friends are considered. Clan members and blocked
users never show up here, whatever they are playing.
Since v0.6.0.
friendLevel(steamId: bigint): number
nickname(steamId: bigint): string | null
hasFriend(steamId: bigint, flags?: number): booleanfriendLevel is a user's Steam level, or 0 while the client has no data for
them. nickname is the private nickname the local user gave that user, or
null when there is none: nicknames are local to this account, nobody else
sees them, and showing one instead of the persona name is what the Steam client
itself does. hasFriend checks the relationship against an EFriendFlags bit
field, defaulting to k_EFriendFlagImmediate, the plain friend list.
const other = 76561197960287930n;
const label = steam.social.nickname(other) ?? steam.social.friendName(other);
console.log(label, steam.social.friendLevel(other), steam.social.hasFriend(other));requestUserInformation(steamId: bigint, nameOnly?: boolean): booleanAsks Steam to fetch persona data for a user who is not a friend. nameOnly
(default false) fetches only the name and the avatar, which is faster.
Returns true when Steam started a fetch and false when the data was already
cached. A false means no callback follows. The data arrives on
onPersonaStateChange; read it back with
friendName or friendState, or with
avatar.
const other = 76561197960287930n;
if (steam.social.requestUserInformation(other, true)) {
steam.social.onPersonaStateChange((e) => {
if (e.steamId === other) console.log(steam.social.friendName(other));
});
}onPersonaStateChange(listener: (change: PersonaStateChange) => void): () => voidSubscribes to persona changes of any user the client tracks, and returns an
unsubscribe function. This fires often and for many users, the local one
included, so filter on steamId before doing work.
avatar(steamId: bigint, size?: AvatarSize): Avatar | nullA user's avatar as raw RGBA pixels. size is 'small' (32x32), 'medium'
(64x64, the default) or 'large' (184x184), but read the reported width and
height rather than assuming them.
Steam hands out an image handle first and loads the pixels afterwards, so the
first call for an uncached user returns null. Subscribe to
AvatarImageLoaded_t with steam.on and call this again from the listener.
For a non-friend, call requestUserInformation first
to start the fetch at all.
const me = steam.steamId();
const off = steam.on('AvatarImageLoaded_t', (e) => {
if (e.m_steamID !== me) return;
const a = steam.social.avatar(me, 'large');
if (a) console.log(a.width, a.height, a.rgba.length);
off();
});Throws Error: steamwand: GetImageSize returned false (...) when Steam refuses
the handle it just handed out.
setRichPresence(key: string, value: string): voidSets one rich presence key on the local user. Friends read it with
getRichPresence, and Steam shows the
value of the status key in the friend list. The connect key is the one
Steam turns into a "Join game" option, which arrives on the other side as
onGameRichPresenceJoinRequested.
Steam allows 20 keys, 64 bytes per key and 256 bytes per value. An empty
value removes the key.
steam.social.setRichPresence('status', 'In the lobby');
steam.social.setRichPresence('connect', '+connect_lobby 109775242724');Throws Error: steamwand: SetRichPresence returned false (...) when a key or
value is too long, or the 20 key limit is reached.
clearRichPresence(): voidRemoves every rich presence key of the local user. Steam has no result for this, so it cannot fail from JavaScript.
getRichPresence(steamId: bigint, key: string): string
listRichPresence(steamId: bigint): Record<string, string>One rich presence key of a user, or all of them at once on a null-prototype
object so a key named toString is safe. Only friends running the same app
have rich presence, and only once the client cached it. Pass the local Steam id
to read back what setRichPresence wrote.
onGameRichPresenceJoinRequested(
listener: (request: RichPresenceJoinRequest) => void,
): () => voidFires when a friend clicks "Join game" while this app is already running. The
connect string is whatever that friend put in their connect key. When the app
is not running, Steam passes the same string on the command line as
+connect <value> instead, so handle both paths.
onGameLobbyJoinRequested(listener: (request: LobbyJoinRequest) => void): () => voidFires when the user accepts a lobby invite while this app is running. Pass
lobbyId straight to lobbies.join.
steam.social.onGameLobbyJoinRequested(async (r) => {
await steam.lobbies.join(r.lobbyId);
});Since v0.6.0.
inviteToGame(steamId: bigint, connectString: string): void
setPlayedWith(steamId: bigint): voidinviteToGame sends a friend an invite into this user's game, shown in their
Steam chat. If they already run the app it arrives as
onGameRichPresenceJoinRequested carrying
this connect string; if not, Steam launches the app and passes the string on
the command line, where
system.launchCommandLine reads
it. The string is capped at 256 UTF-8 bytes.
setPlayedWith puts a user in the local user's "Recently played with" list,
which is where a player goes to add a stranger from the last match as a friend.
Call it once per other player at the end of a session. Steam has no result for
it, so it cannot fail from JavaScript.
const lobbyId = await steam.lobbies.create(flat.ELobbyType.k_ELobbyTypeFriendsOnly, 4);
steam.social.inviteToGame(friendId, `+connect_lobby ${lobbyId}`);
// at the end of the match:
steam.social.setPlayedWith(friendId);Throws Error: steamwand: InviteUserToGame returned false (...) when the
connect string is over its byte cap or the user cannot be invited.
One entry from listFriends.
| Field | Type | Meaning |
|---|---|---|
steamId |
bigint |
Steam id of the friend. |
name |
string |
Persona name, empty while Steam has not cached it. |
state |
number |
EPersonaState. |
relationship |
number |
EFriendRelationship: 0 none, 1 blocked, 3 friend. |
Since v0.6.0. Returned by friendGame, and attached as game to
every entry of listFriendsInGame.
| Field | Type | Meaning |
|---|---|---|
appId |
number |
App id of the game, from the low 24 bits of the CGameID. |
gameId |
bigint |
The full CGameID, which also encodes the id type and the mod id. |
lobbyId |
bigint | null |
Lobby the user is in, ready for steam.lobbies.join, or null for none. |
ip |
string |
Game server address as a dotted quad, empty when there is no server. |
port |
number |
Game server port, 0 when there is no server. |
queryPort |
number |
Game server query port, 0 when there is no server. |
Returned by avatar. AvatarSize is
'small' | 'medium' | 'large'.
| Field | Type | Meaning |
|---|---|---|
width |
number |
Image width in pixels. |
height |
number |
Image height in pixels. |
rgba |
Buffer |
Raw pixels, 4 bytes each in RGBA order, width * height * 4 long. |
| Field | Type | Meaning |
|---|---|---|
steamId |
bigint |
The user whose persona changed. |
changeFlags |
number |
EPersonaChange bit field: which parts changed. |
| Field | Type | Meaning |
|---|---|---|
steamId |
bigint |
The friend who sent the invite. |
connect |
string |
That friend's connect key, max 256 bytes. |
| Field | Type | Meaning |
|---|---|---|
lobbyId |
bigint |
Lobby to pass to steam.lobbies.join. |
steamId |
bigint |
The friend who invited, or a clan id for a group invite. |
| Shape | When |
|---|---|
Error: steamwand: SetRichPresence returned false (invalid handle or argument?) |
A key or value is over its byte cap, or the 20 key limit is reached. |
Error: steamwand: InviteUserToGame returned false (...) |
inviteToGame got a connect string over 256 bytes, or a user Steam will not deliver an invite to. |
Error: steamwand: GetImageSize returned false (...), GetImageRGBA returned false (...)
|
avatar got a handle from Steam that Steam then refused. |
Nothing here throws SteamResultError, because nothing here waits for a call
result.
The rest of ISteamFriends is on the raw generated steam.friends:
- The overlay calls, wrapped separately on Overlay.
- Clans:
GetClanCount,GetClanName,GetClanOfficerCount, and the clan chat calls. - Coplay:
GetCoplayFriendCountandGetFriendCoplayGame. -
SetPersonaName,SetListenForFriendsMessagesand the friend messaging calls. - The friend groups:
GetFriendsGroupCountand its index reads.
Flat API explains the calling convention.
Next: Overlay, the dialog half of the same interface.