Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,21 @@
"script": "sha256:d58629b152ad3471e27dfb4cbe33b84d74f426a7ff330754aacdc1ec89f72f6c",
"metadata": "sha256:d3ff5230a6ebe6ff3f655546d834e7ed59f741febdc540dd2f89894b6e5baa7a"
}
},
{
"id": "steam-playwright",
"company": "valve",
"version": "1.0.0",
"name": "Steam",
"description": "Exports your Steam profile, game library with playtime stats, and friend list using the Steam Web API.",
"files": {
"script": "valve/steam-playwright.js",
"metadata": "valve/steam-playwright.json"
},
"checksums": {
"script": "sha256:3c4b768fee81c9347ae6f8da714ce7fbabd2bb16918992a2e3e34db20c49acfc",
"metadata": "sha256:f2cabb1d26193df8c12207ebdcd6e668c05cfc14a112264a73ac3f88fb5e2bec"
}
}
]
}
20 changes: 20 additions & 0 deletions valve/schemas/steam.friends.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Steam Friends",
"scope": "steam.friends",
"description": "Steam friend list with persona names, avatars, and friendship dates",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"steamId": { "type": "string", "description": "Friend's 64-bit Steam ID" },
"personaName": { "type": ["string", "null"], "description": "Friend's display name" },
"avatarUrl": { "type": ["string", "null"], "format": "uri", "description": "URL to the friend's avatar image" },
"profileUrl": { "type": ["string", "null"], "format": "uri", "description": "URL to the friend's Steam Community profile" },
"friendSince": { "type": ["string", "null"], "format": "date-time", "description": "When the friendship was established" },
"relationship": { "type": "string", "description": "Relationship type (usually 'friend')" }
},
"required": ["steamId"]
}
}
}
42 changes: 42 additions & 0 deletions valve/schemas/steam.games.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "Steam Games",
"scope": "steam.games",
"description": "Owned games with playtime statistics and recently played games",
"schema": {
"type": "object",
"properties": {
"owned": {
"type": "array",
"description": "All games owned by the user, sorted by playtime descending",
"items": {
"type": "object",
"properties": {
"appId": { "type": "integer", "description": "Steam application ID" },
"name": { "type": "string", "description": "Game title" },
"playtimeMinutes": { "type": "integer", "description": "Total playtime in minutes" },
"playtimeHours": { "type": "number", "description": "Total playtime in hours (rounded to 1 decimal)" },
"playtimeTwoWeeksMinutes": { "type": "integer", "description": "Playtime in the last two weeks in minutes" },
"iconUrl": { "type": ["string", "null"], "format": "uri", "description": "URL to the game's icon image" },
"lastPlayed": { "type": ["string", "null"], "format": "date-time", "description": "When the game was last played" }
},
"required": ["appId", "name", "playtimeMinutes"]
}
},
"recentlyPlayed": {
"type": "array",
"description": "Games played in the last two weeks",
"items": {
"type": "object",
"properties": {
"appId": { "type": "integer", "description": "Steam application ID" },
"name": { "type": "string", "description": "Game title" },
"playtimeTwoWeeksMinutes": { "type": "integer", "description": "Playtime in last two weeks in minutes" },
"playtimeForeverMinutes": { "type": "integer", "description": "Total playtime in minutes" }
},
"required": ["appId", "name"]
}
}
},
"required": ["owned", "recentlyPlayed"]
}
}
24 changes: 24 additions & 0 deletions valve/schemas/steam.profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "Steam Profile",
"scope": "steam.profile",
"description": "Steam user profile including persona name, avatar, level, location, and account metadata",
"schema": {
"type": "object",
"properties": {
"steamId": { "type": "string", "description": "64-bit Steam ID" },
"personaName": { "type": "string", "description": "Display name on Steam" },
"profileUrl": { "type": "string", "format": "uri", "description": "URL to the user's Steam Community profile" },
"avatarUrl": { "type": "string", "format": "uri", "description": "URL to the user's full-size avatar image" },
"realName": { "type": ["string", "null"], "description": "Real name if set by the user" },
"country": { "type": ["string", "null"], "description": "ISO 3166-1 alpha-2 country code" },
"state": { "type": ["string", "null"], "description": "State/region code" },
"cityId": { "type": ["integer", "null"], "description": "Steam internal city identifier" },
"steamLevel": { "type": ["integer", "null"], "description": "Steam profile level" },
"accountCreated": { "type": ["string", "null"], "format": "date-time", "description": "When the Steam account was created" },
"lastLogoff": { "type": ["string", "null"], "format": "date-time", "description": "Last time the user was seen online" },
"personaState": { "type": "integer", "description": "Online status (0=offline, 1=online, 2=busy, 3=away, 4=snooze, 5=looking to trade, 6=looking to play)" },
"communityVisibilityState": { "type": "integer", "description": "Profile visibility (1=private, 3=public)" }
},
"required": ["steamId", "personaName", "profileUrl"]
}
}
174 changes: 174 additions & 0 deletions valve/steam-playwright.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@

