Skip to content

Commit 61bd91c

Browse files
authored
feat(plugins/community/splatoon): add plugin (#1287)
1 parent 69604e9 commit 61bd91c

File tree

17 files changed

+8345
-1
lines changed

17 files changed

+8345
-1
lines changed

.github/actions/spelling/allow.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
deno
12
gpgarmor
23
github
34
https
45
leetcode
56
pgn
67
scm
78
shas
9+
splatoon
810
ssh
911
ubuntu
1012
yargsparser

.github/actions/spelling/excludes.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ ignore$
4747
^\.github/actions/spelling/
4848
^\Q.github/readme/partials/documentation/inspirations.md\E$
4949
^\Q.github/workflows/spelling.yml\E$
50+
^source/plugins/community/splatoon/
5051
^\Qsource/plugins/sponsors/index.mjs\E$
5152
^\Qsource/plugins/stargazers/worldmap/atlas/50m_countries.geojson\E$
53+
^\Qsource/templates/classic/partials/splatoon.ejs\E$
5254
^\Qsource/templates/terminal/fonts.css\E$
5355
^\Qsource/templates/terminal/partials/screenshot.ejs\E$
5456
^\Qtests/mocks/api/github/rest/emojis/get.mjs\E$
146 KB
Loading
563 KB
Loading

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ RUN chmod +x /metrics/source/app/action/index.mjs \
1515
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
1616
&& apt-get update \
1717
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 libx11-xcb1 libxtst6 lsb-release --no-install-recommends \
18+
# Install deno for miscellaneous scripts
19+
&& apt-get install -y curl unzip \
20+
&& curl -fsSL https://deno.land/x/install/install.sh | DENO_INSTALL=/usr/local sh \
1821
# Install ruby to support github licensed gem
1922
&& apt-get install -y ruby-full git g++ cmake pkg-config libssl-dev \
2023
&& gem install licensed \

source/app/metrics/utils.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ export const filters = {
444444

445445
/**Image to base64 */
446446
export async function imgb64(image, {width, height, fallback = true} = {}) {
447+
//Ignore already encoded-base 64
448+
if ((typeof image === "string")&&(image.startsWith("data:image/png;base64")))
449+
return image
447450
//Undefined image
448451
if (!image)
449452
return fallback ? "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==" : null
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!--header-->
2+
<!--/header-->
3+
4+
## ➡️ Available options
5+
6+
<!--options-->
7+
<!--/options-->
8+
9+
## 🗝️ Obtaining a *Nintendo Switch Online* token
10+
11+
The helper script is intended to be run by [deno runtime](https://deno.land/). Either [install it locally](https://deno.land/manual/getting_started/installation) or use its [docker image](https://hub.docker.com/r/denoland/deno).
12+
13+
Run the following command in your terminal and follow instructions:
14+
```bash
15+
deno run --allow-run=deno --allow-read=profile.json --allow-write=profile.json --unstable https://raw.githubusercontent.com/lowlighter/metrics/master/source/plugins/community/splatoon/token.ts
16+
```
17+
18+
![Script](/.github/readme/imgs/plugin_splatoon_script.png)
19+
20+
![Authentication](/.github/readme/imgs/plugin_splatoon_auth.png)
21+
22+
## ℹ️ Examples workflows
23+
24+
<!--examples-->
25+
<!--/examples-->

source/plugins/community/splatoon/assets.mjs

Lines changed: 164 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- name: Example
2+
uses: lowlighter/metrics@latest
3+
with:
4+
filename: metrics.plugin.splatoon.svg
5+
token: ${{ secrets.METRICS_TOKEN }}
6+
base: ""
7+
plugin_splatoon: yes
8+
plugin_splatoon_token: ${{ secrets.SPLATOON_TOKEN }}
9+
test:
10+
skip: true
11+
prod:
12+
skip: true
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//Imports
2+
import assets from "./assets.mjs"
3+
4+
//Setup
5+
export default async function({login, q, imports, data, account}, {enabled = false, extras = false, token} = {}) {
6+
//Plugin execution
7+
try {
8+
//Check if plugin is enabled and requirements are met
9+
if ((!q.splatoon) || (!imports.metadata.plugins.splatoon.enabled(enabled, {extras})))
10+
return null
11+
12+
//Load inputs
13+
const {modes, "versus.limit":_versus_limit, "salmon.limit":_salmon_limit} = imports.metadata.plugins.splatoon.inputs({data, account, q})
14+
15+
//Save profile
16+
{
17+
const profile = `${imports.__module(import.meta.url)}/s3si/profile.json`
18+
console.debug(`metrics/compute/${login}/plugins > splatoon > saving ${profile}`)
19+
const parsed = JSON.parse(token)
20+
if (!parsed?.loginState?.sessionToken)
21+
throw new Error("Configuration is missing sessionToken")
22+
await imports.fs.writeFile(profile, token)
23+
}
24+
25+
//Fetch data
26+
const allowed = {
27+
files:["profile.json", "profile.json.swap", "export", "cache"],
28+
net:["api.imink.app", "accounts.nintendo.com", "api.accounts.nintendo.com", "api-lp1.znc.srv.nintendo.net", "api.lp1.av5ja.srv.nintendo.net"]
29+
}
30+
await imports.run(`deno run --no-prompt --cached-only --no-remote --allow-read="${allowed.files}" --allow-write="${allowed.files}" --allow-net="${allowed.net}" index.ts --exporter file --no-progress`, {cwd: `${imports.__module(import.meta.url)}/s3si`}, {prefixed:false})
31+
32+
//Read fetched data
33+
const fetched = (await Promise.all(
34+
(await imports.fs.readdir(`${imports.__module(import.meta.url)}/s3si/export`))
35+
.map(async file => JSON.parse(await imports.fs.readFile(`${imports.__module(import.meta.url)}/s3si/export/${file}`)))))
36+
.sort((a, b) => new Date(b.data.detail.playedTime) - new Date(a.data.detail.playedTime))
37+
console.debug(`metrics/compute/${login}/plugins > splatoon > fetched ${fetched.length} matches`)
38+
39+
//Versus mode
40+
let vs = null
41+
if (!((modes.length === 1)&&(modes[0] === "salmon-run"))) {
42+
vs = {
43+
matches:await Promise.all(fetched.filter(({type, data}) => (type === "VS")&&(modes.includes(data.detail.vsRule.name.toLocaleLowerCase().replace(/ /g, "-")))).slice(0, _versus_limit).map(async ({data}) => ({
44+
mode:{
45+
name:data.detail.vsRule.name,
46+
icon:await imports.imgb64(assets.modes[data.detail.vsRule.name]),
47+
},
48+
result:data.detail.judgement,
49+
knockout:data.detail.knockout ?? null,
50+
teams:await Promise.all([data.detail.myTeam, ...data.detail.otherTeams].map(async team => ({
51+
color:`#${Math.round(255*team.color.r).toString(16)}${Math.round(255*team.color.g).toString(16)}${Math.round(255*team.color.b).toString(16)}`,
52+
score:((data.detail.vsRule.name === "Turf War") ? team.result?.paintRatio*100 : team.result?.score) ?? null,
53+
players:await Promise.all(team.players.map(async ({name, byname, weapon, paint, result, isMyself:self}) => ({
54+
name,
55+
byname,
56+
self,
57+
weapon:{
58+
name:weapon.name,
59+
icon:await imports.imgb64(assets.weapons[weapon.name]),
60+
},
61+
special:{
62+
name:weapon.specialWeapon.name,
63+
icon:await imports.imgb64(assets.specials[weapon.specialWeapon.name]),
64+
},
65+
sub:{
66+
name:weapon.subWeapon.name,
67+
icon:await imports.imgb64(assets.subweapons[weapon.subWeapon.name]),
68+
},
69+
result:{
70+
paint:paint ?? 0,
71+
kill:result?.kill ?? 0,
72+
death:result?.death ?? 0,
73+
assist:result?.assist ?? 0,
74+
special:result?.special ?? 0,
75+
}
76+
})))
77+
}))),
78+
awards:data.detail.awards,
79+
date:data.detail.playedTime,
80+
duration:data.detail.duration,
81+
player:{
82+
name:data.detail.player.name,
83+
byname:data.detail.player.byname,
84+
rank:data.listNode?.udemae ?? null,
85+
},
86+
stage:{
87+
name:data.detail.vsStage.name,
88+
icon:await imports.imgb64(assets.stages[data.detail.vsStage.name]),
89+
}
90+
})))
91+
}
92+
vs.player = vs.matches.at(-1)?.player ?? null
93+
}
94+
95+
//Salmon run
96+
let salmon = null
97+
if (modes.includes("salmon-run")) {
98+
salmon = {
99+
matches:await Promise.all(fetched.filter(({type}) => type === "COOP").slice(0, _salmon_limit).map(async ({data}) => ({
100+
weapons:await Promise.all(data.detail.myResult.weapons.map(async ({name}) => ({name, icon:await imports.imgb64(assets.weapons[name])}))),
101+
special:{
102+
name:data.detail.myResult.specialWeapon.name,
103+
icon:await imports.imgb64(assets.specials[data.detail.myResult.specialWeapon.name])
104+
},
105+
eggs:{
106+
golden:data.detail.myResult.goldenDeliverCount,
107+
regular:data.detail.myResult.deliverCount,
108+
},
109+
defeated:await Promise.all(data.detail.enemyResults.map(async ({defeatCount:count, enemy:{name}}) => ({name, count, icon:await imports.imgb64(assets.salmon[name])}))),
110+
rescues:data.detail.myResult.rescueCount,
111+
rescued:data.detail.myResult.rescuedCount,
112+
waves:data.detail.waveResults.map(({deliverNorm:quota, teamDeliverCount:delivered}) => ({quota, delivered})),
113+
failed:data.detail.resultWave,
114+
hazard:Math.round(data.detail.dangerRate*100),
115+
boss:data.detail.bossResult ? {
116+
defeated:data.detail.bossResult.hasDefeatBoss,
117+
name:data.detail.bossResult.boss.name,
118+
icon:await imports.imgb64(assets.salmon[data.detail.bossResult.boss.name])
119+
} : null,
120+
stage:{
121+
name:data.detail.coopStage.name,
122+
icon:await imports.imgb64(assets.stages[data.detail.coopStage.name])
123+
},
124+
date:data.detail.playedTime,
125+
grade:data.detail.afterGrade.name,
126+
player:data.detail.myResult.player.name,
127+
}))),
128+
}
129+
salmon.player = {
130+
name:salmon.matches.at(-1)?.player ?? null,
131+
grade:salmon.matches.at(-1)?.grade ?? null,
132+
}
133+
}
134+
135+
//Results
136+
return {
137+
vs,
138+
salmon,
139+
icons:Object.fromEntries(await Promise.all(Object.entries(assets.icons).map(async ([k, v]) => [k, await imports.imgb64(v)])))
140+
}
141+
}
142+
//Handle errors
143+
catch (error) {
144+
throw imports.format.error(error)
145+
}
146+
}
147+

0 commit comments

Comments
 (0)