diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 24555fd1..dfdbd479 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -1,11 +1,23 @@
+appid
+apikey
+apiname
+appdetails
+appids
+appinfo
deno
gpgarmor
github
githubassets
https
+IPlayer
+ISteam
leetcode
+Nie
npx
+personaname
pgn
+playerstats
+rtime
scm
shas
splatoon
@@ -13,5 +25,13 @@ Splatnet
ssh
statink
STATINK
+steamcommunity
+steamid
+steamids
+steampowered
+timecreated
ubuntu
+unlocktime
+userid
yargsparser
+webtoken
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
index 51b24af1..7b180c74 100644
--- a/.github/actions/spelling/excludes.txt
+++ b/.github/actions/spelling/excludes.txt
@@ -55,3 +55,4 @@ ignore$
^\Qsource/templates/terminal/partials/screenshot.ejs\E$
^\Qtests/mocks/api/github/rest/emojis/get.mjs\E$
^\Qtests/mocks/api/axios/get/lichess.mjs\E$
+^\Qtests/mocks/api/axios/get/steam.mjs\E$
\ No newline at end of file
diff --git a/.github/readme/imgs/plugin_steam_userid.png b/.github/readme/imgs/plugin_steam_userid.png
new file mode 100644
index 00000000..30ede968
Binary files /dev/null and b/.github/readme/imgs/plugin_steam_userid.png differ
diff --git a/.github/readme/imgs/plugin_steam_webtoken.png b/.github/readme/imgs/plugin_steam_webtoken.png
new file mode 100644
index 00000000..63e83717
Binary files /dev/null and b/.github/readme/imgs/plugin_steam_webtoken.png differ
diff --git a/source/app/web/statics/embed/app.placeholder.js b/source/app/web/statics/embed/app.placeholder.js
index f671abaa..da229afd 100644
--- a/source/app/web/statics/embed/app.placeholder.js
+++ b/source/app/web/statics/embed/app.placeholder.js
@@ -1035,6 +1035,104 @@
},
})
: 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
...(set.plugins.enabled.leetcode
? ({
diff --git a/source/plugins/community/splatoon/s3si/index.ts b/source/plugins/community/splatoon/s3si/index.ts
index 203a64e1..f2b37097 100644
--- a/source/plugins/community/splatoon/s3si/index.ts
+++ b/source/plugins/community/splatoon/s3si/index.ts
@@ -12,9 +12,9 @@ class APIError extends Error {
}
}
const AGENT_NAME = "s3si.ts";
-const S3SI_VERSION = "0.2.4";
-const NSOAPP_VERSION = "2.4.0";
-const WEB_VIEW_VERSION = "2.0.0-bd36a652";
+const S3SI_VERSION = "0.3.1";
+const NSOAPP_VERSION = "2.5.0";
+const WEB_VIEW_VERSION = "3.0.0-2857bc50";
const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts";
const USERAGENT = `${AGENT_NAME}/${S3SI_VERSION} (${S3SI_LINK})`;
const DEFAULT_APP_USER_AGENT = "Mozilla/5.0 (Linux; Android 11; Pixel 5) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/94.0.4606.61 Mobile Safari/537.36";
@@ -29,7 +29,7 @@ const SPLATNET3_STATINK_MAP = {
LOFT: "yagura",
GOAL: "hoko",
CLAM: "asari",
- TRI_COLOR: "nawabari"
+ TRI_COLOR: "tricolor"
},
RESULT: {
WIN: "win",
@@ -54,15 +54,6 @@ const SPLATNET3_STATINK_MAP = {
7: "giant_tornado",
8: "mudmouth_eruption"
},
- COOP_UNIFORM_MAP: {
- 1: "orange",
- 2: "green",
- 3: "yellow",
- 4: "pink",
- 5: "blue",
- 6: "black",
- 7: "white"
- },
COOP_SPECIAL_MAP: {
"bd327d1b64372dedefd32adb28bea62a5b6152d93aada5d9fc4f669a1955d6d4": "nicedama",
"463eedc60013608666b260c79ac8c352f9795c3d0cce074d3fbbdbd2c054a56d": "hopsonar",
@@ -748,19 +739,19 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
}
const aliases = {};
if (alias !== undefined) {
- for(const key1 in alias){
- const val = getForce(alias, key1);
+ for(const key in alias){
+ const val = getForce(alias, key);
if (typeof val === "string") {
- aliases[key1] = [
+ aliases[key] = [
val
];
} else {
- aliases[key1] = val;
+ aliases[key] = val;
}
- for (const alias1 of getForce(aliases, key1)){
- aliases[alias1] = [
- key1
- ].concat(aliases[key1].filter((y)=>alias1 !== y));
+ for (const alias of getForce(aliases, key)){
+ aliases[alias] = [
+ key
+ ].concat(aliases[key].filter((y)=>alias !== y));
}
}
}
@@ -768,11 +759,11 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
const stringArgs = typeof string === "string" ? [
string
] : string;
- for (const key2 of stringArgs.filter(Boolean)){
- flags.strings[key2] = true;
- const alias2 = get(aliases, key2);
- if (alias2) {
- for (const al of alias2){
+ for (const key of stringArgs.filter(Boolean)){
+ flags.strings[key] = true;
+ const alias = get(aliases, key);
+ if (alias) {
+ for (const al of alias){
flags.strings[al] = true;
}
}
@@ -782,12 +773,12 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
const collectArgs = typeof collect === "string" ? [
collect
] : collect;
- for (const key3 of collectArgs.filter(Boolean)){
- flags.collect[key3] = true;
- const alias3 = get(aliases, key3);
- if (alias3) {
- for (const al1 of alias3){
- flags.collect[al1] = true;
+ for (const key of collectArgs.filter(Boolean)){
+ flags.collect[key] = true;
+ const alias = get(aliases, key);
+ if (alias) {
+ for (const al of alias){
+ flags.collect[al] = true;
}
}
}
@@ -796,12 +787,12 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
const negatableArgs = typeof negatable === "string" ? [
negatable
] : negatable;
- for (const key4 of negatableArgs.filter(Boolean)){
- flags.negatable[key4] = true;
- const alias4 = get(aliases, key4);
- if (alias4) {
- for (const al2 of alias4){
- flags.negatable[al2] = true;
+ for (const key of negatableArgs.filter(Boolean)){
+ flags.negatable[key] = true;
+ const alias = get(aliases, key);
+ if (alias) {
+ for (const al of alias){
+ flags.negatable[al] = true;
}
}
}
@@ -864,47 +855,47 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
if (/^--.+=/.test(arg)) {
const m = arg.match(/^--([^=]+)=(.*)$/s);
assert(m != null);
- const [, key5, value] = m;
- if (flags.bools[key5]) {
+ const [, key, value] = m;
+ if (flags.bools[key]) {
const booleanValue = value !== "false";
- setArg(key5, booleanValue, arg);
+ setArg(key, booleanValue, arg);
} else {
- setArg(key5, value, arg);
+ setArg(key, value, arg);
}
} else if (/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))) {
- const m1 = arg.match(/^--no-(.+)/);
- assert(m1 != null);
- setArg(m1[1], false, arg, false);
+ const m = arg.match(/^--no-(.+)/);
+ assert(m != null);
+ setArg(m[1], false, arg, false);
} else if (/^--.+/.test(arg)) {
- const m2 = arg.match(/^--(.+)/);
- assert(m2 != null);
- const [, key6] = m2;
+ const m = arg.match(/^--(.+)/);
+ assert(m != null);
+ const [, key] = m;
const next = args[i + 1];
- if (next !== undefined && !/^-/.test(next) && !get(flags.bools, key6) && !flags.allBools && (get(aliases, key6) ? !aliasIsBoolean(key6) : true)) {
- setArg(key6, next, arg);
+ if (next !== undefined && !/^-/.test(next) && !get(flags.bools, key) && !flags.allBools && (get(aliases, key) ? !aliasIsBoolean(key) : true)) {
+ setArg(key, next, arg);
i++;
} else if (/^(true|false)$/.test(next)) {
- setArg(key6, next === "true", arg);
+ setArg(key, next === "true", arg);
i++;
} else {
- setArg(key6, get(flags.strings, key6) ? "" : true, arg);
+ setArg(key, get(flags.strings, key) ? "" : true, arg);
}
} else if (/^-[^-]+/.test(arg)) {
const letters = arg.slice(1, -1).split("");
let broken = false;
for(let j = 0; j < letters.length; j++){
- const next1 = arg.slice(j + 2);
- if (next1 === "-") {
- setArg(letters[j], next1, arg);
+ const next = arg.slice(j + 2);
+ if (next === "-") {
+ setArg(letters[j], next, arg);
continue;
}
- if (/[A-Za-z]/.test(letters[j]) && /=/.test(next1)) {
- setArg(letters[j], next1.split(/=(.+)/)[1], arg);
+ if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
+ setArg(letters[j], next.split(/=(.+)/)[1], arg);
broken = true;
break;
}
- if (/[A-Za-z]/.test(letters[j]) && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next1)) {
- setArg(letters[j], next1, arg);
+ if (/[A-Za-z]/.test(letters[j]) && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
+ setArg(letters[j], next, arg);
broken = true;
break;
}
@@ -916,16 +907,16 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg);
}
}
- const [key7] = arg.slice(-1);
- if (!broken && key7 !== "-") {
- if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) && !get(flags.bools, key7) && (get(aliases, key7) ? !aliasIsBoolean(key7) : true)) {
- setArg(key7, args[i + 1], arg);
+ const [key] = arg.slice(-1);
+ if (!broken && key !== "-") {
+ if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) && !get(flags.bools, key) && (get(aliases, key) ? !aliasIsBoolean(key) : true)) {
+ setArg(key, args[i + 1], arg);
i++;
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
- setArg(key7, args[i + 1] === "true", arg);
+ setArg(key, args[i + 1] === "true", arg);
i++;
} else {
- setArg(key7, get(flags.strings, key7) ? "" : true, arg);
+ setArg(key, get(flags.strings, key) ? "" : true, arg);
}
}
} else {
@@ -938,35 +929,35 @@ function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean
}
}
}
- for (const [key8, value1] of Object.entries(defaults)){
- if (!hasKey(argv, key8.split("."))) {
- setKey(argv, key8, value1);
- if (aliases[key8]) {
- for (const x of aliases[key8]){
- setKey(argv, x, value1);
+ for (const [key, value] of Object.entries(defaults)){
+ if (!hasKey(argv, key.split("."))) {
+ setKey(argv, key, value);
+ if (aliases[key]) {
+ for (const x of aliases[key]){
+ setKey(argv, x, value);
}
}
}
}
- for (const key9 of Object.keys(flags.bools)){
- if (!hasKey(argv, key9.split("."))) {
- const value2 = get(flags.collect, key9) ? [] : false;
- setKey(argv, key9, value2, false);
+ for (const key of Object.keys(flags.bools)){
+ if (!hasKey(argv, key.split("."))) {
+ const value = get(flags.collect, key) ? [] : false;
+ setKey(argv, key, value, false);
}
}
- for (const key10 of Object.keys(flags.strings)){
- if (!hasKey(argv, key10.split(".")) && get(flags.collect, key10)) {
- setKey(argv, key10, [], false);
+ for (const key of Object.keys(flags.strings)){
+ if (!hasKey(argv, key.split(".")) && get(flags.collect, key)) {
+ setKey(argv, key, [], false);
}
}
if (doubleDash) {
argv["--"] = [];
- for (const key11 of notFlags){
- argv["--"].push(key11);
+ for (const key of notFlags){
+ argv["--"].push(key);
}
} else {
- for (const key12 of notFlags){
- argv._.push(key12);
+ for (const key of notFlags){
+ argv._.push(key);
}
}
return argv;
@@ -1011,9 +1002,9 @@ class BytesList {
chunk.start += diff;
}
let offset = 0;
- for (const chunk1 of this.#chunks){
- chunk1.offset = offset;
- offset += chunk1.end - chunk1.start;
+ for (const chunk of this.#chunks){
+ chunk.offset = offset;
+ offset += chunk.end - chunk.start;
}
this.#len = offset;
}
@@ -1099,9 +1090,9 @@ function concat(...buf) {
}
const output = new Uint8Array(length);
let index = 0;
- for (const b1 of buf){
- output.set(b1, index);
- index += b1.length;
+ for (const b of buf){
+ output.set(b, index);
+ index += b.length;
}
return output;
}
@@ -1324,10 +1315,10 @@ class BufReader {
if (p.byteLength === 0) return rr;
if (this.#r === this.#w) {
if (p.byteLength >= this.#buf.byteLength) {
- const rr1 = await this.#rd.read(p);
- const nread = rr1 ?? 0;
+ const rr = await this.#rd.read(p);
+ const nread = rr ?? 0;
assert(nread >= 0, "negative read");
- return rr1;
+ return rr;
}
this.#r = 0;
this.#w = 0;
@@ -2120,14 +2111,14 @@ function utf8DecodeJs(bytes, inputOffset, byteLength) {
const byte2 = bytes[offset++] & 0x3f;
units.push((byte1 & 0x1f) << 6 | byte2);
} else if ((byte1 & 0xf0) === 0xe0) {
- const byte21 = bytes[offset++] & 0x3f;
+ const byte2 = bytes[offset++] & 0x3f;
const byte3 = bytes[offset++] & 0x3f;
- units.push((byte1 & 0x1f) << 12 | byte21 << 6 | byte3);
+ units.push((byte1 & 0x1f) << 12 | byte2 << 6 | byte3);
} else if ((byte1 & 0xf8) === 0xf0) {
- const byte22 = bytes[offset++] & 0x3f;
- const byte31 = bytes[offset++] & 0x3f;
+ const byte2 = bytes[offset++] & 0x3f;
+ const byte3 = bytes[offset++] & 0x3f;
const byte4 = bytes[offset++] & 0x3f;
- let unit = (byte1 & 0x07) << 0x12 | byte22 << 0x0c | byte31 << 0x06 | byte4;
+ let unit = (byte1 & 0x07) << 0x12 | byte2 << 0x0c | byte3 << 0x06 | byte4;
if (unit > 0xffff) {
unit -= 0x10000;
units.push(unit >>> 10 & 0x3ff | 0xd800);
@@ -2195,18 +2186,18 @@ function encodeTimeSpecToTimestamp({ sec , nsec }) {
} else {
const secHigh = sec / 0x100000000;
const secLow = sec & 0xffffffff;
- const rv1 = new Uint8Array(8);
- const view1 = new DataView(rv1.buffer);
- view1.setUint32(0, nsec << 2 | secHigh & 0x3);
- view1.setUint32(4, secLow);
- return rv1;
+ const rv = new Uint8Array(8);
+ const view = new DataView(rv.buffer);
+ view.setUint32(0, nsec << 2 | secHigh & 0x3);
+ view.setUint32(4, secLow);
+ return rv;
}
} else {
- const rv2 = new Uint8Array(12);
- const view2 = new DataView(rv2.buffer);
- view2.setUint32(0, nsec);
- setInt64(view2, 4, sec);
- return rv2;
+ const rv = new Uint8Array(12);
+ const view = new DataView(rv.buffer);
+ view.setUint32(0, nsec);
+ setInt64(view, 4, sec);
+ return rv;
}
}
function encodeDateToTimeSpec(date) {
@@ -2242,20 +2233,20 @@ function decodeTimestampToTimeSpec(data) {
{
const nsec30AndSecHigh2 = view.getUint32(0);
const secLow32 = view.getUint32(4);
- const sec1 = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
- const nsec1 = nsec30AndSecHigh2 >>> 2;
+ const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
+ const nsec = nsec30AndSecHigh2 >>> 2;
return {
- sec: sec1,
- nsec: nsec1
+ sec,
+ nsec
};
}
case 12:
{
- const sec2 = getInt64(view, 4);
- const nsec2 = view.getUint32(0);
+ const sec = getInt64(view, 4);
+ const nsec = view.getUint32(0);
return {
- sec: sec2,
- nsec: nsec2
+ sec,
+ nsec
};
}
default:
@@ -2302,13 +2293,13 @@ class ExtensionCodec {
}
}
}
- for(let i1 = 0; i1 < this.encoders.length; i1++){
- const encoder1 = this.encoders[i1];
- if (encoder1 != null) {
- const data1 = encoder1(object, context);
- if (data1 != null) {
- const type1 = i1;
- return new ExtData(type1, data1);
+ for(let i = 0; i < this.encoders.length; i++){
+ const encoder = this.encoders[i];
+ if (encoder != null) {
+ const data = encoder(object, context);
+ if (data != null) {
+ const type = i;
+ return new ExtData(type, data);
}
}
}
@@ -2482,11 +2473,11 @@ class Encoder {
utf8EncodeTE(object, this.bytes, this.pos);
this.pos += byteLength;
} else {
- const byteLength1 = utf8Count(object);
- this.ensureBufferSizeToWrite(maxHeaderSize + byteLength1);
- this.writeStringHeader(byteLength1);
+ const byteLength = utf8Count(object);
+ this.ensureBufferSizeToWrite(maxHeaderSize + byteLength);
+ this.writeStringHeader(byteLength);
utf8EncodeJs(object, this.bytes, this.pos);
- this.pos += byteLength1;
+ this.pos += byteLength;
}
}
encodeObject(object, depth) {
@@ -2896,9 +2887,9 @@ class Decoder {
object = {};
}
} else if (headByte < 0xa0) {
- const size1 = headByte - 0x90;
- if (size1 !== 0) {
- this.pushArrayState(size1);
+ const size = headByte - 0x90;
+ if (size !== 0) {
+ this.pushArrayState(size);
this.complete();
continue DECODE;
} else {
@@ -2935,59 +2926,59 @@ class Decoder {
} else if (headByte === 0xd3) {
object = this.readI64();
} else if (headByte === 0xd9) {
- const byteLength1 = this.lookU8();
- object = this.decodeUtf8String(byteLength1, 1);
+ const byteLength = this.lookU8();
+ object = this.decodeUtf8String(byteLength, 1);
} else if (headByte === 0xda) {
- const byteLength2 = this.lookU16();
- object = this.decodeUtf8String(byteLength2, 2);
+ const byteLength = this.lookU16();
+ object = this.decodeUtf8String(byteLength, 2);
} else if (headByte === 0xdb) {
- const byteLength3 = this.lookU32();
- object = this.decodeUtf8String(byteLength3, 4);
+ const byteLength = this.lookU32();
+ object = this.decodeUtf8String(byteLength, 4);
} else if (headByte === 0xdc) {
- const size2 = this.readU16();
- if (size2 !== 0) {
- this.pushArrayState(size2);
+ const size = this.readU16();
+ if (size !== 0) {
+ this.pushArrayState(size);
this.complete();
continue DECODE;
} else {
object = [];
}
} else if (headByte === 0xdd) {
- const size3 = this.readU32();
- if (size3 !== 0) {
- this.pushArrayState(size3);
+ const size = this.readU32();
+ if (size !== 0) {
+ this.pushArrayState(size);
this.complete();
continue DECODE;
} else {
object = [];
}
} else if (headByte === 0xde) {
- const size4 = this.readU16();
- if (size4 !== 0) {
- this.pushMapState(size4);
+ const size = this.readU16();
+ if (size !== 0) {
+ this.pushMapState(size);
this.complete();
continue DECODE;
} else {
object = {};
}
} else if (headByte === 0xdf) {
- const size5 = this.readU32();
- if (size5 !== 0) {
- this.pushMapState(size5);
+ const size = this.readU32();
+ if (size !== 0) {
+ this.pushMapState(size);
this.complete();
continue DECODE;
} else {
object = {};
}
} else if (headByte === 0xc4) {
- const size6 = this.lookU8();
- object = this.decodeBinary(size6, 1);
+ const size = this.lookU8();
+ object = this.decodeBinary(size, 1);
} else if (headByte === 0xc5) {
- const size7 = this.lookU16();
- object = this.decodeBinary(size7, 2);
+ const size = this.lookU16();
+ object = this.decodeBinary(size, 2);
} else if (headByte === 0xc6) {
- const size8 = this.lookU32();
- object = this.decodeBinary(size8, 4);
+ const size = this.lookU32();
+ object = this.decodeBinary(size, 4);
} else if (headByte === 0xd4) {
object = this.decodeExtension(1, 0);
} else if (headByte === 0xd5) {
@@ -2999,14 +2990,14 @@ class Decoder {
} else if (headByte === 0xd8) {
object = this.decodeExtension(16, 0);
} else if (headByte === 0xc7) {
- const size9 = this.lookU8();
- object = this.decodeExtension(size9, 1);
+ const size = this.lookU8();
+ object = this.decodeExtension(size, 1);
} else if (headByte === 0xc8) {
- const size10 = this.lookU16();
- object = this.decodeExtension(size10, 2);
+ const size = this.lookU16();
+ object = this.decodeExtension(size, 2);
} else if (headByte === 0xc9) {
- const size11 = this.lookU32();
- object = this.decodeExtension(size11, 4);
+ const size = this.lookU32();
+ object = this.decodeExtension(size, 4);
} else {
throw new Error(`Unrecognized type byte: ${prettyByte(headByte)}`);
}
@@ -4458,7 +4449,7 @@ function globToRegExp(glob, { extended =true , globstar: globstarOption = true ,
else if (value == "lower") segment += "a-z";
else if (value == "print") segment += "\x20-\x7E";
else if (value == "punct") {
- segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_‘{|}~";
+ segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_ÔÇÿ{|}~";
} else if (value == "space") segment += "\\s\v";
else if (value == "upper") segment += "A-Z";
else if (value == "word") segment += "\\w";
@@ -5051,12 +5042,12 @@ function parseHistoryDetailId(id) {
uuid
};
} else if (coopRE.test(plainText)) {
- const [, uid1, timestamp1, uuid1] = plainText.match(coopRE);
+ const [, uid, timestamp, uuid] = plainText.match(coopRE);
return {
type: "CoopHistoryDetail",
- uid: uid1,
- timestamp: timestamp1,
- uuid: uuid1
+ uid,
+ timestamp,
+ uuid
};
} else {
throw new Error(`Invalid ID: ${plainText}`);
@@ -5081,55 +5072,75 @@ function urlSimplify(url) {
return url;
}
}
-async function loginManually({ newFetcher , prompts: { promptLogin } }) {
+async function loginSteps({ newFetcher }, step2) {
const fetch = newFetcher();
- const state = urlBase64Encode(random(36));
- const authCodeVerifier = urlBase64Encode(random(32));
- const authCvHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(authCodeVerifier));
- const authCodeChallenge = urlBase64Encode(authCvHash);
- const body = {
- "state": state,
- "redirect_uri": "npf71b963c1b7b6d119://auth",
- "client_id": "71b963c1b7b6d119",
- "scope": "openid user user.birthday user.mii user.screenName",
- "response_type": "session_token_code",
- "session_token_code_challenge": authCodeChallenge,
- "session_token_code_challenge_method": "S256",
- "theme": "login_form"
- };
- const url = "https://accounts.nintendo.com/connect/1.0.0/authorize?" + new URLSearchParams(body);
- const res = await fetch.get({
- url,
- headers: {
- "Host": "accounts.nintendo.com",
- "Connection": "keep-alive",
- "Cache-Control": "max-age=0",
- "Upgrade-Insecure-Requests": "1",
- "User-Agent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Mobile Safari/537.36",
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8n",
- "DNT": "1",
- "Accept-Encoding": "gzip,deflate,br"
+ if (!step2) {
+ const state = urlBase64Encode(random(36));
+ const authCodeVerifier = urlBase64Encode(random(32));
+ const authCvHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(authCodeVerifier));
+ const authCodeChallenge = urlBase64Encode(authCvHash);
+ const body = {
+ "state": state,
+ "redirect_uri": "npf71b963c1b7b6d119://auth",
+ "client_id": "71b963c1b7b6d119",
+ "scope": "openid user user.birthday user.mii user.screenName",
+ "response_type": "session_token_code",
+ "session_token_code_challenge": authCodeChallenge,
+ "session_token_code_challenge_method": "S256",
+ "theme": "login_form"
+ };
+ const url = "https://accounts.nintendo.com/connect/1.0.0/authorize?" + new URLSearchParams(body);
+ const res = await fetch.get({
+ url,
+ headers: {
+ "Host": "accounts.nintendo.com",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=0",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": DEFAULT_APP_USER_AGENT,
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8n",
+ "DNT": "1",
+ "Accept-Encoding": "gzip,deflate,br"
+ }
+ });
+ return {
+ authCodeVerifier,
+ url: res.url
+ };
+ } else {
+ const { login , authCodeVerifier } = step2;
+ const loginURL = new URL(login);
+ const params = new URLSearchParams(loginURL.hash.substring(1));
+ const sessionTokenCode = params.get("session_token_code");
+ if (!sessionTokenCode) {
+ throw new Error("No session token code provided");
}
- });
- const login = (await promptLogin(res.url)).trim();
+ const sessionToken = await getSessionToken({
+ fetch,
+ sessionTokenCode,
+ authCodeVerifier
+ });
+ if (!sessionToken) {
+ throw new Error("No session token found");
+ }
+ return {
+ sessionToken
+ };
+ }
+}
+async function loginManually(env) {
+ const { prompts: { promptLogin } } = env;
+ const step1 = await loginSteps(env);
+ const { url , authCodeVerifier } = step1;
+ const login = (await promptLogin(url)).trim();
if (!login) {
throw new Error("No login URL provided");
}
- const loginURL = new URL(login);
- const params = new URLSearchParams(loginURL.hash.substring(1));
- const sessionTokenCode = params.get("session_token_code");
- if (!sessionTokenCode) {
- throw new Error("No session token code provided");
- }
- const sessionToken = await getSessionToken({
- fetch,
- sessionTokenCode,
- authCodeVerifier
+ const step2 = await loginSteps(env, {
+ authCodeVerifier,
+ login
});
- if (!sessionToken) {
- throw new Error("No session token found");
- }
- return sessionToken;
+ return step2.sessionToken;
}
async function getGToken({ fApi , sessionToken , env }) {
const fetch = env.newFetcher();
@@ -5460,7 +5471,7 @@ class Profile {
}
var Queries;
(function(Queries) {
- Queries["HomeQuery"] = "dba47124d5ec3090c97ba17db5d2f4b3";
+ Queries["HomeQuery"] = "22e2fa8294168003c21b00c333c35384";
Queries["LatestBattleHistoriesQuery"] = "4f5f26e64bca394b45345a65a2f383bd";
Queries["RegularBattleHistoriesQuery"] = "d5b795d09e67ce153e622a184b7e7dfa";
Queries["BankaraBattleHistoriesQuery"] = "de4754588109b77dbcb90fbe44b612ee";
@@ -5468,7 +5479,7 @@ var Queries;
Queries["PrivateBattleHistoriesQuery"] = "1d6ed57dc8b801863126ad4f351dfb9a";
Queries["VsHistoryDetailQuery"] = "291295ad311b99a6288fc95a5c4cb2d2";
Queries["CoopHistoryQuery"] = "6ed02537e4a65bbb5e7f4f23092f6154";
- Queries["CoopHistoryDetailQuery"] = "3cc5f826a6646b85f3ae45db51bd0707";
+ Queries["CoopHistoryDetailQuery"] = "379f0d9b78b531be53044bcac031b34b";
Queries["myOutfitCommonDataFilteringConditionQuery"] = "d02ab22c9dccc440076055c8baa0fa7a";
Queries["myOutfitCommonDataEquipmentsQuery"] = "d29cd0c2b5e6bac90dd5b817914832f8";
Queries["HistoryRecordQuery"] = "32b6771f94083d8f04848109b7300af5";
@@ -5594,7 +5605,7 @@ class Splatnet3 {
return false;
}
try {
- await this.request(Queries.HomeQuery);
+ await this.request(Queries.ConfigureAnalyticsQuery);
return true;
} catch (_e) {
return false;
@@ -5843,8 +5854,8 @@ class StatInkAPI {
}
}
_getAliasName(name) {
- const STAT_INK_DOT = "·";
- const SPLATNET_DOT = "‧";
+ const STAT_INK_DOT = "┬À";
+ const SPLATNET_DOT = "";
if (name.includes(STAT_INK_DOT)) {
return [
name,
@@ -5894,12 +5905,6 @@ class StatInkExporter {
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
}
async exportGame(game) {
- if (game.type === "VsInfo" && this.isTriColor(game.detail)) {
- return {
- status: "skip",
- reason: "Tri-color fest is not supported"
- };
- }
if (game.type === "VsInfo") {
const body = await this.mapBattle(game);
const { url } = await this.api.postBattle(body);
@@ -5908,11 +5913,11 @@ class StatInkExporter {
url
};
} else {
- const body1 = await this.mapCoop(game);
- const { url: url1 } = await this.api.postCoop(body1);
+ const body = await this.mapCoop(game);
+ const { url } = await this.api.postCoop(body);
return {
status: "success",
- url: url1
+ url
};
}
}
@@ -5955,7 +5960,7 @@ class StatInkExporter {
} else if (modeId === 7) {
return "splatfest_challenge";
} else if (modeId === 8) {
- throw new Error("Tri-color battle is not supported");
+ return "splatfest_open";
}
} else if (vsMode === "X_MATCH") {
return "xmatch";
@@ -6009,6 +6014,7 @@ class StatInkExporter {
weapon: b64Number(player.weapon.id).toString(),
inked: player.paint,
gears: await this.mapGears(player),
+ crown: player.crown ? "yes" : "no",
disconnected: player.result ? "no" : "yes"
};
if (player.result) {
@@ -6016,6 +6022,7 @@ class StatInkExporter {
result.assist = player.result.assist;
result.kill = result.kill_or_assist - result.assist;
result.death = player.result.death;
+ result.signal = player.result.noroshiTry ?? undefined;
result.special = player.result.special;
}
return result;
@@ -6027,6 +6034,9 @@ class StatInkExporter {
throw new Error("Self not found");
}
const startedAt = Math.floor(new Date(playedTime).getTime() / 1000);
+ if (otherTeams.length === 0) {
+ throw new Error(`Other teams is empty`);
+ }
const result = {
uuid: await gameId(vsDetail.id),
lobby: this.mapLobby(vsDetail),
@@ -6038,7 +6048,7 @@ class StatInkExporter {
rank_in_team: vsDetail.myTeam.players.indexOf(self) + 1,
medals: vsDetail.awards.map((i)=>i.name),
our_team_players: await Promise.all(myTeam.players.map(this.mapPlayer)),
- their_team_players: await Promise.all(otherTeams.flatMap((i)=>i.players).map(this.mapPlayer)),
+ their_team_players: await Promise.all(otherTeams[0].players.map(this.mapPlayer)),
agent: AGENT_NAME,
agent_version: S3SI_VERSION,
agent_variables: {
@@ -6053,18 +6063,47 @@ class StatInkExporter {
result.assist = self.result.assist;
result.kill = result.kill_or_assist - result.assist;
result.death = self.result.death;
+ result.signal = self.result.noroshiTry ?? undefined;
result.special = self.result.special;
}
+ result.our_team_color = this.mapColor(myTeam.color);
+ result.their_team_color = this.mapColor(otherTeams[0].color);
+ if (otherTeams.length === 2) {
+ result.third_team_color = this.mapColor(otherTeams[1].color);
+ }
if (festMatch) {
result.fest_dragon = SPLATNET3_STATINK_MAP.DRAGON[festMatch.dragonMatchType];
result.clout_change = festMatch.contribution;
result.fest_power = festMatch.myFestPower ?? undefined;
}
- if (rule === "TURF_WAR") {
+ if (rule === "TURF_WAR" || rule === "TRI_COLOR") {
result.our_team_percent = (myTeam?.result?.paintRatio ?? 0) * 100;
result.their_team_percent = (otherTeams?.[0]?.result?.paintRatio ?? 0) * 100;
result.our_team_inked = myTeam.players.reduce((acc, i)=>acc + i.paint, 0);
result.their_team_inked = otherTeams?.[0].players.reduce((acc, i)=>acc + i.paint, 0);
+ if (myTeam.festTeamName) {
+ result.our_team_theme = myTeam.festTeamName;
+ }
+ if (myTeam.tricolorRole) {
+ result.our_team_role = myTeam.tricolorRole === "DEFENSE" ? "defender" : "attacker";
+ }
+ if (otherTeams[0].festTeamName) {
+ result.their_team_theme = otherTeams[0].festTeamName;
+ }
+ if (otherTeams[0].tricolorRole) {
+ result.their_team_role = otherTeams[0].tricolorRole === "DEFENSE" ? "defender" : "attacker";
+ }
+ if (otherTeams.length === 2) {
+ result.third_team_players = await Promise.all(otherTeams[1].players.map(this.mapPlayer));
+ result.third_team_percent = (otherTeams[1]?.result?.paintRatio ?? 0) * 100;
+ result.third_team_inked = otherTeams[1].players.reduce((acc, i)=>acc + i.paint, 0);
+ if (otherTeams[1].festTeamName) {
+ result.third_team_theme = otherTeams[1].festTeamName;
+ }
+ if (otherTeams[1].tricolorRole) {
+ result.third_team_role = otherTeams[1].tricolorRole === "DEFENSE" ? "defender" : "attacker";
+ }
+ }
}
if (knockout) {
result.knockout = knockout === "NEITHER" ? "no" : "yes";
@@ -6100,9 +6139,6 @@ class StatInkExporter {
result.rank_after_exp = rankState.rankPoint;
if (!bankaraMatchChallenge?.isUdemaeUp && result.rank_exp_change === undefined) {
result.rank_exp_change = result.rank_after_exp - result.rank_before_exp;
- } else if (bankaraMatchChallenge?.isUdemaeUp && bankaraMatchChallenge.earnedUdemaePoint) {
- result.rank_before_exp = result.rank_after_exp - bankaraMatchChallenge.earnedUdemaePoint;
- result.rank_exp_change = undefined;
}
if (!result.rank_after) {
[result.rank_after, result.rank_after_s_plus] = parseUdemae(rankState.rank);
@@ -6110,6 +6146,16 @@ class StatInkExporter {
}
return result;
}
+ mapColor(color) {
+ const float2hex = (i)=>Math.round(i * 255).toString(16).padStart(2, "0");
+ const nums = [
+ color.r,
+ color.g,
+ color.b,
+ color.a
+ ];
+ return nums.map(float2hex).join("");
+ }
isRandom(image) {
const RANDOM_FILENAME = "473fffb2442075078d8bb7125744905abdeae651b6a5b7453ae295582e45f7d1";
const url = image?.url;
@@ -6158,7 +6204,7 @@ class StatInkExporter {
name: player.name,
number: player.nameId,
splashtag_title: player.byname,
- uniform: SPLATNET3_STATINK_MAP.COOP_UNIFORM_MAP[b64Number(player.uniform.id)],
+ uniform: b64Number(player.uniform.id).toString(),
special: specialWeapon ? await this.mapSpecial(specialWeapon) : undefined,
weapons: await Promise.all(weapons.map((w)=>this.mapCoopWeapon(w))),
golden_eggs: goldenDeliverCount,
@@ -6310,7 +6356,7 @@ class FileExporter {
const { uid , timestamp } = parseHistoryDetailId(id);
return `${uid}_${timestamp}Z.json`;
}
- async exportedGames({ uid , type }) {
+ async exportedGames({ uid , type , filter }) {
const out = [];
for await (const entry of Deno.readDir(this.exportPath)){
const filename = entry.name;
@@ -6325,12 +6371,18 @@ class FileExporter {
continue;
}
if (body.type === "VS" && type === "VsInfo") {
+ if (filter && !filter(body.data)) {
+ continue;
+ }
out.push({
id: body.data.detail.id,
filepath,
timestamp
});
} else if (body.type === "COOP" && type === "CoopInfo") {
+ if (filter && !filter(body.data)) {
+ continue;
+ }
out.push({
id: body.data.detail.id,
filepath,
@@ -6405,6 +6457,29 @@ class FileExporter {
}
exportPath;
}
+const SEASONS = [
+ {
+ id: "season202209",
+ name: "Drizzle Season 2022",
+ start: new Date("2022-09-01T00:00:00+00:00"),
+ end: new Date("2022-12-01T00:00:00+00:00")
+ },
+ {
+ id: "season202212",
+ name: "Chill Season 2022",
+ start: new Date("2022-12-01T00:00:00+00:00"),
+ end: new Date("2023-03-01T00:00:00+00:00")
+ },
+ {
+ id: "season202303",
+ name: "Fresh Season 2023",
+ start: new Date("2023-03-01T00:00:00+00:00"),
+ end: new Date("2023-06-01T00:00:00+00:00")
+ }
+];
+const getSeason = (date)=>{
+ return SEASONS.find((s)=>s.start <= date && date < s.end);
+};
const splusParams = ()=>{
const out = [];
for(let i = 0; i < 50; i++){
@@ -6520,8 +6595,27 @@ const RANK_PARAMS = [
...splusParams()
];
function addRank(state, delta) {
+ if (!state) {
+ if (delta.isPromotion && delta.isRankUp) {
+ state = getRankStateByDelta(delta);
+ } else {
+ return;
+ }
+ }
+ if (state.gameId !== delta.before.gameId) {
+ throw new Error("Invalid state");
+ }
const { rank , rankPoint } = state;
const { gameId , timestamp , rankAfter , isPromotion , isRankUp , isChallengeFirst } = delta;
+ if (state.timestamp) {
+ const oldSeason = getSeason(new Date(state.timestamp * 1000));
+ if (oldSeason) {
+ const newSeason = getSeason(new Date(timestamp * 1000));
+ if (newSeason?.id !== oldSeason.id) {
+ return;
+ }
+ }
+ }
const rankIndex = RANK_PARAMS.findIndex((r)=>r.rank === rank);
if (rankIndex === -1) {
throw new Error(`Rank not found: ${rank}`);
@@ -6529,34 +6623,46 @@ function addRank(state, delta) {
const rankParam = RANK_PARAMS[rankIndex];
if (isChallengeFirst) {
return {
- gameId,
- timestamp,
- rank,
- rankPoint: rankPoint - rankParam.charge
+ before: state,
+ after: {
+ gameId,
+ timestamp,
+ rank,
+ rankPoint: rankPoint - rankParam.charge
+ }
};
}
if (rankIndex === RANK_PARAMS.length - 1) {
return {
- timestamp,
- gameId,
- rank,
- rankPoint: Math.min(rankPoint + delta.rankPoint, rankParam.pointRange[1])
+ before: state,
+ after: {
+ timestamp,
+ gameId,
+ rank,
+ rankPoint: Math.min(rankPoint + delta.rankPoint, rankParam.pointRange[1])
+ }
};
}
if (isPromotion && isRankUp) {
const nextRankParam = RANK_PARAMS[rankIndex + 1];
return {
- gameId,
- timestamp,
- rank: nextRankParam.rank,
- rankPoint: nextRankParam.pointRange[0]
+ before: state,
+ after: {
+ gameId,
+ timestamp,
+ rank: nextRankParam.rank,
+ rankPoint: nextRankParam.pointRange[0]
+ }
};
}
return {
- gameId,
- timestamp,
- rank: rankAfter ?? rank,
- rankPoint: rankPoint + delta.rankPoint
+ before: state,
+ after: {
+ gameId,
+ timestamp,
+ rank: rankAfter ?? rank,
+ rankPoint: rankPoint + delta.rankPoint
+ }
};
}
const battleTime = (id)=>{
@@ -6564,32 +6670,56 @@ const battleTime = (id)=>{
const dateStr = timestamp.replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/, "$1-$2-$3T$4:$5:$6Z");
return new Date(dateStr);
};
-function generateDeltaList(state, flatten) {
- const index = flatten.findIndex((i)=>i.gameId === state.gameId);
- if (index === -1) {
- return;
+function beginPoint(state, flatten) {
+ if (state) {
+ const index = flatten.findIndex((i)=>i.gameId === state.gameId);
+ if (index !== -1) {
+ return [
+ flatten[index],
+ flatten.slice(index)
+ ];
+ }
}
- const unProcessed = flatten.slice(index);
+ if (flatten.length === 0) {
+ throw new Error("flatten must not be empty");
+ }
+ return [
+ flatten[0],
+ flatten
+ ];
+}
+function getTimestamp(date) {
+ return Math.floor(date.getTime() / 1000);
+}
+function generateDeltaList(state, flatten) {
+ const [firstItem, unProcessed] = beginPoint(state, flatten);
const deltaList = [];
- let beforeGameId = state.gameId;
+ let before = {
+ gameId: firstItem.gameId,
+ timestamp: getTimestamp(firstItem.time)
+ };
for (const i of unProcessed.slice(1)){
if (!i.detail.bankaraMatch) {
throw new TypeError("bankaraMatch must be defined");
}
let delta = {
- beforeGameId,
+ before,
gameId: i.gameId,
- timestamp: Math.floor(i.time.getTime() / 1000),
+ timestamp: getTimestamp(i.time),
rankPoint: 0,
isPromotion: false,
isRankUp: false,
isChallengeFirst: false
};
- beforeGameId = i.gameId;
+ before = {
+ gameId: i.gameId,
+ timestamp: Math.floor(i.time.getTime() / 1000)
+ };
if (i.bankaraMatchChallenge) {
if (i.index === 0 && i.bankaraMatchChallenge.state !== "INPROGRESS") {
delta = {
...delta,
+ rank: i.detail.udemae,
rankAfter: i.bankaraMatchChallenge.udemaeAfter ?? undefined,
rankPoint: i.bankaraMatchChallenge.earnedUdemaePoint ?? 0,
isPromotion: i.bankaraMatchChallenge.isPromo ?? false,
@@ -6611,52 +6741,42 @@ function generateDeltaList(state, flatten) {
}
deltaList.push(delta);
}
- return deltaList;
+ return {
+ firstItem,
+ deltaList
+ };
}
-function getRankState(i) {
- const rank = i.detail.udemae;
- if (!rank) {
- throw new Error("rank must be defined");
+function getRankStateByDelta(i) {
+ const rank = i.rank;
+ const nextRank = i.rankAfter;
+ const earnedUdemaePoint = i.rankPoint;
+ if (!rank || !nextRank) {
+ throw new Error("rank and nextRank must be defined");
}
const param = RANK_PARAMS.find((i)=>i.rank === rank);
- if (!param) {
- throw new Error(`Rank not found: ${rank}`);
+ const nextParam = RANK_PARAMS.find((i)=>i.rank === nextRank);
+ if (!param || !nextParam) {
+ throw new Error(`Rank or nextRank not found: ${rank} ${nextRank}`);
}
+ const oldRankPoint = nextParam.pointRange[0] - earnedUdemaePoint;
return {
- gameId: i.gameId,
- timestamp: Math.floor(i.time.getTime() / 1000),
+ gameId: i.before.gameId,
+ timestamp: i.before.timestamp,
rank,
- rankPoint: -1
+ rankPoint: oldRankPoint
};
}
class RankTracker {
deltaMap;
+ stateMap;
constructor(state){
this.state = state;
this.deltaMap = new Map();
+ this.stateMap = new Map();
}
async getRankStateById(id) {
- if (!this.state) {
- return;
- }
const gid = await gameId(id);
- let cur = this.state;
- let before = cur;
- if (cur.gameId === gid) {
- return;
- }
- while(cur.gameId !== gid){
- const delta = this.deltaMap.get(cur.gameId);
- if (!delta) {
- return;
- }
- before = cur;
- cur = addRank(cur, delta);
- }
- return {
- before,
- after: cur
- };
+ return this.stateMap.get(gid);
}
setState(state) {
this.state = state;
@@ -6676,37 +6796,18 @@ class RankTracker {
...i,
gameId
}))));
- const gameIdTime = new Map(flatten.map((i)=>[
- i.gameId,
- i.time
- ]));
- let curState;
- const oldestPromotion = flatten.find((i)=>i.bankaraMatchChallenge?.isPromo && i.bankaraMatchChallenge.isUdemaeUp);
- const thisStateTime = gameIdTime.get(this.state?.gameId);
- if (!thisStateTime && !oldestPromotion) {
- return;
- } else if (thisStateTime && !oldestPromotion) {
- curState = this.state;
- } else if (!thisStateTime && oldestPromotion) {
- curState = getRankState(oldestPromotion);
- } else if (thisStateTime && oldestPromotion) {
- if (thisStateTime <= oldestPromotion.time) {
- curState = this.state;
- } else {
- curState = getRankState(oldestPromotion);
- }
- }
- if (!curState) {
- return;
- }
- this.state = curState;
- const deltaList = generateDeltaList(curState, flatten);
- if (!deltaList) {
+ let curState = this.state;
+ const { firstItem , deltaList } = generateDeltaList(curState, flatten);
+ if (curState && firstItem.gameId !== curState.gameId) {
return;
}
for (const delta of deltaList){
- this.deltaMap.set(delta.beforeGameId, delta);
- curState = addRank(curState, delta);
+ this.deltaMap.set(delta.before.gameId, delta);
+ const result = addRank(curState, delta);
+ curState = result?.after;
+ if (result) {
+ this.stateMap.set(result.after.gameId, result);
+ }
}
return curState;
}
@@ -7072,26 +7173,26 @@ class App {
} else {
this.env.logger.log("Fetching coop battle list...");
const coopBattleList = await splatnet.getBattleList(BattleListType.Coop);
- const { redraw: redraw1 , endBar: endBar1 } = this.exporterProgress("Export coop games");
- const fetcher1 = new GameFetcher({
+ const { redraw , endBar } = this.exporterProgress("Export coop games");
+ const fetcher = new GameFetcher({
cache: this.opts.cache ?? new FileCache(this.profile.state.cacheDir),
state: this.profile.state,
splatnet
});
await Promise.all(exporters.map((e)=>showError(this.env, this.exportGameList({
type: "CoopInfo",
- fetcher: fetcher1,
+ fetcher,
exporter: e,
gameList: coopBattleList,
stepProgress: stats[e.name],
onStep: ()=>{
- redraw1(e.name, progress(stats[e.name]));
+ redraw(e.name, progress(stats[e.name]));
}
})).catch((err)=>{
errors.push(err);
this.env.logger.error(`\nFailed to export to ${e.name}:`, err);
})));
- endBar1();
+ endBar();
this.printStats(stats);
if (errors.length > 0) {
throw errors[0];
diff --git a/source/plugins/community/splatoon/token.ts b/source/plugins/community/splatoon/token.ts
index 71641c91..dff26365 100644
--- a/source/plugins/community/splatoon/token.ts
+++ b/source/plugins/community/splatoon/token.ts
@@ -31,15 +31,16 @@ const args = [
'--exporter=none'
]
try {
- await Deno.spawn("deno", {
+ const command = new Deno.Command("deno", {
args,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
windowsRawArguments:true
})
+ await command.output()
}
-catch {}
+catch (error) { console.log(error) }
//Extract profile.json and print instructions
try {
diff --git a/source/plugins/steam/README.md b/source/plugins/steam/README.md
new file mode 100644
index 00000000..8d720a2a
--- /dev/null
+++ b/source/plugins/steam/README.md
@@ -0,0 +1,22 @@
+
+
+
+## ➡️ Available 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:
+
+
+
+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:
+
+
+
+## ℹ️ Examples workflows
+
+
+
diff --git a/source/plugins/steam/examples.yml b/source/plugins/steam/examples.yml
new file mode 100644
index 00000000..8a125ba7
--- /dev/null
+++ b/source/plugins/steam/examples.yml
@@ -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
\ No newline at end of file
diff --git a/source/plugins/steam/index.mjs b/source/plugins/steam/index.mjs
new file mode 100644
index 00000000..209eeef2
--- /dev/null
+++ b/source/plugins/steam/index.mjs
@@ -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)
+ }
+}
diff --git a/source/plugins/steam/metadata.yml b/source/plugins/steam/metadata.yml
new file mode 100644
index 00000000..d0b51951
--- /dev/null
+++ b/source/plugins/steam/metadata.yml
@@ -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
+
diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json
index e8523ae5..a7886e43 100644
--- a/source/templates/classic/partials/_.json
+++ b/source/templates/classic/partials/_.json
@@ -42,5 +42,6 @@
"sponsorships",
"poopmap",
"fortune",
- "splatoon"
+ "splatoon",
+ "steam"
]
diff --git a/source/templates/classic/partials/steam.ejs b/source/templates/classic/partials/steam.ejs
new file mode 100644
index 00000000..9d1345e3
--- /dev/null
+++ b/source/templates/classic/partials/steam.ejs
@@ -0,0 +1,108 @@
+<% if (plugins.steam) { %>
+
+
+ Steam
+
+ <% if (plugins.steam.error) { %>
+
+
+ <%= {"most-played":"Most played", "recently-played":"Recently played"}[section] %>
+
+
+
+

The NieR:Automata™ Game of the YoRHa Edition includes the game itself and comes packed with DLC and bonus content for the full experience of the award-winning post-apocalyptic action RPG, including:

The NieR: Automata 3C3C1D119440927 DLC is out now and includes three new colosseums to challenge, plus additional sub-quests. Upon completion of these quests, players can earn various rewards including new costumes from NieR: Replicant, new equipment and cosmetic accessories such as masks, hairspray that change the color of your character, records that add special music tracks to the players’ jukebox and much more!<\/p>
9\/10 "Don’t miss this"<\/i> – VideoGamer<\/a>
10\/10 "One of the best games I’ve ever played"<\/i> – RPGSite<\/a>
4\/5 "Pure genius"<\/i> – Trusted Reviews<\/a>
9\/10 "One of the most interesting and unique games you’ll play this year"<\/i> – God is a Geek<\/a>
9\/10 "Classic Platinum action combined with a deep role-playing system"<\/i> – Metro<\/a><\/p>About the Game<\/h1>https:\/\/store.steampowered.com\/app\/1113560\/NieR_Replicant_ver122474487139\/<\/a>

NieR: Automata tells the story of androids 2B, 9S and A2 and their battle to reclaim the machine-driven dystopia overrun by powerful machines.
Humanity has been driven from the Earth by mechanical beings from another world. In a final effort to take back the planet, the human resistance sends a force of android soldiers to destroy the invaders. Now, a war between machines and androids rages on... A war that could soon unveil a long-forgotten truth of the world.
Key Features:
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
NieR: Automata tells the story of androids 2B, 9S and A2 and their battle to reclaim the machine-driven dystopia overrun by powerful machines.
Humanity has been driven from the Earth by mechanical beings from another world. In a final effort to take back the planet, the human resistance sends a force of android soldiers to destroy the invaders. Now, a war between machines and androids rages on... A war that could soon unveil a long-forgotten truth of the world.
Key Features:
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
*<\/strong>languages with full audio support",
+ "reviews":
+ '“Riveting and gratifying combat”
9\/10 – Gamespot<\/a>
“A breath of fresh air”
4.5\/5 – GamesRadar+<\/a>
“Bold, ambitious, and surprising”
Recommended – Eurogamer<\/a>
',
+ "header_image": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/524220\/header.jpg?t=1646911723",
+ "website": "http:\/\/www.niergame.com\/",
+ "pc_requirements": {
+ "minimum":
+ 'Minimum:<\/strong>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>Reviews & Accolades<\/h1>
<\/p>4 YoRHa<\/h1>

4 YoRHa is a free downloadable content.
Enjoy 4 costumes and 4 weapons from NieR:Automata in NieR Replicant ver.1.22474487139....
*Due to the story of the game, this content may not be playable during some scenes.
*The full game (sold separately) is required to use this content. Also, if the latest patch needs to be applied, please update before use.<\/p>About the Game<\/h1>

A thousand-year lie that would live on for eternity...
NieR Replicant ver.1.22474487139... is an updated version of NieR Replicant, previously only released in Japan.
Discover the one-of-a-kind prequel to the critically-acclaimed masterpiece NieR:Automata. Now with a modern upgrade, experience masterfully revived visuals, a fascinating storyline and more!
The protagonist is a kind young man living in a remote village. In order to save his sister Yonah, who fell terminally ill to the Black Scrawl, he sets out with Grimoire Weiss, a strange talking tome, to search for the "Sealed verses."
Experience the NieR Replicant story for the first time in the west through the eyes of the protagonist as a brother.
The original all-star team returns including acclaimed director, YOKO TARO (Drakengard \/ NieR:Automata), composer Keiichi Okabe (TEKKEN \/ Drakengard \/ NieR:Automata), and producer Yosuke Saito (DRAGON QUEST X \/ NieR:Automata).
In a distant, distant future, humanity is on the brink of extinction.
A black scrawl disease and strange beasts threaten the world.
A young kindhearted boy makes a promise to his little sister.
A thousand-year lie that would live on for eternity...
<\/li>
<\/li>
<\/li>
<\/li>
A thousand-year lie that would live on for eternity...
NieR Replicant ver.1.22474487139... is an updated version of NieR Replicant, previously only released in Japan.
Discover the one-of-a-kind prequel to the critically-acclaimed masterpiece NieR:Automata. Now with a modern upgrade, experience masterfully revived visuals, a fascinating storyline and more!
The protagonist is a kind young man living in a remote village. In order to save his sister Yonah, who fell terminally ill to the Black Scrawl, he sets out with Grimoire Weiss, a strange talking tome, to search for the "Sealed verses."
Experience the NieR Replicant story for the first time in the west through the eyes of the protagonist as a brother.
The original all-star team returns including acclaimed director, YOKO TARO (Drakengard \/ NieR:Automata), composer Keiichi Okabe (TEKKEN \/ Drakengard \/ NieR:Automata), and producer Yosuke Saito (DRAGON QUEST X \/ NieR:Automata).
In a distant, distant future, humanity is on the brink of extinction.
A black scrawl disease and strange beasts threaten the world.
A young kindhearted boy makes a promise to his little sister.
A thousand-year lie that would live on for eternity...
<\/li>
<\/li>
<\/li>
<\/li>
*<\/strong>languages with full audio support",
+ "reviews":
+ '“Lives up to the hype”
4.5\/5 – Windows Central<\/a>
“Incredible, refined version”
5\/5 – https:\/\/www.thegamer.com\/nier-replicant-remake-review\/<\/a>
“Ambitious, fascinating game”
10\/10 – GAMINGbible<\/a>
',
+ "header_image": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/header.jpg?t=1673963725",
+ "website": "https:\/\/nier.square-enix-games.com\/",
+ "pc_requirements": {
+ "minimum":
+ 'Minimum:<\/strong>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
<\/li>
\r\nNIER REPLICANT is a registered trademark or trademark of Square Enix Co., Ltd. SQUARE ENIX and the SQUARE ENIX logo are registered trademarks or trademarks of Square Enix Holdings Co., Ltd.",
+ "developers": [
+ "Square Enix",
+ "Toylogic Inc.",
+ ],
+ "publishers": [
+ "Square Enix",
+ ],
+ "price_overview": {
+ "currency": "CAD",
+ "initial": 7999,
+ "final": 7999,
+ "discount_percent": 0,
+ "initial_formatted": "",
+ "final_formatted": "CDN$ 79.99",
+ },
+ "packages": [
+ 502733,
+ ],
+ "package_groups": [
+ {
+ "name": "default",
+ "title": "Buy NieR Replicant™ ver.1.22474487139...",
+ "description": "",
+ "selection_text": "Select a purchase option",
+ "save_text": "",
+ "display_type": 0,
+ "is_recurring_subscription": "false",
+ "subs": [
+ {
+ "packageid": 502733,
+ "percent_savings_text": " ",
+ "percent_savings": 0,
+ "option_text": "NieR Replicant ver.1.22474487139 - CDN$ 79.99",
+ "option_description": "",
+ "can_get_free_license": "0",
+ "is_free_license": false,
+ "price_in_cents_with_discount": 7999,
+ },
+ ],
+ },
+ ],
+ "platforms": {
+ "windows": true,
+ "mac": false,
+ "linux": false,
+ },
+ "categories": [
+ {
+ "id": 2,
+ "description": "Single-player",
+ },
+ {
+ "id": 22,
+ "description": "Steam Achievements",
+ },
+ {
+ "id": 29,
+ "description": "Steam Trading Cards",
+ },
+ {
+ "id": 18,
+ "description": "Partial Controller Support",
+ },
+ {
+ "id": 23,
+ "description": "Steam Cloud",
+ },
+ ],
+ "genres": [
+ {
+ "id": "1",
+ "description": "Action",
+ },
+ {
+ "id": "25",
+ "description": "Adventure",
+ },
+ {
+ "id": "3",
+ "description": "RPG",
+ },
+ ],
+ "screenshots": [
+ {
+ "id": 0,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_2ff3ddb26c30b8397bce45abd0b4d847c3457259.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_2ff3ddb26c30b8397bce45abd0b4d847c3457259.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 1,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_f33bdbe53fa2d75e429f9b35a1299109c9ab8991.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_f33bdbe53fa2d75e429f9b35a1299109c9ab8991.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 2,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_b7867dae1f1fa62a2cc82165c8c79eb6821782d6.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_b7867dae1f1fa62a2cc82165c8c79eb6821782d6.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 3,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_8d7a5e5a0a7fe7782bf238763a2e29f8f6419e84.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_8d7a5e5a0a7fe7782bf238763a2e29f8f6419e84.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 4,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_f7689ef6964eee2407d9e996bc73b380e3e7a56a.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_f7689ef6964eee2407d9e996bc73b380e3e7a56a.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 5,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_6740ec558094fafc86f0933264e50e796c21e9cf.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_6740ec558094fafc86f0933264e50e796c21e9cf.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 6,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_eb6b583db6d9b3051c131ba748c768b2d6226277.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_eb6b583db6d9b3051c131ba748c768b2d6226277.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 7,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_c93af51b782d72cf081d4e8451836ad1716f63be.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_c93af51b782d72cf081d4e8451836ad1716f63be.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 8,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_56ecf25d6d95441a23f5481f895771c2dcb9ac18.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_56ecf25d6d95441a23f5481f895771c2dcb9ac18.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 9,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_a2c004272402bf76b70ed13c920fbd3c85d43d94.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_a2c004272402bf76b70ed13c920fbd3c85d43d94.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 10,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_0621ab7f10a0b23f4124509a13a8ace0487d856a.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_0621ab7f10a0b23f4124509a13a8ace0487d856a.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 11,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_ada29940be1bdb0e8b70e64788044f4c2592657f.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_ada29940be1bdb0e8b70e64788044f4c2592657f.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 12,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_1c972f496956e106c3b9023c68ff309fd649407e.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_1c972f496956e106c3b9023c68ff309fd649407e.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 13,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_344cfa42952beb6d51540d43bdfcbeaa0949c9a7.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_344cfa42952beb6d51540d43bdfcbeaa0949c9a7.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 14,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_2a5576351e2f84518dec11a796fbff609f4e5b9f.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_2a5576351e2f84518dec11a796fbff609f4e5b9f.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 15,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_071609b98112f7621067dd5275ce0ead665f13f2.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_071609b98112f7621067dd5275ce0ead665f13f2.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 16,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_b3a1dfdbb9d4a47b6407de5c8b1dc75ddfcea931.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_b3a1dfdbb9d4a47b6407de5c8b1dc75ddfcea931.1920x1080.jpg?t=1673963725",
+ },
+ {
+ "id": 17,
+ "path_thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_942e62e6f1bf312148a7480f943babe2325a3714.600x338.jpg?t=1673963725",
+ "path_full": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/ss_942e62e6f1bf312148a7480f943babe2325a3714.1920x1080.jpg?t=1673963725",
+ },
+ ],
+ "movies": [
+ {
+ "id": 256831765,
+ "name": "ESRB EN Launch NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831765\/movie.293x165.jpg?t=1619194859",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831765\/movie480_vp9.webm?t=1619194859",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831765\/movie_max_vp9.webm?t=1619194859",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831765\/movie480.mp4?t=1619194859",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831765\/movie_max.mp4?t=1619194859",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256831398,
+ "name": "PEGI FR Gestalt NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831398\/movie.293x165.jpg?t=1619194237",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831398\/movie480_vp9.webm?t=1619194237",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831398\/movie_max_vp9.webm?t=1619194237",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831398\/movie480.mp4?t=1619194237",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831398\/movie_max.mp4?t=1619194237",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256831395,
+ "name": "ESRB EN Extra content NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831395\/movie.293x165.jpg?t=1619194332",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831395\/movie480_vp9.webm?t=1619194332",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831395\/movie_max_vp9.webm?t=1619194332",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831395\/movie480.mp4?t=1619194332",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256831395\/movie_max.mp4?t=1619194332",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256827494,
+ "name": "ESRB EN OpeningCinematic NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256827494\/movie.293x165.jpg?t=1616752558",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256827494\/movie480_vp9.webm?t=1616752558",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256827494\/movie_max_vp9.webm?t=1616752558",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256827494\/movie480.mp4?t=1616752558",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256827494\/movie_max.mp4?t=1616752558",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256813164,
+ "name": "ESRB EN TGA 2020 NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256813164\/movie.293x165.jpg?t=1607687616",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256813164\/movie480_vp9.webm?t=1607687616",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256813164\/movie_max_vp9.webm?t=1607687616",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256813164\/movie480.mp4?t=1607687616",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256813164\/movie_max.mp4?t=1607687616",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256802321,
+ "name": "ESRB EN TGS NierRep",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256802321\/movie.293x165.jpg?t=1600963175",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256802321\/movie480_vp9.webm?t=1600963175",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256802321\/movie_max_vp9.webm?t=1600963175",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256802321\/movie480.mp4?t=1600963175",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256802321\/movie_max.mp4?t=1600963175",
+ },
+ "highlight": true,
+ },
+ {
+ "id": 256782057,
+ "name": "ESRB EN - Teaser",
+ "thumbnail": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256782057\/movie.293x165.jpg?t=1600964179",
+ "webm": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256782057\/movie480_vp9.webm?t=1600964179",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256782057\/movie_max_vp9.webm?t=1600964179",
+ },
+ "mp4": {
+ "480": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256782057\/movie480.mp4?t=1600964179",
+ "max": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/256782057\/movie_max.mp4?t=1600964179",
+ },
+ "highlight": true,
+ },
+ ],
+ "recommendations": {
+ "total": 15065,
+ },
+ "achievements": {
+ "total": 47,
+ "highlighted": [
+ {
+ "name": "The Final Verse",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/a9369e5eddeb1486ffd849aa3820607353ddbc23.jpg",
+ },
+ {
+ "name": "The Book of Legend",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/6d11bfa34275061352048988a9202c3194259851.jpg",
+ },
+ {
+ "name": "The Wild Companion",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/8ac7d0d717ccda449162f94f81054073e9679fc5.jpg",
+ },
+ {
+ "name": "The Mellow Companion",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/1981aa8fe7fb43d11720331503fa3c9028f0e18a.jpg",
+ },
+ {
+ "name": "Release",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/88d76547290c9c200536cbb1a48d255303010a5b.jpg",
+ },
+ {
+ "name": "Gratitude",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/d9b8fd4cc2e4fc270edf18b177729b07e8a32cc9.jpg",
+ },
+ {
+ "name": "Key Collector",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/6629579d96114c2a268898911634495f8235ce08.jpg",
+ },
+ {
+ "name": "A World in Flux",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/4b3b4bcf8267f93bb73600cc688323c2c910e0b5.jpg",
+ },
+ {
+ "name": "Combo Fanatic",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/d19d83d2254a60554435a1e1d99896e96bdf64d5.jpg",
+ },
+ {
+ "name": "Combo Master",
+ "path": "https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/1113560\/d62e08ce00fd14c0fb72acebdfb09c838803a102.jpg",
+ },
+ ],
+ },
+ "release_date": {
+ "coming_soon": false,
+ "date": "23 Apr, 2021",
+ },
+ "support_info": {
+ "url": "https:\/\/support.square-enix-games.com\/",
+ "email": "",
+ },
+ "background": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/page_bg_generated_v6b.jpg?t=1673963725",
+ "background_raw": "https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/1113560\/page.bg.jpg?t=1673963725",
+ "content_descriptors": {
+ "ids": [
+ 2,
+ 5,
+ ],
+ "notes": "NieR Replicant ver.1.22474487139… contains adult scenes and theme, strong language in certain areas of the game, including blood, violence, criminal acts and killing. ",
+ },
+ },
+ },
+ },
+ })
+ }
+
+ //Steam api
+ if (/^https:..api.steampowered.com.*$/.test(url)) {
+ //Owned games
+ if (/^.*IPlayerService.GetOwnedGames.v0001.*$/.test(url)) {
+ console.debug(`metrics/compute/mocks > mocking steam api result > ${url}`)
+ return ({
+ status: 200,
+ data: {
+ "response": {
+ "game_count": 2,
+ "games": [
+ {
+ "appid": 524220,
+ "name": "NieR:Automata™",
+ "playtime_forever": 2693,
+ "img_icon_url": "ec431ecb2a5178c5a01bb15550f112f93af029bb",
+ "has_community_visible_stats": true,
+ "playtime_windows_forever": 3,
+ "playtime_mac_forever": 0,
+ "playtime_linux_forever": 0,
+ "rtime_last_played": 1582407120,
+ },
+ {
+ "appid": 1113560,
+ "name": "NieR Replicant ver.1.22474487139...",
+ "playtime_forever": 2661,
+ "img_icon_url": "8ca16d03995179d1ba3a2263e03db170100c6382",
+ "has_community_visible_stats": true,
+ "playtime_windows_forever": 2661,
+ "playtime_mac_forever": 0,
+ "playtime_linux_forever": 0,
+ "rtime_last_played": 1625611102,
+ "content_descriptorids": [
+ 2,
+ 5,
+ ],
+ },
+ ],
+ },
+ },
+ })
+ }
+ //Game schema
+ if (/^.*ISteamUserStats.GetSchemaForGame.v0002.*$/.test(url)) {
+ console.debug(`metrics/compute/mocks > mocking steam api result > ${url}`)
+ return ({
+ status: 200,
+ data: /appid=524220/.test(url)
+ ? {
+ "game": {
+ "gameName": "TRAIN",
+ "gameVersion": "14",
+ "availableGameStats": {
+ "achievements": [
+ {
+ "name": "ACH_VISITED_BUNKER",
+ "defaultvalue": 0,
+ "displayName": "Resuscitated Body",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/3396a4732f6c521900195182eadc9c523988e271.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_VISITED_RUINS_CITY",
+ "defaultvalue": 0,
+ "displayName": "Vestiges of Prosperity",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/a68518be86868a69b68d64b9720d65a33068713f.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_DESERT_AREA",
+ "defaultvalue": 0,
+ "displayName": "It's a Healthy Baby Boy!",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/9c038749e650134ddbe142a76c7a920e98947a82.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_AMUSEMENT_PARK",
+ "defaultvalue": 0,
+ "displayName": "We Await Your Next Visit",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/e028dca2f5773e58e6a36c0bbb5e3dfc4de1806a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_ALIEN_SHIP",
+ "defaultvalue": 0,
+ "displayName": "Creation and Insurrection",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/52cadba291448ae488147c0a31e9ffddd64adab7.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_CASTLE_FOREST",
+ "defaultvalue": 0,
+ "displayName": "The Mechanical Kingdom",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/f57deb02acdf63c9cf9adc21abb5a3158db77cfa.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_SUBMERGE_CITY",
+ "defaultvalue": 0,
+ "displayName": "Ruler of the Deep",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/6af7569b29b4f6f2b62f4a8d4b3be6f25183e8da.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_COPY_CITY",
+ "defaultvalue": 0,
+ "displayName": "Those Who Love Humans",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/ef85393debcb1c87e0f48f50b8afd89305d65c73.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_ROBOT_MT",
+ "defaultvalue": 0,
+ "displayName": "Iron Soul",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/80808a96458c14f827bb66a12c1ef0a5c877b9d4.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_A",
+ "defaultvalue": 0,
+ "displayName": "One Battle Ends",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5172a623c717e83a83c41b52f52e9b39077f0289.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_B",
+ "defaultvalue": 0,
+ "displayName": "A New Battle Begins",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/91df4ff96e60f0f0434afd1105c82a59dddb20a9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_SEE_2B_DIE",
+ "defaultvalue": 0,
+ "displayName": "Final Wish",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/9337b450a269e175ac5c96606237f7b5cedf52e4.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_PLAY_A2_FIRST_TIME",
+ "defaultvalue": 0,
+ "displayName": "Treacherous Blade",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/f8bbe8253972c94d1692dcc3733e98f37c0fd92e.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_LAST_REQUEST_PASCAL",
+ "defaultvalue": 0,
+ "displayName": "Farewell, Pascal",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/392b60acb70e7d048edbd4de5cfb5bb39ccc524c.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_KILL_ROBOT_BROTHER",
+ "defaultvalue": 0,
+ "displayName": "Justice",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/c81d259248687088bc59cdb50a668434bbb1e246.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_DEVOLA_NOVEL_END",
+ "defaultvalue": 0,
+ "displayName": "Crime and Punishment",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/9eb6d496221f65495a09764d3ae1ec453f12208f.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_9S",
+ "defaultvalue": 0,
+ "displayName": "Leaving for the New World",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/f92c92e085c3d6edec542f25b3561d8eb810c5d7.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_A2",
+ "defaultvalue": 0,
+ "displayName": "Beautiful World",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/c36712e0b97b65ee1ecb027db8e3ff8798d3bb55.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_TRUE_END",
+ "defaultvalue": 0,
+ "displayName": "The Minds That Emerged",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/2989eb6c7f2918ecfc7c756e401a271c17157c74.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_RECOVERY_SELF_CORPSE",
+ "defaultvalue": 0,
+ "displayName": "The Circle of Death",
+ "hidden": 0,
+ "description": "Have your body collected.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/076ec7fa27bb95797b07da45c3b65af5c3a447a9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_RECOVERY_CORPSE",
+ "defaultvalue": 0,
+ "displayName": "Cherish Our Resources",
+ "hidden": 0,
+ "description": "Have 100 bodies collected.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/1940860e6bd542954652853f5fbbe8bcd58fbac4.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_QUEST_FIRST_TIME",
+ "defaultvalue": 0,
+ "displayName": "First Errand",
+ "hidden": 0,
+ "description": "Complete your first quest.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/f31eb13d16a028fb3de23f5623955b8441e57c7c.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_MANY_QUEST",
+ "defaultvalue": 0,
+ "displayName": "The Mercenary",
+ "hidden": 0,
+ "description": "80% of all quests completed.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/bbdd00f461406cbf64726d753dac5f3abe066ef8.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_COLLECT_ARCHIVE",
+ "defaultvalue": 0,
+ "displayName": "Information Master",
+ "hidden": 0,
+ "description": "80% of all archives found.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/8d536964d4eabac2027adfc2705661fea25ede6b.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_COLLECT_EM_LIST",
+ "defaultvalue": 0,
+ "displayName": "Destruction is My Job",
+ "hidden": 0,
+ "description": "80% of all unit data unlocked.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/8048ddee99c03defd4562520d28bc4445938bda0.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_COLLECT_CHIP",
+ "defaultvalue": 0,
+ "displayName": "Chip Collector",
+ "hidden": 0,
+ "description": "80% of all plug-in chips collected.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/c8600b37d39a3d09ba4730a480d4ea5c87790a22.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_HAS_ALL_ACTIVE_SKILL",
+ "defaultvalue": 0,
+ "displayName": "Weapons Maniac",
+ "hidden": 0,
+ "description": "All Pod programs obtained.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/bd78a999e5b0eea50d60e7e1158a93a58d82a517.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_WEAPON_LV_MAX",
+ "defaultvalue": 0,
+ "displayName": "Tools of the Trade",
+ "hidden": 0,
+ "description": "Any weapon upgraded to the highest level.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/6bd93e150b03f8de3c857e3589a4e92eed0b03cc.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_WEAPON_LV_MAX_ALL",
+ "defaultvalue": 0,
+ "displayName": "Inorganic Blade",
+ "hidden": 0,
+ "description": "All weapons upgraded to the highest level.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/54bf4e0bef1e06348ccc291d1e87d49ce58e63db.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_POD_LV_MAX_ALL",
+ "defaultvalue": 0,
+ "displayName": "Supreme Support Weapons",
+ "hidden": 0,
+ "description": "All Pods upgraded to the highest level.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/254d29f9ddf775427dec4ea2e2456ee32100f0bc.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CLEAR_HACKING_9S",
+ "defaultvalue": 0,
+ "displayName": "Fighting's Not My Thing",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/aa7810edbe2376ebe0456f553bd3d33a139bd539.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_HACKING_KILL",
+ "defaultvalue": 0,
+ "displayName": "A Scanner's Power",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/a8077834157c6407d5855157d0a902f5415edf07.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_REMOTO_KILL",
+ "defaultvalue": 0,
+ "displayName": "Machines vs. Machines",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/ac4bc0d5ea71c94f14b5b6d18c7aee30804fff55.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_BERSERKER_KILL",
+ "defaultvalue": 0,
+ "displayName": "The Power of Hate",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/ca0c9026a53cc307519c09b43b198823bd4a28b5.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_SHOOTING",
+ "defaultvalue": 0,
+ "displayName": "Ruler of the Skies",
+ "hidden": 0,
+ "description": "255 enemies destroyed using a flight unit.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/ed342c90ae36c5655e9e5ff2bef89162a5af8f62.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_EXTRACTION",
+ "defaultvalue": 0,
+ "displayName": "Harvest King",
+ "hidden": 0,
+ "description": "Materials gathered at a hidden harvest point 10 times.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/7434e8cf28fa79d62acf02c37359155c42223bef.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_GET_ALL_POD",
+ "defaultvalue": 0,
+ "displayName": "Pod Hunter",
+ "hidden": 0,
+ "description": "All Pods found.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/1f40b92b07294e7c25ef3b1d19dfb14f452e662c.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_RICH_MAN",
+ "defaultvalue": 0,
+ "displayName": "Desire Without Emotion",
+ "hidden": 0,
+ "description": "At least 100,000 G in possession.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/e899966832c71f405fc33a0e0302212efc66ee5a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_ANIMAL_RUN",
+ "defaultvalue": 0,
+ "displayName": "Animal Rider",
+ "hidden": 0,
+ "description": "Any animal ridden for 5 kilometers.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/6985b5f0ff4c7b03aa8e652015e548d72198c575.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_FISHING",
+ "defaultvalue": 0,
+ "displayName": "A Round by the Pond",
+ "hidden": 0,
+ "description": "20 different kinds of fish caught.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/08f09efe530ca0dc577b3808a7d7a94a936df655.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_NPC_KILL",
+ "defaultvalue": 0,
+ "displayName": "Wait! Don't Kill Me!",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/41b7f8c6625f9a566c625b49f1c6cc8314202aad.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_SKIRT_ESCAPE",
+ "defaultvalue": 0,
+ "displayName": "What Are You Doing?",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/bf5e3ee4db9b1cd77223e1f30657325fda93b9f2.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_PANTS",
+ "defaultvalue": 0,
+ "displayName": "Not That I Mind...",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/31aef58a56f5e92d65437f17718c3dd01c54cf56.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_EMILE_SHOP",
+ "defaultvalue": 0,
+ "displayName": "Come Take a Look!",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/99f6388ed11b885d386eb979a2a8736290a4a235.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_KILL_EM1010",
+ "defaultvalue": 0,
+ "displayName": "Naughty Children",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/6e29e9ce653df1b4f4e51778df371f40851e409c.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_BAD_END",
+ "defaultvalue": 0,
+ "displayName": "Transcendent Being",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5649159a9e23e6f490360e0de38dd65da1fa9fdb.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ {
+ "name": "ACH_CALL_AT_KAINES_ROOM",
+ "defaultvalue": 0,
+ "displayName": "Lunar Tear",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/6b6e361e2917f8996a39b977b8df7a8fc640729d.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/524220/5cad263135d7f42d3304918370f492b353aa46f3.jpg",
+ },
+ ],
+ },
+ },
+ }
+ : {
+ "game": {
+ "gameName": "",
+ "gameVersion": "30",
+ "availableGameStats": {
+ "achievements": [
+ {
+ "name": "ACHIEVEMENT_0000",
+ "defaultvalue": 0,
+ "displayName": "The Final Verse",
+ "hidden": 0,
+ "description": "Congratulations! Thank you for playing!",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/a9369e5eddeb1486ffd849aa3820607353ddbc23.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0010",
+ "defaultvalue": 0,
+ "displayName": "The Book of Legend",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/6d11bfa34275061352048988a9202c3194259851.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0020",
+ "defaultvalue": 0,
+ "displayName": "The Wild Companion",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/8ac7d0d717ccda449162f94f81054073e9679fc5.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0030",
+ "defaultvalue": 0,
+ "displayName": "The Mellow Companion",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/1981aa8fe7fb43d11720331503fa3c9028f0e18a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0040",
+ "defaultvalue": 0,
+ "displayName": "Release",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/88d76547290c9c200536cbb1a48d255303010a5b.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0050",
+ "defaultvalue": 0,
+ "displayName": "Gratitude",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d9b8fd4cc2e4fc270edf18b177729b07e8a32cc9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0060",
+ "defaultvalue": 0,
+ "displayName": "Key Collector",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/6629579d96114c2a268898911634495f8235ce08.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0070",
+ "defaultvalue": 0,
+ "displayName": "A World in Flux",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/4b3b4bcf8267f93bb73600cc688323c2c910e0b5.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0080",
+ "defaultvalue": 0,
+ "displayName": "Combo Fanatic",
+ "hidden": 0,
+ "description": "You pulled off a 50-hit combo.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d19d83d2254a60554435a1e1d99896e96bdf64d5.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0090",
+ "defaultvalue": 0,
+ "displayName": "Combo Master",
+ "hidden": 0,
+ "description": "You pulled off a 100-hit combo.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d62e08ce00fd14c0fb72acebdfb09c838803a102.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0100",
+ "defaultvalue": 0,
+ "displayName": "The Magic Man",
+ "hidden": 0,
+ "description": "You learned every type of magic.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/2d7124c94623283b25cd1889cc6599d23b8cab5c.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0110",
+ "defaultvalue": 0,
+ "displayName": "Wordsmith",
+ "hidden": 0,
+ "description": "You collected 50 percent of all words.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/8a8b550048c266f8132d1d6c36967f2dee42a0ec.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0120",
+ "defaultvalue": 0,
+ "displayName": "Weapons Collector",
+ "hidden": 0,
+ "description": "You found every weapon.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/36b528043b6af91f11116aeed684d950722f1d60.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0130",
+ "defaultvalue": 0,
+ "displayName": "Village Handyman",
+ "hidden": 0,
+ "description": "You completed 10 quests.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/93856660d44eccb4edc0ebbb9d63a5445e688528.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0140",
+ "defaultvalue": 0,
+ "displayName": "Jack of All Trades",
+ "hidden": 0,
+ "description": "You completed 20 quests.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/ca294c1039d08d7c72682ca2a8dd88bca4a48cf0.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0150",
+ "defaultvalue": 0,
+ "displayName": "Go-To Guy",
+ "hidden": 0,
+ "description": "You completed 30 quests.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/ca294c1039d08d7c72682ca2a8dd88bca4a48cf0.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0160",
+ "defaultvalue": 0,
+ "displayName": "Dear Diary",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/676d5f5e2cd45a24738dc6078031f14d56ed923e.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0170",
+ "defaultvalue": 0,
+ "displayName": "Man of Means",
+ "hidden": 0,
+ "description": "You accumulated 1,000,000 pieces of gold.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d7fb76788b77cd73639cefd292567e596ecc912a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0180",
+ "defaultvalue": 0,
+ "displayName": "Educated Warrior",
+ "hidden": 0,
+ "description": "You read all novel segments about your friends' pasts.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d59ca2f03b4015bc9b6134d921262f8768f843f1.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0190",
+ "defaultvalue": 0,
+ "displayName": "Call Her Back",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/cfbbf964649924e25af995c47f9207e6bdb0e82a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0200",
+ "defaultvalue": 0,
+ "displayName": "Lingering Memories",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/7f65bb588647210451d003101fb6ce7ca335fb92.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0210",
+ "defaultvalue": 0,
+ "displayName": "Thank You",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/acd8fa3e92c01655b9dfeb91ea13b7c997df3825.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0220",
+ "defaultvalue": 0,
+ "displayName": "Something Very Special",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/5bc97058ccbe271edd966ca6ee9d0f4aefc0307b.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0230",
+ "defaultvalue": 0,
+ "displayName": "e8 a8 98 e6 86 b6 e3 82 b5 e3 83 bc e3 83 90 e3 83 bc",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d556f212037cb9645aa17c87c1a01a34ccacfaa5.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0240",
+ "defaultvalue": 0,
+ "displayName": "Legendary Gardener",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/636ac036eb45750ac6d125ea8d6936ed1130266d.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0250",
+ "defaultvalue": 0,
+ "displayName": "Fish of Legend",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/b5e054f5234a31c7bbc3b458c0f3ae5289069b81.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0260",
+ "defaultvalue": 0,
+ "displayName": "A Round by the Pond",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/c67e64eb2e069c0b115a5feea3cb02451df4f11a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0270",
+ "defaultvalue": 0,
+ "displayName": "Material Hunter",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/c0d448fc0d573abb796053bf7f2b12eb0b6f4c32.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0280",
+ "defaultvalue": 0,
+ "displayName": "Upgrade Apprentice",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/369a20404467230c20b158f7a3dd77699be9effd.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0290",
+ "defaultvalue": 0,
+ "displayName": "Reform Specialist",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/3abb815e20d23a97e80b2a10742cc3afd38e53cc.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0300",
+ "defaultvalue": 0,
+ "displayName": "Forging Master",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/ef2b7d68560bcfb3d32347e8884a0b736be4750b.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0310",
+ "defaultvalue": 0,
+ "displayName": "All Aboared!",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/669fb049d3b0d0ac200125736144e4e274c7620e.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0320",
+ "defaultvalue": 0,
+ "displayName": "The Sheep Whisperer",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/3c40216c95eb8d9acdb90435760c921dc6bd3954.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0330",
+ "defaultvalue": 0,
+ "displayName": "Lightspeed Fighter",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/f61dd8bd207e6a897ca5fa0297dbfbab0ff51103.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0340",
+ "defaultvalue": 0,
+ "displayName": "King of the Lost Shrine",
+ "hidden": 0,
+ "description": "You defeated Gretel within three minutes and twenty seconds.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/70883d0681b1993aa430bbcd8f9d4241f637b3ce.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0350",
+ "defaultvalue": 0,
+ "displayName": "A True Friend",
+ "hidden": 0,
+ "description": "You stopped the berserk Kainé within one minute.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/0b7d266b6959f86fa40ce0108cda8d7c7df207f9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0360",
+ "defaultvalue": 0,
+ "displayName": "Boss of the Junk Heap",
+ "hidden": 0,
+ "description": "You defeated P-33 within four and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/602644eaf05ce46aa16b2d58299210d68e114d92.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0370",
+ "defaultvalue": 0,
+ "displayName": "Scourge of The Aerie",
+ "hidden": 0,
+ "description": "You defeated Wendy within eight and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/0ef270a84980596473ae130aee58f8275ee15ef2.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0380",
+ "defaultvalue": 0,
+ "displayName": "Protector of Facade",
+ "hidden": 0,
+ "description": "You defeated Roc within three and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/d9e9a588af83858f08ad062bba1d2148e23fdcde.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0390",
+ "defaultvalue": 0,
+ "displayName": "The Little Mermaid",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/0c6faf92c5cdfb91223238bfd12a0829bd8b13a9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0400",
+ "defaultvalue": 0,
+ "displayName": "Permission Granted",
+ "hidden": 0,
+ "description": "You drove off Devola and Popola within three minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/52b749ff62320430f2440c7dff89898e9fae3473.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0410",
+ "defaultvalue": 0,
+ "displayName": "A Dirge for the Hero",
+ "hidden": 0,
+ "description": "You defeated Goose within two minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/c33b07dfe29428d7caab6a1f7c6a13c49e539ceb.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0420",
+ "defaultvalue": 0,
+ "displayName": "Soul Crusher",
+ "hidden": 0,
+ "description": "You defeated Devola and Popola within three and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/f80e7782c38c6657bb6a39f9c8904219e1cb3d1a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0430",
+ "defaultvalue": 0,
+ "displayName": "Book Burner",
+ "hidden": 0,
+ "description": "You defeated Grimoire Noir within one and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/f0c1804eee52d941045b24beb6353967ad522eb7.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0440",
+ "defaultvalue": 0,
+ "displayName": "The Once and Final King",
+ "hidden": 0,
+ "description": "You defeated the Shadowlord within four minutes and twenty seconds.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/9395b1e23f8ffecde59ac3d84d4c17844d9c13e9.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0450",
+ "defaultvalue": 0,
+ "displayName": "The Strongest Bond",
+ "hidden": 0,
+ "description": "You defeated the berserk Kainé within three and a half minutes.",
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/66a42f466f875af265a505380e0c9754c8d2c550.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/dccf83b1927eb511d794ec172eb2a8737aa5f317.jpg",
+ },
+ {
+ "name": "ACHIEVEMENT_0460",
+ "defaultvalue": 0,
+ "displayName": "Daredevil",
+ "hidden": 1,
+ "icon": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/9c70d4e9d69a09bcda912fa4323bded42b9d183a.jpg",
+ "icongray": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/1113560/67f2dca4872e41dd6ce5b923edf54bf16d42ef53.jpg",
+ },
+ ],
+ },
+ },
+ },
+ })
+ }
+
+ //Player level
+ if (/^.*IPlayerService.GetSteamLevel.v1.*$/.test(url)) {
+ console.debug(`metrics/compute/mocks > mocking steam api result > ${url}`)
+ return ({
+ status: 200,
+ data: {
+ response: {
+ player_level: faker.datatype.number(500),
+ },
+ },
+ })
+ }
+
+ //Player summary
+ if (/^.*ISteamUser.GetPlayerSummaries.v0002.*$/.test(url)) {
+ console.debug(`metrics/compute/mocks > mocking steam api result > ${url}`)
+ return ({
+ status: 200,
+ data: {
+ "response": {
+ "players": [
+ {
+ "steamid": "0",
+ "communityvisibilitystate": 3,
+ "profilestate": 1,
+ "personaname": faker.internet.userName(),
+ "commentpermission": 2,
+ "profileurl": "https://steamcommunity.com/id",
+ "avatar": null,
+ "avatarmedium": null,
+ "avatarfull": null,
+ "avatarhash": "562c4d8db58d44af73b0f5f46d2f1bb5a24e54b3",
+ "lastlogoff": 1676688802,
+ "personastate": 0,
+ "primaryclanid": "0",
+ "timecreated": 1366386002,
+ "personastateflags": 0,
+ "loccountrycode": "FR",
+ },
+ ],
+ },
+ },
+ })
+ }
+
+ //Player achievements
+ if (/^.*ISteamUserStats.GetPlayerAchievements.v0001.*$/.test(url)) {
+ console.debug(`metrics/compute/mocks > mocking steam api result > ${url}`)
+ return ({
+ status: 200,
+ data: /appid=524220/.test(url)
+ ? {
+ "playerstats": {
+ "steamID": "76561198089224516",
+ "gameName": "NieR:Automata™",
+ "achievements": [
+ {
+ "apiname": "ACH_VISITED_BUNKER",
+ "achieved": 1,
+ "unlocktime": 1565069702,
+ "name": "Resuscitated Body",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_VISITED_RUINS_CITY",
+ "achieved": 1,
+ "unlocktime": 1565376632,
+ "name": "Vestiges of Prosperity",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_DESERT_AREA",
+ "achieved": 1,
+ "unlocktime": 1565974024,
+ "name": "It's a Healthy Baby Boy!",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_AMUSEMENT_PARK",
+ "achieved": 1,
+ "unlocktime": 1565974917,
+ "name": "We Await Your Next Visit",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_ALIEN_SHIP",
+ "achieved": 1,
+ "unlocktime": 1565431434,
+ "name": "Creation and Insurrection",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_CASTLE_FOREST",
+ "achieved": 1,
+ "unlocktime": 1565433829,
+ "name": "The Mechanical Kingdom",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_SUBMERGE_CITY",
+ "achieved": 1,
+ "unlocktime": 1565434679,
+ "name": "Ruler of the Deep",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_COPY_CITY",
+ "achieved": 1,
+ "unlocktime": 1565441114,
+ "name": "Those Who Love Humans",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_ROBOT_MT",
+ "achieved": 1,
+ "unlocktime": 1565444308,
+ "name": "Iron Soul",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_A",
+ "achieved": 1,
+ "unlocktime": 1565447636,
+ "name": "One Battle Ends",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_B",
+ "achieved": 1,
+ "unlocktime": 1565532631,
+ "name": "A New Battle Begins",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_SEE_2B_DIE",
+ "achieved": 1,
+ "unlocktime": 1565537220,
+ "name": "Final Wish",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_PLAY_A2_FIRST_TIME",
+ "achieved": 1,
+ "unlocktime": 1565537371,
+ "name": "Treacherous Blade",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_LAST_REQUEST_PASCAL",
+ "achieved": 1,
+ "unlocktime": 1565548811,
+ "name": "Farewell, Pascal",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_KILL_ROBOT_BROTHER",
+ "achieved": 1,
+ "unlocktime": 1565551466,
+ "name": "Justice",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_DEVOLA_NOVEL_END",
+ "achieved": 1,
+ "unlocktime": 1565553188,
+ "name": "Crime and Punishment",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_9S",
+ "achieved": 1,
+ "unlocktime": 1565558387,
+ "name": "Leaving for the New World",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CLEAR_A2",
+ "achieved": 1,
+ "unlocktime": 1565556538,
+ "name": "Beautiful World",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_TRUE_END",
+ "achieved": 1,
+ "unlocktime": 1565558387,
+ "name": "The Minds That Emerged",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_RECOVERY_SELF_CORPSE",
+ "achieved": 1,
+ "unlocktime": 1565431434,
+ "name": "The Circle of Death",
+ "description": "Have your body collected.",
+ },
+ {
+ "apiname": "ACH_RECOVERY_CORPSE",
+ "achieved": 1,
+ "unlocktime": 1565473831,
+ "name": "Cherish Our Resources",
+ "description": "Have 100 bodies collected.",
+ },
+ {
+ "apiname": "ACH_CLEAR_QUEST_FIRST_TIME",
+ "achieved": 1,
+ "unlocktime": 1565378552,
+ "name": "First Errand",
+ "description": "Complete your first quest.",
+ },
+ {
+ "apiname": "ACH_CLEAR_MANY_QUEST",
+ "achieved": 1,
+ "unlocktime": 1565543033,
+ "name": "The Mercenary",
+ "description": "80% of all quests completed.",
+ },
+ {
+ "apiname": "ACH_COLLECT_ARCHIVE",
+ "achieved": 1,
+ "unlocktime": 1565897848,
+ "name": "Information Master",
+ "description": "80% of all archives found.",
+ },
+ {
+ "apiname": "ACH_COLLECT_EM_LIST",
+ "achieved": 1,
+ "unlocktime": 1565551063,
+ "name": "Destruction is My Job",
+ "description": "80% of all unit data unlocked.",
+ },
+ {
+ "apiname": "ACH_COLLECT_CHIP",
+ "achieved": 1,
+ "unlocktime": 1565470147,
+ "name": "Chip Collector",
+ "description": "80% of all plug-in chips collected.",
+ },
+ {
+ "apiname": "ACH_HAS_ALL_ACTIVE_SKILL",
+ "achieved": 1,
+ "unlocktime": 1565901900,
+ "name": "Weapons Maniac",
+ "description": "All Pod programs obtained.",
+ },
+ {
+ "apiname": "ACH_WEAPON_LV_MAX",
+ "achieved": 1,
+ "unlocktime": 1565520201,
+ "name": "Tools of the Trade",
+ "description": "Any weapon upgraded to the highest level.",
+ },
+ {
+ "apiname": "ACH_WEAPON_LV_MAX_ALL",
+ "achieved": 1,
+ "unlocktime": 1565892897,
+ "name": "Inorganic Blade",
+ "description": "All weapons upgraded to the highest level.",
+ },
+ {
+ "apiname": "ACH_POD_LV_MAX_ALL",
+ "achieved": 1,
+ "unlocktime": 1565892998,
+ "name": "Supreme Support Weapons",
+ "description": "All Pods upgraded to the highest level.",
+ },
+ {
+ "apiname": "ACH_CLEAR_HACKING_9S",
+ "achieved": 1,
+ "unlocktime": 1565448443,
+ "name": "Fighting's Not My Thing",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_HACKING_KILL",
+ "achieved": 1,
+ "unlocktime": 1565483879,
+ "name": "A Scanner's Power",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_REMOTO_KILL",
+ "achieved": 1,
+ "unlocktime": 1565883806,
+ "name": "Machines vs. Machines",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_BERSERKER_KILL",
+ "achieved": 1,
+ "unlocktime": 1565903370,
+ "name": "The Power of Hate",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_SHOOTING",
+ "achieved": 1,
+ "unlocktime": 1565433829,
+ "name": "Ruler of the Skies",
+ "description": "255 enemies destroyed using a flight unit.",
+ },
+ {
+ "apiname": "ACH_EXTRACTION",
+ "achieved": 1,
+ "unlocktime": 1565892563,
+ "name": "Harvest King",
+ "description": "Materials gathered at a hidden harvest point 10 times.",
+ },
+ {
+ "apiname": "ACH_GET_ALL_POD",
+ "achieved": 1,
+ "unlocktime": 1565524165,
+ "name": "Pod Hunter",
+ "description": "All Pods found.",
+ },
+ {
+ "apiname": "ACH_RICH_MAN",
+ "achieved": 1,
+ "unlocktime": 1565467456,
+ "name": "Desire Without Emotion",
+ "description": "At least 100,000 G in possession.",
+ },
+ {
+ "apiname": "ACH_ANIMAL_RUN",
+ "achieved": 1,
+ "unlocktime": 1565522297,
+ "name": "Animal Rider",
+ "description": "Any animal ridden for 5 kilometers.",
+ },
+ {
+ "apiname": "ACH_FISHING",
+ "achieved": 1,
+ "unlocktime": 1565976316,
+ "name": "A Round by the Pond",
+ "description": "20 different kinds of fish caught.",
+ },
+ {
+ "apiname": "ACH_NPC_KILL",
+ "achieved": 1,
+ "unlocktime": 1565481959,
+ "name": "Wait! Don't Kill Me!",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_SKIRT_ESCAPE",
+ "achieved": 1,
+ "unlocktime": 1565902246,
+ "name": "What Are You Doing?",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_PANTS",
+ "achieved": 1,
+ "unlocktime": 1565903882,
+ "name": "Not That I Mind...",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_EMILE_SHOP",
+ "achieved": 1,
+ "unlocktime": 1565435609,
+ "name": "Come Take a Look!",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_KILL_EM1010",
+ "achieved": 1,
+ "unlocktime": 1565889756,
+ "name": "Naughty Children",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_BAD_END",
+ "achieved": 1,
+ "unlocktime": 1565976624,
+ "name": "Transcendent Being",
+ "description": "",
+ },
+ {
+ "apiname": "ACH_CALL_AT_KAINES_ROOM",
+ "achieved": 1,
+ "unlocktime": 1565903768,
+ "name": "Lunar Tear",
+ "description": "",
+ },
+ ],
+ "success": true,
+ },
+ }
+ : {
+ "playerstats": {
+ "steamID": "76561198089224516",
+ "gameName": "NieR Replicant ver.1.22474487139...",
+ "achievements": [
+ {
+ "apiname": "ACHIEVEMENT_0000",
+ "achieved": 0,
+ "unlocktime": 0,
+ "name": "The Final Verse",
+ "description": "Congratulations! Thank you for playing!",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0010",
+ "achieved": 1,
+ "unlocktime": 1623873057,
+ "name": "The Book of Legend",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0020",
+ "achieved": 1,
+ "unlocktime": 1624294156,
+ "name": "The Wild Companion",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0030",
+ "achieved": 1,
+ "unlocktime": 1624466800,
+ "name": "The Mellow Companion",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0040",
+ "achieved": 1,
+ "unlocktime": 1624471033,
+ "name": "Release",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0050",
+ "achieved": 1,
+ "unlocktime": 1625520706,
+ "name": "Gratitude",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0060",
+ "achieved": 1,
+ "unlocktime": 1624486870,
+ "name": "Key Collector",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0070",
+ "achieved": 1,
+ "unlocktime": 1624489046,
+ "name": "A World in Flux",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0080",
+ "achieved": 1,
+ "unlocktime": 1624399122,
+ "name": "Combo Fanatic",
+ "description": "You pulled off a 50-hit combo.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0090",
+ "achieved": 1,
+ "unlocktime": 1624486870,
+ "name": "Combo Master",
+ "description": "You pulled off a 100-hit combo.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0100",
+ "achieved": 1,
+ "unlocktime": 1624397883,
+ "name": "The Magic Man",
+ "description": "You learned every type of magic.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0110",
+ "achieved": 1,
+ "unlocktime": 1625391608,
+ "name": "Wordsmith",
+ "description": "You collected 50 percent of all words.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0120",
+ "achieved": 1,
+ "unlocktime": 1625417752,
+ "name": "Weapons Collector",
+ "description": "You found every weapon.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0130",
+ "achieved": 1,
+ "unlocktime": 1624277733,
+ "name": "Village Handyman",
+ "description": "You completed 10 quests.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0140",
+ "achieved": 1,
+ "unlocktime": 1624305463,
+ "name": "Jack of All Trades",
+ "description": "You completed 20 quests.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0150",
+ "achieved": 1,
+ "unlocktime": 1624310689,
+ "name": "Go-To Guy",
+ "description": "You completed 30 quests.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0160",
+ "achieved": 1,
+ "unlocktime": 1625417752,
+ "name": "Dear Diary",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0170",
+ "achieved": 1,
+ "unlocktime": 1625396749,
+ "name": "Man of Means",
+ "description": "You accumulated 1,000,000 pieces of gold.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0180",
+ "achieved": 1,
+ "unlocktime": 1625590899,
+ "name": "Educated Warrior",
+ "description": "You read all novel segments about your friends' pasts.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0190",
+ "achieved": 1,
+ "unlocktime": 1624489684,
+ "name": "Call Her Back",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0200",
+ "achieved": 1,
+ "unlocktime": 1625437212,
+ "name": "Lingering Memories",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0210",
+ "achieved": 1,
+ "unlocktime": 1625592439,
+ "name": "Thank You",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0220",
+ "achieved": 1,
+ "unlocktime": 1625598751,
+ "name": "Something Very Special",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0230",
+ "achieved": 1,
+ "unlocktime": 1625610706,
+ "name": "e8 a8 98 e6 86 b6 e3 82 b5 e3 83 bc e3 83 90 e3 83 bc",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0240",
+ "achieved": 1,
+ "unlocktime": 1625515136,
+ "name": "Legendary Gardener",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0250",
+ "achieved": 1,
+ "unlocktime": 1625399355,
+ "name": "Fish of Legend",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0260",
+ "achieved": 1,
+ "unlocktime": 1625406465,
+ "name": "A Round by the Pond",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0270",
+ "achieved": 1,
+ "unlocktime": 1624305463,
+ "name": "Material Hunter",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0280",
+ "achieved": 1,
+ "unlocktime": 1625396391,
+ "name": "Upgrade Apprentice",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0290",
+ "achieved": 1,
+ "unlocktime": 1625517105,
+ "name": "Reform Specialist",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0300",
+ "achieved": 1,
+ "unlocktime": 1625517174,
+ "name": "Forging Master",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0310",
+ "achieved": 1,
+ "unlocktime": 1625522791,
+ "name": "All Aboared!",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0320",
+ "achieved": 1,
+ "unlocktime": 1625600972,
+ "name": "The Sheep Whisperer",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0330",
+ "achieved": 0,
+ "unlocktime": 0,
+ "name": "Lightspeed Fighter",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0340",
+ "achieved": 1,
+ "unlocktime": 1625516447,
+ "name": "King of the Lost Shrine",
+ "description": "You defeated Gretel within three minutes and twenty seconds.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0350",
+ "achieved": 1,
+ "unlocktime": 1625392124,
+ "name": "A True Friend",
+ "description": "You stopped the berserk Kainé within one minute.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0360",
+ "achieved": 1,
+ "unlocktime": 1625405608,
+ "name": "Boss of the Junk Heap",
+ "description": "You defeated P-33 within four and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0370",
+ "achieved": 1,
+ "unlocktime": 1625518485,
+ "name": "Scourge of The Aerie",
+ "description": "You defeated Wendy within eight and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0380",
+ "achieved": 1,
+ "unlocktime": 1625521457,
+ "name": "Protector of Facade",
+ "description": "You defeated Roc within three and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0390",
+ "achieved": 0,
+ "unlocktime": 0,
+ "name": "The Little Mermaid",
+ "description": "",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0400",
+ "achieved": 1,
+ "unlocktime": 1625589841,
+ "name": "Permission Granted",
+ "description": "You drove off Devola and Popola within three minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0410",
+ "achieved": 1,
+ "unlocktime": 1625437212,
+ "name": "A Dirge for the Hero",
+ "description": "You defeated Goose within two minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0420",
+ "achieved": 1,
+ "unlocktime": 1625595493,
+ "name": "Soul Crusher",
+ "description": "You defeated Devola and Popola within three and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0430",
+ "achieved": 1,
+ "unlocktime": 1625595593,
+ "name": "Book Burner",
+ "description": "You defeated Grimoire Noir within one and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0440",
+ "achieved": 1,
+ "unlocktime": 1625591368,
+ "name": "The Once and Final King",
+ "description": "You defeated the Shadowlord within four minutes and twenty seconds.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0450",
+ "achieved": 1,
+ "unlocktime": 1625596007,
+ "name": "The Strongest Bond",
+ "description": "You defeated the berserk Kainé within three and a half minutes.",
+ },
+ {
+ "apiname": "ACHIEVEMENT_0460",
+ "achieved": 1,
+ "unlocktime": 1625607419,
+ "name": "Daredevil",
+ "description": "",
+ },
+ ],
+ "success": true,
+ },
+ },
+ })
+ }
+ }
+}
diff --git a/tests/secrets.json b/tests/secrets.json
index 97ffeb99..809c6094 100644
--- a/tests/secrets.json
+++ b/tests/secrets.json
@@ -12,6 +12,7 @@
"GOOGLE_MAP_TOKEN": "MOCKED_TOKEN",
"STOCK_TOKEN":"MOCKED_TOKEN",
"CHESS_TOKEN":"MOCKED_TOKEN",
+ "STEAM_TOKEN":"MOCKED_TOKEN",
"SPLATOON_TOKEN":"{}",
"SPLATOON_STATINK_TOKEN":"MOCKED_TOKEN",
"POOPMAP_TOKEN":"MOCKED_TOKEN"