(async () => {
const STEAM_API = 'https://api.steampowered.com';

// ─── Auth: API key + Steam ID ─────────────────────────────
let apiKey = process.env.STEAM_API_KEY;
let steamId = process.env.STEAM_ID;

if (!apiKey || !steamId) {
const creds = await page.requestInput({
message: 'Enter your Steam Web API key and Steam ID.\n\nGet your API key at: https://steamcommunity.com/dev/apikey\nFind your Steam ID at: https://steamid.io',
schema: {
type: 'object',
properties: {
apiKey: { type: 'string', title: 'Steam Web API Key' },
steamId: { type: 'string', title: 'Steam ID (76561...)' },
},
required: ['apiKey', 'steamId'],
},
});
apiKey = creds.apiKey;
steamId = creds.steamId;
}

await page.closeBrowser();

// ─── Helpers ──────────────────────────────────────────────
async function steamGet(iface, method, version, params) {
const qs = new URLSearchParams({ key: apiKey, steamid: steamId, ...params });
const url = STEAM_API + '/' + iface + '/' + method + '/' + version + '/?' + qs.toString();
const resp = await page.httpFetch(url);
if (!resp.ok) return { _error: 'HTTP ' + resp.status };
return resp.json;
}

// ─── Step 1: Profile ─────────────────────────────────────
await page.setProgress({ phase: { step: 1, total: 4, label: 'Fetching profile' }, message: 'Getting player summary...' });

const profileResp = await steamGet('ISteamUser', 'GetPlayerSummaries', 'v2', { steamids: steamId });
if (profileResp._error) {
await page.setData('error', 'Failed to fetch profile: ' + profileResp._error + '. Check your API key and Steam ID.');
return;
}

const players = (profileResp.response && profileResp.response.players) || [];
if (players.length === 0) {
await page.setData('error', 'No player found for Steam ID ' + steamId + '. Check your Steam ID.');
return;
}

const player = players[0];

const levelResp = await steamGet('IPlayerService', 'GetSteamLevel', 'v1', {});
const steamLevel = (levelResp.response && levelResp.response.player_level) || null;

const profile = {
steamId: player.steamid,
personaName: player.personaname,
profileUrl: player.profileurl,
avatarUrl: player.avatarfull || player.avatar,
realName: player.realname || null,
country: player.loccountrycode || null,
state: player.locstatecode || null,
cityId: player.loccityid || null,
steamLevel: steamLevel,
accountCreated: player.timecreated ? new Date(player.timecreated * 1000).toISOString() : null,
lastLogoff: player.lastlogoff ? new Date(player.lastlogoff * 1000).toISOString() : null,
personaState: player.personastate,
communityVisibilityState: player.communityvisibilitystate,
};

// ─── Step 2: Owned games ─────────────────────────────────
await page.setProgress({ phase: { step: 2, total: 4, label: 'Fetching games' }, message: 'Getting owned games...' });

const gamesResp = await steamGet('IPlayerService', 'GetOwnedGames', 'v1', {
include_appinfo: '1',
include_played_free_games: '1',
});

let games = [];
if (!gamesResp._error && gamesResp.response && gamesResp.response.games) {
games = gamesResp.response.games.map(function (g) {
return {
appId: g.appid,
name: g.name,
playtimeMinutes: g.playtime_forever || 0,
playtimeHours: Math.round((g.playtime_forever || 0) / 60 * 10) / 10,
playtimeTwoWeeksMinutes: g.playtime_2weeks || 0,
iconUrl: g.img_icon_url
? 'https://media.steampowered.com/steamcommunity/public/images/apps/' + g.appid + '/' + g.img_icon_url + '.jpg'
: null,
lastPlayed: g.rtime_last_played ? new Date(g.rtime_last_played * 1000).toISOString() : null,
};
});
games.sort(function (a, b) { return b.playtimeMinutes - a.playtimeMinutes; });
}

const recentResp = await steamGet('IPlayerService', 'GetRecentlyPlayedGames', 'v1', {});
let recentGames = [];
if (!recentResp._error && recentResp.response && recentResp.response.games) {
recentGames = recentResp.response.games.map(function (g) {
return {
appId: g.appid,
name: g.name,
playtimeTwoWeeksMinutes: g.playtime_2weeks || 0,
playtimeForeverMinutes: g.playtime_forever || 0,
};
});
}

// ─── Step 3: Friends ─────────────────────────────────────
await page.setProgress({ phase: { step: 3, total: 4, label: 'Fetching friends' }, message: 'Getting friend list...' });

let friends = [];
const friendsResp = await steamGet('ISteamUser', 'GetFriendList', 'v1', { relationship: 'friend' });
if (!friendsResp._error && friendsResp.friendslist && friendsResp.friendslist.friends) {
const friendIds = friendsResp.friendslist.friends;

// Batch resolve friend names (100 at a time)
var friendProfiles = {};
for (var i = 0; i < friendIds.length; i += 100) {
var batch = friendIds.slice(i, i + 100);
var ids = batch.map(function (f) { return f.steamid; }).join(',');
var batchResp = await page.httpFetch(
STEAM_API + '/ISteamUser/GetPlayerSummaries/v2/?key=' + apiKey + '&steamids=' + ids
);
if (batchResp.ok && batchResp.json && batchResp.json.response) {
var batchPlayers = batchResp.json.response.players || [];
for (var p = 0; p < batchPlayers.length; p++) {
friendProfiles[batchPlayers[p].steamid] = batchPlayers[p];
}
}
if (i + 100 < friendIds.length) await page.sleep(300);
}

friends = friendIds.map(function (f) {
var fp = friendProfiles[f.steamid];
return {
steamId: f.steamid,
personaName: fp ? fp.personaname : null,
avatarUrl: fp ? (fp.avatarfull || fp.avatar) : null,
profileUrl: fp ? fp.profileurl : null,
friendSince: f.friend_since ? new Date(f.friend_since * 1000).toISOString() : null,
relationship: f.relationship,
};
});
}

// ─── Step 4: Build result ────────────────────────────────
await page.setProgress({ phase: { step: 4, total: 4, label: 'Building result' }, message: 'Packaging data...' });

var totalPlaytimeHours = Math.round(games.reduce(function (sum, g) { return sum + g.playtimeMinutes; }, 0) / 60);

var topGames = games.slice(0, 5).map(function (g) { return g.name + ' (' + g.playtimeHours + 'h)'; }).join(', ');

var result = {
'steam.profile': profile,
'steam.games': { owned: games, recentlyPlayed: recentGames },
'steam.friends': friends,
exportSummary: {
count: games.length + friends.length,
label: 'Steam data items',
details: games.length + ' games (' + totalPlaytimeHours + 'h total), '
+ friends.length + ' friends, '
+ recentGames.length + ' recently played. '
+ (topGames ? 'Top: ' + topGames : ''),
},
timestamp: new Date().toISOString(),
version: '1.0.0',
platform: 'steam',
};

await page.setData('result', result);
})();
32 changes: 32 additions & 0 deletions valve/steam-playwright.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"id": "steam-playwright",
"version": "1.0.0",
"name": "Steam",
"company": "valve",
"iconURL": "icons/steam.svg",
"description": "Exports your Steam profile, game library with playtime stats, and friend list using the Steam Web API.",
"connectURL": "https://steamcommunity.com/dev/apikey",
"connectSelector": "input#domain",
"exportFrequency": "weekly",
"runtime": "playwright",
"scopes": [
{
"scope": "steam.profile",
"label": "Your Profile",
"description": "Steam profile including persona name, avatar, level, country, and account age"
},
{
"scope": "steam.games",
"label": "Your Games",
"description": "Owned games with playtime statistics, plus recently played games"
},
{
"scope": "steam.friends",
"label": "Your Friends",
"description": "Friend list with persona names, avatars, and friendship dates"
}
],
"vectorize_config": {
"documents": "steam.games.owned[].name"
}
}