feat(plugins/steam): add plugin (#1400) [skip ci]

This commit is contained in:
Simon Lecoq
2023-03-13 19:37:00 -04:00
committed by GitHub
parent 78709320de
commit b85fa234dd
14 changed files with 2825 additions and 1 deletions

View File

@@ -1,11 +1,23 @@
appid
apikey
apiname
appdetails
appids
appinfo
deno deno
gpgarmor gpgarmor
github github
githubassets githubassets
https https
IPlayer
ISteam
leetcode leetcode
Nie
npx npx
personaname
pgn pgn
playerstats
rtime
scm scm
shas shas
splatoon splatoon
@@ -13,5 +25,13 @@ Splatnet
ssh ssh
statink statink
STATINK STATINK
steamcommunity
steamid
steamids
steampowered
timecreated
ubuntu ubuntu
unlocktime
userid
yargsparser yargsparser
webtoken

View File

@@ -55,3 +55,4 @@ ignore$
^\Qsource/templates/terminal/partials/screenshot.ejs\E$ ^\Qsource/templates/terminal/partials/screenshot.ejs\E$
^\Qtests/mocks/api/github/rest/emojis/get.mjs\E$ ^\Qtests/mocks/api/github/rest/emojis/get.mjs\E$
^\Qtests/mocks/api/axios/get/lichess.mjs\E$ ^\Qtests/mocks/api/axios/get/lichess.mjs\E$
^\Qtests/mocks/api/axios/get/steam.mjs\E$

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View File

@@ -1035,6 +1035,104 @@
}, },
}) })
: null), : null),
//Steam
...(set.plugins.enabled.steam
? ({
steam: {
sections: options["anilist.sections"].split(",").map(x => x.trim()).filter(x => x),
player: {
level: faker.datatype.number(100),
avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
created: 1366386002,
name: faker.internet.userName(),
},
games: {
count: 2,
playtime: 89.23333333333333,
achievements: 0,
"most-played": [
{
id: 524220,
name: "NieR:Automata™",
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABAAEADASIAAhEBAxEB/8QAHAAAAgMBAQEBAAAAAAAAAAAABgcFCAkEAwEA/8QAMBAAAQMDAwEGBQQDAAAAAAAAAQIDBAAFEQYHITESExQiQVEjMkJhcQhSobEVgcH/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8ArPebk9cpIW+U8D0FcJUTj39q+Kxnjp6VN6N0zcdV3xi12lpTjzqgCoDIQPc0EKlZ6YJI6Yrvt1oudwIRAhPPqV+1NXb252F01pmHGfuDHjLoPMtazlGfbsmmoxZbXHwI9uhN4/aykH+qDNa5afvNsBE+3vsgj6k1EqKgeRhXTmtQXbTb3jh6BEcT7LZSo/yKXevNktKaqbkOGGIk5acNuMnsJSfwKCgXbUlWfqrpts12DLS8zgqB5yKJNydDXLQl9ct9xQe7J+G5jhQ9KEU/MMUH5XUZOR9qu1+l7Q8fS+jDfLk0lufLyVLWPlb6giqdaYgpueorfDI8rzoSRWhGpbZKe20MK15D6YiEoSnqSAKCMTuzZv8AMeFUy6Ifb7vx2fhAjqKYTD7chpDzCgttYCkqHQikM3fbEnbB6yqhpevThU34NKfi9vI82cUz9EKfs+lILd+eS24oAISRykY4BoC5RwCSQPzS9uu6tmgXZcRCHJEdo4elIPkb980R3iWLvZp8ayS21TO78uOopC27VtqsGjLzpm/2l1m8rU4UhxIKpBUrjBxQMHfPSkDX+3L8yEEPSmGy9GcQOSeOKoU60piQ404PM2opUPYjitDNl7XPg6Ebau6VBTqlLS2vqlB6CqQbsWtFn3AusZsdlJcLmPyTQRGkZibfqq2ynD5Wngo1ou5Pks6et8yE33iCyhS04z5SkVmecpWCnqORV7/0266Z1ZoliHJfQq5RPI40evYHAoCKdqvR0BwyViCLljPd9gBeag7rutF75pk2N98k+TBHP4rx3F2YiX68qvlqfVGuSQMIUSUEj7UAzdtNwo0pl+LLjPOtqzgNelAwY+58W3zA2rT8lhx35jkDipuJrLRV5mI8aICbik5Sh5sKXn84pSx9ttwrhOW7cJUdBUAAe66Ue6G2WhWq6ovF+f8AG3JGCgtkpQD9x60DMtEp+WHHFoCI2MN4GOKz73lnouW410eRyAstjHuCauzvNrGLonQ81/vEty3GyiMgcEqrPSXJXMmyJLxy68srJ+5OaDxX81EOhNWXHR1/YudsdUlTagVIB4UPYioi4wZEJ/upCCleM1yhJ59KC/8AtzvRpnV8NhLstuFcSACw6rlSvXFM5JT84UCFDg54rLZpx1hxLsdS2lJ6KScEf7ootm4OqbatCmLvJV2eneOFQ/ug0jzzyefzQDr3dfTOjWHRMnNPTUDiM2rzVR27bjaruR7Ui7SEk8ZbWU/9oXlyZEt0uS3nH3T9S1FR/mgLd0df3LXl9cmTVqEYKIaazwkenFBaeo9K+dk56V7w4zsqQGWkFS1dAKD/2Q==",
playtime: 44.88333333333333,
played: 1582407120,
description: "NieR: Automata tells the story of androids 2B, 9S and A2 and their battle to reclaim the machine-driven dystopia overrun by powerful machines.",
genres: [
"Action",
"RPG",
],
achievements: [
{
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAgACADASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAUGBwP/xAAmEAABAgQDCQAAAAAAAAAAAAABAAIDBAUGBxIUFiMxNVNhc5Gx/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAME/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A6XVcM5QqlppTK+G4GJvSXEEngOyT7d1TpwPRRiVz5niH0qTWNVWbd1TpwPRTi1bhnK7UtNN5WQ2gRN0S0kg8D2Wdqsw158/xH6EBiVz5niH0qTWiXVb05XalqZTKyG0GHvQWkkHiOyT7CVTqQPZQSarMNefP8R+hGwlU6kD2U4tW3pyhVLUzeV8NwEPdAuIJPE9kH//Z",
achieved: true,
unlocked: 1565976624,
name: "Transcendent Being",
description: "",
id: "ACH_BAD_END",
},
{
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAgACADASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAABQQDAAL/xAAtEAABAwMCBAQGAwAAAAAAAAABAgMEAAURBiISITFBExQjMlFhcYGRsRXB0f/EABcBAQEBAQAAAAAAAAAAAAAAAAABAwT/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwC3UF7lWeeY8bc2rLnqKJIJPT6VhE1BdpCgAhtORy2qJ/ArxqprxtRIB7IH7pixMJZfZWOQPEB+DXG1FSL/AHhj3soB64IINU6evMm8TvLytqEYX6aikkg9D8vlSmomEPcK8hWEYIznuaE0uz4N/cx3R/dQdqdt3+cDkfgUtLZ2LOAr4DPaqbOiDeZrcW4w5cd2Mkq3HiQVBW4Ajl7eX3OOlb3W2yLjMMiPxJSklO9JByDjP0q+2tzIiAlyO27j4k1QfdotvsD7zjEOa9Hkoy64yBhrh6de2P1U2mXmpN2W8wh5Dakjh8UYKh3OKcuKZUpspbitNkjBOT/tHWu3yLfN8xJClpWQnYkqIyeX2oP/2Q==",
achieved: true,
unlocked: 1565976316,
name: "A Round by the Pond",
description: "20 different kinds of fish caught.",
id: "ACH_FISHING",
},
],
rate: {
total: 47,
achieved: 47,
},
},
],
"recently-played": [
{
id: 1113560,
name: "NieR Replicant ver.1.22474487139...",
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABAAEADASIAAhEBAxEB/8QAHAAAAgIDAQEAAAAAAAAAAAAABgcDBQEECAIA/8QAMBAAAQMDAgUCBgEFAQAAAAAAAQIDBAAFERIhBhMxQVEHYRQiIzJCcRUIJFJigcH/xAAYAQADAQEAAAAAAAAAAAAAAAABAgMABP/EAB8RAAICAwACAwAAAAAAAAAAAAABAhEDEiEEQSIxYf/aAAwDAQACEQMRAD8A6m228CgT1PmN/DtMDd1s6z+jRfebg3abVJnSN2mEa1fqubeKOOBxNcBYoTwTcpaylxfX6Z+0DwaWT9FsMbezNluY5e3Jdpt0hoMqRh94jIx4HvV9ZbVHs0NMa3r5YTuvmb6vcV9Bsjdkt6GorJ0gfMvuVd81aWpUfWEqBKldSqpHV+lhHZCmNTbqSfyFYdjoQgKdSS0fzHQVOuGIriXUk8rqfet55DcVkTYg5jB+9pW9YWynjKkR5BcjFL7H+o3o1tPEbbiEtyxy1dM0K3S2amhMthUjI1AZ2J8YrUtt6DTyBcGg2UnAKhso0U6BKCmi09dLkm3cEvLdUUtuZSrfqKQ/oPbbchM6+SAVzHCUoKznAB2xTj/qVZMn08faQkqdVnTjzQF6dQmWOD4fKQOcjdxI/VNJ0LiXxDNy5FlvXo5jRPzHskeTUciNFmR0vwZCQ71Tg7ZoUeW6J5nWtZkxSdDzIOdGKEo/FlxmcQqZiwHmilWFJ7Y84qd2X1G1br4/8O7BeSEuDrqGc/qpLZc+RJMaUfpObJBPehlm6KU+0uc1yXBsVEdRUvEENu5RNKlrQtr6jC0Kxk1vsDjQfEKjRgw25g5K0at+teNUG5RXTIbCHEjC09/2KHrRc0XaAwlSyHUfTO++1ef5Q2+4FqajY7ax0Ioi6tBd6o2r+QjxtassqUQR2G1IK4sXPh65MXC3zELjh0pcjAbhI710h6gsvOWlHJISkElRPakTxE6yxqclIPwh+Vah296M+MGDsFZZw1CU4LjDQYsh0AOxldFjyBWxHgMc9UlLaQ6vZeBUMN2JCjMhb6dKkhSD3welbLMqDJePwMlLjgGVpT2qTtnSlqZlRfiHmFLwW0KyoeR4qJUpLbHORlbTSjgf+VsPvKDiUspKlDqmhuXJ+HYfLSgVJJOj/I+KMX6Fkm+klhuTFr4mXHQomPJwUgncKO5ovvqorrzSJK0pc/An8vakxeHC1Li3SM5qTHXrUQe/cVf8cX1f8FAfOdThyhwHocU9E2unQ/EUkXO3PvNqKYUdOvmdnD4pK8ZRFOfGW6WOWmUyChRGwB3FP3iRtL1qctkZCUuSRpAA2T7mgj1UtammLNISxzQkhp9SR9oCetPJeyGGdcOd/TG4G7zXbBc1KE2EsqjLUdl74A99qawZgWNouTHWoh/LVjKjSC9WZbNt4nactWuNNbOoqScZHagGVcrlOK3JEyS7k5IU4SKDx7O0Os+q1as6Ru/FYkkpgILLWcKWo/cPY0u+LuNmYrzTMJfMcZOsqB2UT2pdyOILm+xyVu4RpCfl2OKqMkryrJPcmtHHTthyeTaqKDnh3idtiW9FkkqgSDqyo50LO5/5RFd7wzcOCp0DX/cxAXU7/iemKUmQVbZx4qQOuDOHFZIwd+o8GncVdkFmdas//9k=",
playtime: 44.35,
played: 1625611102,
description: "The upgraded prequel of NieR:Automata. A kind young man sets out with Grimoire Weiss, a strange talking book, to search for the "Sealed verses" in order to save his sister Yonah, who fell terminally ill to the Black Scrawl.",
genres: [
"Action",
"Adventure",
"RPG",
],
achievements: [
{
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAgACADASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAwUGBAL/xAAsEAACAgEDAwMDAwUAAAAAAAABAgMEBQAGERIhMSJBUQdhkRMUoRVDUnGz/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/xAAYEQEBAQEBAAAAAAAAAAAAAAABACERMf/aAAwDAQACEQMRAD8AsL+R3Jkd+5LEYnIrSr1Yo2QGFGDcqpJLEE88t4+2rbA08hXQDJ5N70jEDkxIgX/XSBzoWMxpr5vK3XPa08ZUD2CoF/PY6lZs7uaDaW9LmTr16NnHGT9nJGvVwvSCOxPq4DDhu3f21fcJ5a89nb8eKwVqKZ68mTtCoqxlAFLu3Qx6lbn0qPHzrNHlM5S3thcdcumercezDIpVCrGOIOGUhVIIblSD8ffSjIY3LZPZGzYzXS3LjpoLd4GRY1SJULBT1Hu3Qy/Y9/GqLH4dpc7jrP8ASWoVcfLI9UGRWLCVGMh4UkKvUV4HnudXCk+x+XisZ3IYz+9VCNz8hlB/jn+dSP1ZuGrs/dNeM8SW3pRIB5P6pCH/AJnXGWo7mxu/psvhKAvU7CKsimRF6V4UMOGYEN6fPfzphujZ1rPSLPDk7UD8wSiKZ+tVkicsvt7Bm4+CffUOCMYONyd/IboyG344QsVXJdVtyhCrVijjWNAf8pGX8Bu3fVVdz9SvuOhhnk5u3EeQIO5CgE8n8HSXBbZtbeny08Vy1dSy5siL9YiWWXjv1sex5+dI8Bh9yZL6hRbg3Jj4aaVYmihCOpBUhgPDE8+s8nto8Zt//9k=",
achieved: true,
unlocked: 1625610706,
name: "e8 a8 98 e6 86 b6 e3 82 b5 e3 83 bc e3 83 90 e3 83 bc",
description: "",
id: "ACHIEVEMENT_0230",
},
{
icon:
"data:image/jpg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAgACADASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAABgQFBwP/xAAsEAACAQQBAwIFBAMAAAAAAAABAgMEBQYRABIhMSJBBxMUUaEVIzJhUnGC/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQAC/8QAGhEAAwEBAQEAAAAAAAAAAAAAAAERIUExMv/aAAwDAQACEQMRAD8AXZJe7vT5DVJLe1sVvR1hgeeCN4JGIH8pvV0MdghXC/0TxvY6S4UsJ/Urm9fIwBBMSIF/10gb5Gp7LG9wvclZFHPTXAorQyKHR0EYUgg+ffgDNbZk2BWSeuw27O1hh0Xt9SonajUt5iZgT0bIBU9wD7+z9Yi8HGaV60ENKTkMVmknf5MRljV1lc6+4Ovbv4G+/CtFeMnt3xHtljvVfFU09XHI5VIkA0EYqdgAhtr48a4dseE5ZltTb8lya4W2qi6FqKairY2eKRWXYUqmgi999tkHR0eaHHZK2qyS23m809ujq6cMiCjeR+gFWB6mYAMNHQAUd/f244sDWW1vu8VRfbhbN/vUoRt/cMoP43+eTLxQpdLPX2+TulVTyQn/AKUgfnXM9yCgyy3ZrV3fG6GOtiqIgvTI6BV9KgjTMp36B3/vifFbvks88aX/AB40miCZ4qiNl2D/AIhiRzM6hp0+Gc/1Hw7xqQb39BErb9mUdJ/IPJtZfqSmyKgszODW1aNIqDyFAJ2fsPSeVCxX+wYvFQ2e3U1wrIzN8tvniNEBlZkBDaJPSwHkDY88O4xaMpuWd02QZVb4KN6WBoUETp0nYYDQDMd+o8YnWV4f/9k=",
achieved: true,
unlocked: 1625607419,
name: "Daredevil",
description: "",
id: "ACHIEVEMENT_0460",
},
],
rate: {
total: 47,
achieved: 44,
},
},
],
},
},
})
: null),
//LeetCode //LeetCode
...(set.plugins.enabled.leetcode ...(set.plugins.enabled.leetcode
? ({ ? ({

View File

@@ -0,0 +1,22 @@
<!--header-->
<!--/header-->
## ➡️ Available options
<!--options-->
<!--/options-->
## 🗝️ Obtaining a *Steam Web API* token
Go to [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) to obtain a Steam Web API token:
![Token](/.github/readme/imgs/plugin_steam_webtoken.png)
To retrieve your Steam ID, access your user account on [store.steampowered.com/account](https://store.steampowered.com/account) and copy the identifier located behind the header:
![User ID](/.github/readme/imgs/plugin_steam_userid.png)
## Examples workflows
<!--examples-->
<!--/examples-->

View File

@@ -0,0 +1,31 @@
- name: Recently played games
uses: lowlighter/metrics@latest
with:
filename: metrics.plugin.steam.svg
token: NOT_NEEDED
base: ""
plugin_steam_token: ${{ secrets.STEAM_TOKEN }}
plugin_steam: yes
plugin_steam_user: 0
plugin_steam_sections: recently-played
plugin_steam_achievements_limit: 0
prod:
# ⚠️ Using mocked data for privacy reasons
with:
plugin_steam_token: MOCKED_TOKEN
use_mocked_data: yes
- name: Profile and detailed game history
uses: lowlighter/metrics@latest
with:
filename: metrics.plugin.steam.full.svg
token: NOT_NEEDED
base: ""
plugin_steam_token: ${{ secrets.STEAM_TOKEN }}
plugin_steam: yes
plugin_steam_user: 0
prod:
# ⚠️ Using mocked data for privacy reasons
with:
plugin_steam_token: MOCKED_TOKEN
use_mocked_data: yes

View File

@@ -0,0 +1,104 @@
//Setup
export default async function({login, q, imports, data, account}, {token, enabled = false, extras = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!q.steam) || (!imports.metadata.plugins.steam.enabled(enabled, {extras})))
return null
//Load inputs
let {user, sections, "games.ignored": _games_ignored, "games.limit": _games_limit, "recent.games.limit": _recent_games_limit, "achievements.limit": _achievements_limit, "playtime.threshold": _playtime_threshold} = imports.metadata.plugins.steam.inputs({data, account, q})
const urls = {
games: {
owned: `https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${token}&steamid=${user}&format=json&include_appinfo=1`,
schema: `https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v0002/?key=${token}&format=json`,
details: "https://store.steampowered.com/api/appdetails?",
},
player: {
summary: `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${token}&steamids=${user}&format=json`,
level: `https://api.steampowered.com/IPlayerService/GetSteamLevel/v1/?key=${token}&steamid=${user}&format=json`,
achievement: `https://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?key=${token}&steamid=${user}&format=json&l=en`,
},
}
const result = {sections, player: null, games: {count: 0, playtime: 0, achievements: 0}}
//Fetch owned games
console.debug(`metrics/compute/${login}/plugins > steam > fetching owned games`)
let {data: {response: {game_count: count, games}}} = await imports.axios.get(urls.games.owned)
result.games.count = count
result.games.playtime = games.reduce((total, {playtime_forever: playtime}) => (total += playtime), 0) / 60
//Fetch game achievements and order games by section
for (const section of ["most-played", "recently-played"]) {
if (!sections.includes(section))
continue
result.games[section] = await Promise.all(
games
.map(({appid: id, name, img_icon_url: icon, playtime_forever: playtime, rtime_last_played: played}) => ({id, name, icon: `http://media.steampowered.com/steamcommunity/public/images/apps/${id}/${icon}.jpg`, playtime: playtime / 60, played}))
.filter(({playtime}) => (playtime >= _playtime_threshold))
.filter(({id}) => (!_games_ignored.includes(`${id}`)))
.sort((a, b) => ({"most-played": (b.playtime - a.playtime), "recently-played": (b.played - a.played)}[section]))
.slice(0, ({"most-played": _games_limit, "recently-played": _recent_games_limit}[section]) || Infinity)
.map(async game => {
const schema = {}
try {
console.debug(`metrics/compute/${login}/plugins > steam > fetching schema for "${game.name}" (${game.id})`)
const {data: {game: {availableGameStats: {achievements = []} = {}}}} = await imports.axios.get(`${urls.games.schema}&appid=${game.id}`)
Object.assign(schema, Object.fromEntries(achievements.map(({name, icon}) => [name, {icon}])))
}
catch (error) {
console.debug(`metrics/compute/${login}/plugins > steam > failed to get schema for "${game.name}" (${game.id}) > ${error}`)
}
const about = {}
try {
console.debug(`metrics/compute/${login}/plugins > steam > fetching details for "${game.name}" (${game.id})`)
const {data: {[game.id]: {data}}} = await imports.axios.get(`${urls.games.details}&appids=${game.id}`)
about.description = data.short_description ?? ""
about.genres = data.genres?.map(({description}) => description) ?? []
}
catch (error) {
console.debug(`metrics/compute/${login}/plugins > steam > failed to get details for "${game.name}" (${game.id}) > ${error}`)
}
let achievements = []
const rate = {total: Object.keys(schema).length, achieved: 0}
try {
console.debug(`metrics/compute/${login}/plugins > steam > fetching player achievements "${game.name}" (${game.id})`)
let {data: {playerstats: {achievements: list = []}}} = await imports.axios.get(`${urls.player.achievement}&appid=${game.id}`)
achievements = await Promise.all(list.map(async ({apiname: id, achieved, unlocktime: unlocked, name, description}) => ({icon: await imports.imgb64(schema[id]?.icon ?? null, {width: 32, height: 32}), achieved: !!achieved, unlocked, name, description, id})))
achievements = achievements.sort((a, b) => (b.unlocked - a.unlocked))
rate.achieved = achievements.filter(({achieved}) => achieved).length
achievements = achievements.slice(0, _achievements_limit)
}
catch (error) {
console.debug(`metrics/compute/${login}/plugins > steam > failed to get player achievements for "${game.name}" (${game.id}) > ${error}`)
}
return {...game, ...about, icon: await imports.imgb64(game.icon, {width: 64, height: 64}), achievements, rate}
}),
)
}
//Fetch player info
if (sections.includes("player")) {
console.debug(`metrics/compute/${login}/plugins > steam > fetching profile info`)
let {data: {response: {players: [info]}}} = await imports.axios.get(urls.player.summary)
console.debug(`metrics/compute/${login}/plugins > steam > fetching profile level`)
const {data: {response: {player_level: level}}} = await imports.axios.get(urls.player.level)
result.player = {
level,
avatar: await imports.imgb64(info.avatar, {width: 64, height: 64}),
created: info.timecreated,
name: info.personaname,
}
}
//Results
console.log(JSON.stringify(result))
return result
}
//Handle errors
catch (error) {
throw imports.format.error(error)
}
}

View File

@@ -0,0 +1,93 @@
name: 🕹️ Steam
category: social
description: |
This plugin can display your player profile and played games from your Steam account.
disclaimer: |
This plugin is not affiliated, associated, authorized, endorsed by, or in any way officially connected with [Steam](https://store.steampowered.com).
All product and company names are trademarks™ or registered® trademarks of their respective holders.
examples:
+Recently played games: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.steam.svg
Profile and detailed game history: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.steam.full.svg
supports:
- user
- organization
scopes: []
inputs:
plugin_steam:
description: |
Enable steam plugin
type: boolean
default: no
plugin_steam_token:
description: |
Steam token
type: token
default: ""
extras:
- metrics.api.steam
plugin_steam_sections:
description: |
Displayed sections
- `player`: display profile
- `most-played`: display most played games
- `recently-played`: display recently played games
type: array
format: comma-separated
default: player, most-played, recently-played
options:
- player
- most-played
- recently-played
plugin_steam_user:
description: |
Steam user id
This can be found on your Steam user account details
type: string
preset: no
plugin_steam_games_ignored:
description: |
Ignored games
Use App id as they are referenced in Steam catalog
type: array
format: comma-separated
default: ""
example: 400, 620
plugin_steam_games_limit:
description: |
Display limit (Most played games)
type: number
min: 0
zero: disable
default: 1
plugin_steam_recent_games_limit:
description: |
Display limit (Recently played games)
type: number
min: 0
zero: disable
default: 1
plugin_steam_achievements_limit:
description: |
Display limit (Games achievements)
type: number
min: 0
default: 2
plugin_steam_playtime_threshold:
description: |
Display threshold (Game playtime in hours)
type: number
min: 0
default: 2

View File

@@ -42,5 +42,6 @@
"sponsorships", "sponsorships",
"poopmap", "poopmap",
"fortune", "fortune",
"splatoon" "splatoon",
"steam"
] ]

View File

@@ -0,0 +1,108 @@
<% if (plugins.steam) { %>
<section class="steam">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.267 1.457c.3.286.312.76.026 1.06A6.475 6.475 0 001.5 7a6.472 6.472 0 001.793 4.483.75.75 0 01-1.086 1.034 8.89 8.89 0 01-.276-.304l.569-.49-.569.49A7.971 7.971 0 010 7c0-2.139.84-4.083 2.207-5.517a.75.75 0 011.06-.026zm9.466 0a.75.75 0 011.06.026A7.975 7.975 0 0116 7c0 2.139-.84 4.083-2.207 5.517a.75.75 0 11-1.086-1.034A6.475 6.475 0 0014.5 7a6.475 6.475 0 00-1.793-4.483.75.75 0 01.026-1.06zM8.75 8.582a1.75 1.75 0 10-1.5 0v5.668a.75.75 0 001.5 0V8.582zM5.331 4.736a.75.75 0 10-1.143-.972A4.983 4.983 0 003 7c0 1.227.443 2.352 1.177 3.222a.75.75 0 001.146-.967A3.483 3.483 0 014.5 7c0-.864.312-1.654.831-2.264zm6.492-.958a.75.75 0 00-1.146.967c.514.61.823 1.395.823 2.255 0 .86-.31 1.646-.823 2.255a.75.75 0 101.146.967A4.983 4.983 0 0013 7a4.983 4.983 0 00-1.177-3.222z"></path></svg>
Steam
</h2>
<% if (plugins.steam.error) { %>
<div class="row">
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.steam.error.message %>
</div>
</section>
</div>
<% } else { %>
<% if ((plugins.steam.sections.includes("player"))&&(plugins.steam.player)) { %>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M10.561 8.073a6.005 6.005 0 0 1 3.432 5.142.75.75 0 1 1-1.498.07 4.5 4.5 0 0 0-8.99 0 .75.75 0 0 1-1.498-.07 6.004 6.004 0 0 1 3.431-5.142 3.999 3.999 0 1 1 5.123 0ZM10.5 5a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"></path></svg>
<%= plugins.steam.player.name %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m10.41.24 4.711 2.774c.544.316.878.897.879 1.526v5.01a1.77 1.77 0 0 1-.88 1.53l-7.753 4.521-.002.001a1.769 1.769 0 0 1-1.774 0H5.59L.873 12.85A1.761 1.761 0 0 1 0 11.327V6.292c0-.304.078-.598.22-.855l.004-.005.01-.019c.15-.262.369-.486.64-.643L8.641.239a1.752 1.752 0 0 1 1.765 0l.002.001ZM9.397 1.534l-7.17 4.182 4.116 2.388a.27.27 0 0 0 .269 0l7.152-4.148-4.115-2.422a.252.252 0 0 0-.252 0Zm-7.768 10.02 4.1 2.393V9.474a1.807 1.807 0 0 1-.138-.072L1.5 7.029v4.298c0 .095.05.181.129.227Zm8.6.642 1.521-.887v-4.45l-1.521.882ZM7.365 9.402h.001c-.044.026-.09.049-.136.071v4.472l1.5-.875V8.61Zm5.885 1.032 1.115-.65h.002a.267.267 0 0 0 .133-.232V5.264l-1.25.725Z"></path></svg>
<%= plugins.steam.games.count %> game<%= s(plugins.steam.games.count) %>
</div>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path></svg>
Steam level <%= plugins.steam.player.level %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg>
<%= f(parseInt(plugins.steam.games.playtime)) %> hour<%= s(plugins.steam.games.playtime) %> played
</div>
</section>
</div>
<% } %>
<% for (const section of ["most-played", "recently-played"]) { if (plugins.steam.sections.includes(section)) { %>
<div class="games">
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 4a1 1 0 100-2 1 1 0 000 2zm3.75-1.5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<%= {"most-played":"Most played", "recently-played":"Recently played"}[section] %>
</h2>
<div class="medias largeable-flex-wrap">
<% for (const {name, icon, playtime, played, achievements, rate, genres, description} of plugins.steam.games[section]) { %>
<div class="media largeable-width-half">
<img src="<%= icon %>" alt="" />
<div class="about">
<div class="name"><%= name %></div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>
<%= genres.join(", ") %>
</div>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"></path></svg>
<%= f(parseInt(playtime)) %> hour<%= s(playtime) %> played
</div>
</div>
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"></path></svg>
Last played on <%= f.date(played*1000, {date:true, timeZone:config.timezone?.name}) %>
</div>
</div>
<div class="achievements">
<div class="infos">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M3.217 6.962A3.75 3.75 0 0 1 0 3.25v-.5C0 1.784.784 1 1.75 1h1.356c.228-.585.796-1 1.462-1h6.864c.647 0 1.227.397 1.462 1h1.356c.966 0 1.75.784 1.75 1.75v.5a3.75 3.75 0 0 1-3.217 3.712 5.014 5.014 0 0 1-2.771 3.117l.144 1.446c.005.05.03.12.114.204.086.087.217.17.373.227.283.103.618.274.89.568.285.31.467.723.467 1.226v.75h1.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H4v-.75c0-.503.182-.916.468-1.226.27-.294.606-.465.889-.568.139-.048.266-.126.373-.227.084-.085.109-.153.114-.204l.144-1.446a5.015 5.015 0 0 1-2.77-3.117ZM4.5 1.568V5.5a3.5 3.5 0 1 0 7 0V1.568a.068.068 0 0 0-.068-.068H4.568a.068.068 0 0 0-.068.068Zm2.957 8.902-.12 1.204c-.093.925-.858 1.47-1.467 1.691a.766.766 0 0 0-.3.176c-.037.04-.07.093-.07.21v.75h5v-.75c0-.117-.033-.17-.07-.21a.766.766 0 0 0-.3-.176c-.609-.221-1.374-.766-1.466-1.69l-.12-1.204a5.064 5.064 0 0 1-1.087 0ZM13 2.5v2.872a2.25 2.25 0 0 0 1.5-2.122v-.5a.25.25 0 0 0-.25-.25H13Zm-10 0H1.75a.25.25 0 0 0-.25.25v.5c0 .98.626 1.813 1.5 2.122Z"></path></svg>
<%= rate.achieved %> / <%= rate.total %> achievement<%= s(rate.total) %> unlocked
</div>
</div>
<% { const achieved = achievements.filter(({achieved}) => achieved) %>
<% for (const {icon, name, description, unlocked} of achieved) { %>
<div class="achievement">
<img src="<%= icon %>" alt="" />
<div>
<div class="name">
<div><%= name %></div>
<div class="unlocked"><%= f.date(unlocked*1000, {date:true, timeZone:config.timezone?.name}) %></div>
</div>
<div class="description"><%= description %></div>
</div>
</div>
<% } %>
<% if ((achieved.length)&&(rate.achieved-achieved.length > 0)) { %>
<div class="achievement">
<div>
<div class="description ellipsis">+<%= rate.achieved-achieved.length %> other<%= s(rate.achieved-achieved.length) %>...</div>
</div>
</div>
<% } %>
<% } %>
</div>
</div>
</div>
<% } %>
</div>
</div>
<% } } %>
<% } %>
</section>
<% } %>

View File

@@ -1049,6 +1049,88 @@
border-radius: 7px; border-radius: 7px;
} }
/* Steam */
.steam .games {
margin-left: 28px;
}
.steam .media {
display: flex;
margin-bottom: 4px;
width: 450px;
}
.steam .media img {
margin: 0 10px;
border-radius: 7px;
}
.steam .media > img {
height: 32px;
width: 32px;
}
.steam .media .about {
flex-grow: 1;
}
.steam .media .name {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
line-height: 14px;
color: #58a6ff;
}
.steam .media .infos {
font-size: 12px;
color: #666666;
display: flex;
align-items: center;
justify-content: space-between;
}
.steam .media .infos > div {
display: inline-flex;
align-items: center;
margin-right: 16px;
}
.steam .media .infos svg {
fill: currentColor;
height: 12px;
width: 12px;
margin: 0;
margin-right: 4px;
}
.steam .media .achievement img {
height: 22px;
width: 22px;
margin-right: 6px;
}
.steam .media .achievement .name {
display: flex;
justify-content: space-between;
}
.steam .media .achievement .name > div:first-child {
max-width: 280px;
}
.steam .media .achievement .description {
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: 344px;
max-height: 38px;
font-size: 12px;
white-space: normal;
/* May not work in all browsers */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.steam .media .achievement .unlocked {
font-size: 12px;
color: #666666;
font-style: italic;
flex-shrink: 0;
}
.steam .media .achievement .ellipsis {
margin-left: 18px;
}
/* Licenses */ /* Licenses */
.licenses { .licenses {
display: flex; display: flex;

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,7 @@
"GOOGLE_MAP_TOKEN": "MOCKED_TOKEN", "GOOGLE_MAP_TOKEN": "MOCKED_TOKEN",
"STOCK_TOKEN":"MOCKED_TOKEN", "STOCK_TOKEN":"MOCKED_TOKEN",
"CHESS_TOKEN":"MOCKED_TOKEN", "CHESS_TOKEN":"MOCKED_TOKEN",
"STEAM_TOKEN":"MOCKED_TOKEN",
"SPLATOON_TOKEN":"{}", "SPLATOON_TOKEN":"{}",
"SPLATOON_STATINK_TOKEN":"MOCKED_TOKEN", "SPLATOON_STATINK_TOKEN":"MOCKED_TOKEN",
"POOPMAP_TOKEN":"MOCKED_TOKEN" "POOPMAP_TOKEN":"MOCKED_TOKEN"